Content Drive: striped/keyed lock around UserFactoryImpl.loadUserById to prevent cross-request thundering herd on cold user cache
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Description
Follow-up from issue #37148 item 4 (tracked as #37186, spec'd in PR #37190).
That item's fix adds a sequential warm-up pass in BrowserAPIImpl.hydrateContentletsInParallel: before a page's rows are hydrated in parallel chunks, every distinct modUser/owner id the page needs is resolved once, sequentially, so the parallel chunks always hit an already-warm cache. This closes the thundering-herd race within a single listing request.
It does not close a narrower version of the same race: two different users browsing the same cold folder at the same time. If both requests need the same not-yet-cached user id concurrently, UserFactoryImpl.loadUserById (dotCMS/src/main/java/com/dotmarketing/business/UserFactoryImpl.java:104-122) has no in-flight de-duplication or locking — both requests independently miss the cache and query the database for the same user record at the same time.
Proposed fix: a striped/keyed lock around loadUserById's database-query branch, so concurrent callers resolving the same user id block on each other (the second caller gets the first caller's result from cache instead of re-querying), while callers resolving different ids remain unblocked and don't serialize against each other. Implementation options to evaluate: a fixed-size striped lock (e.g. Guava Striped<Lock> keyed by a hash of the user id) vs. a per-key lock via ConcurrentHashMap/computeIfAbsent-style coalescing.
This was flagged explicitly during review of PR #37190's spec as a required follow-up, not optional — a related recommendation had already been dropped once between spec drafts.
Acceptance Criteria
- Two concurrent requests resolving the same not-yet-cached user id result in exactly one database query for that id, not two.
- Concurrent requests resolving different user ids do not block on each other (no global lock).
- Locking mechanism choice (striped vs. per-key) documented with rationale.
- No regression to
loadUserById's existing single-threaded behavior or return semantics. - Regression/concurrency test demonstrating the fix (e.g. two threads racing on the same uncached id).
Additional Context
Related: #37148, #37186, PR #37190 (spec.md Edge Cases section documents this as a known, explicitly out-of-scope limitation of that item's warm-up fix).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in dotCMS/src/main/java/com/dotmarketing/business/UserFactoryImpl.java at loadUserById, then read the related issues and PR #37190 context. Compare the striped-lock and per-key approaches, documenting the choice and rationale. Add a concurrency regression test for two callers resolving the same uncached id, and verify different ids do not block and existing return behavior remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend, databases, performance, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100