dotCMS / dotCMS/core

Content Drive: striped/keyed lock around UserFactoryImpl.loadUserById to prevent cross-request thundering herd on cold user cache

Open
#37,335 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Team : Scout Type : Task
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.