Skip to content

fix: remove L2 cache on the OptionSet.options collection to avoid tracker import N+1 - #24811

Draft
jason-p-pickering wants to merge 2 commits into
2.41from
fix/2.41-option-optionset-l2-cache-removal
Draft

fix: remove L2 cache on the OptionSet.options collection to avoid tracker import N+1#24811
jason-p-pickering wants to merge 2 commits into
2.41from
fix/2.41-option-optionset-l2-cache-removal

Conversation

@jason-p-pickering

@jason-p-pickering jason-p-pickering commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Live Glowroot trace on a 2.41 dev server showed an intermittent N+1 during /tracker import: thousands of individual SELECT ... FROM optionvalue WHERE optionvalueid = ? in a single request.
  • Root cause: a collection-cache-hit / entity-cache-miss combination. OptionSet.options' L2 collection cache can hold a cached list of option IDs while the individual Option entities' L2 cache has separately evicted (region-wide L2 eviction is not per-key). When that happens, Hibernate resolves each ID one at a time instead of re-running the plain WHERE optionsetid = ? collection query — which is fast (~1.4ms, indexed FK) and is what a second trace confirmed as the normal/fast path.
  • Fix: remove only the <cache> on the OptionSet.options bag. With no cached id list to reconcile against the entity cache, the collection always loads via its own single query — there's no cached/uncached combination left to produce the N+1.
  • Entity-level <cache usage="read-write"> on Option and OptionSet themselves is left in place — it's unrelated to this mechanism and still useful for other lookup paths (e.g. resolving a many-to-one reference). An earlier version of this PR removed those too, conflating this fix with a separate, unvalidated idea (a service-layer replacement cache) that isn't part of this change.

Test plan

  • xmllint --noout on the edited .hbm.xml file (well-formedness, no stray -- in comments)
  • dhis-service-core + dhis-test-integration build cleanly with the new mapping
  • CI test suite (local Postgres integration run was blocked by pre-existing environment state unrelated to this change — confirmed by reproducing the same failure on unmodified 2.41)

🤖 AI Assisted

@jason-p-pickering jason-p-pickering changed the title fix: remove Hibernate L2 cache for Option/OptionSet to avoid tracker import N+1 fix: remove L2 cache on the OptionSet.options collection to avoid tracker import N+1 Aug 8, 2026
…cker import N+1

Live Glowroot trace on a 2.41 dev server showed an intermittent N+1 during
tracker import: thousands of individual `SELECT ... FROM optionvalue WHERE
optionvalueid = ?` in a single request. Root cause is a collection-cache-hit
plus entity-cache-miss combination: when OptionSet.options' L2 collection
cache still has a cached id list but the individual Option entities' L2
cache has evicted (region-wide L2 eviction is not per-key), Hibernate
resolves each id one at a time instead of re-running the plain
`WHERE optionsetid = ?` collection query.

Removing only the collection-level cache on OptionSet.options makes
Hibernate always take that cheap collection-query path, since there's no
cached id list left to reconcile against the entity cache. Entity-level
caching on Option/OptionSet themselves is unrelated to this mechanism and
is left in place.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jason-p-pickering
jason-p-pickering force-pushed the fix/2.41-option-optionset-l2-cache-removal branch from 163a330 to 256107f Compare August 8, 2026 08:10
@jason-p-pickering
jason-p-pickering marked this pull request as draft August 14, 2026 16:00
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant