Skip to content

Commit 5c5d155

Browse files
committed
perf: predefine hot L2 regions in ehcache.xml as store-by-reference
Regions created on demand through the jsr107 template are store-by-value: every get and put copies the entry through SerializingCopier, inside the READ_WRITE region lock critical section. Predefining a region in ehcache.xml keeps ehcache-native store-by-reference semantics (Hibernate caches disassembled, immutable entries, so by-reference is safe; 2.41 ran Ehcache 2 by-reference for years). Region list and heap bounds come from measured traffic: the hot metadata regions from a read/write metadata ramp, plus the tracker-import hot set (the Option region alone takes ~98M gets per 25 minute import run). Measured effect in the full combination: SerializingCopier wall samples 236,287 -> 1,057. AI Assisted
1 parent 31ee50d commit 5c5d155

2 files changed

Lines changed: 149 additions & 10 deletions

File tree

dhis-2/dhis-support/dhis-support-hibernate/src/main/resources/ehcache.xml

Lines changed: 133 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,139 @@
4545
</cache>
4646

4747
<!--
48-
NOTE: The old configuration did not define specific caches for entities.
49-
Entities will use the 'defaultCacheTemplate' settings unless you define
50-
specific <cache> elements for them below using their fully qualified class name as the alias.
51-
Example:
52-
<cache alias="org.hisp.dhis.organisationunit.OrganisationUnit" uses-template="defaultCacheTemplate">
53-
<resources>
54-
<heap unit="entries">50000</heap> <!- More specific size ->
55-
</resources>
56-
</cache>
48+
l2-cache-truth Phase 4.2 (EXPERIMENT): the hottest Hibernate regions measured in the
49+
Phase 3 concurrency baseline are PREDEFINED here as ehcache-native caches. Caches created
50+
on demand through the JSR-107 API (hibernate-jcache MissingCacheStrategy.CREATE) get the
51+
jsr107 default MutableConfiguration semantics, which force store-BY-VALUE: every get and
52+
put serializes the entry (SerializingCopier) INSIDE the READ_WRITE region lock critical
53+
section (62k wall samples per Phase 3 read run). Predefined caches keep ehcache-native
54+
store-by-reference: no copier, no serialization on the cache path. Hibernate stores
55+
disassembled (immutable) cache entries, so by-reference is safe; 2.41 ran Ehcache 2
56+
by-reference for years.
57+
58+
Region list and heap bounds derive from phase3-results.json get counts (top regions by
59+
gets across all cells); bounds are sized for large national deployments, not the demo DB.
60+
Remaining regions still fall back to the jsr107 template above (bounded, store-by-value).
5761
-->
5862

63+
<!-- Entity regions -->
64+
<cache alias="org.hisp.dhis.organisationunit.OrganisationUnit">
65+
<expiry><ttl unit="seconds">21600</ttl></expiry>
66+
<resources><heap unit="entries">200000</heap></resources>
67+
</cache>
68+
<cache alias="org.hisp.dhis.category.CategoryOption">
69+
<expiry><ttl unit="seconds">21600</ttl></expiry>
70+
<resources><heap unit="entries">100000</heap></resources>
71+
</cache>
72+
<cache alias="org.hisp.dhis.dataelement.DataElement">
73+
<expiry><ttl unit="seconds">21600</ttl></expiry>
74+
<resources><heap unit="entries">100000</heap></resources>
75+
</cache>
76+
<cache alias="org.hisp.dhis.dataset.DataSet">
77+
<expiry><ttl unit="seconds">21600</ttl></expiry>
78+
<resources><heap unit="entries">20000</heap></resources>
79+
</cache>
80+
<cache alias="org.hisp.dhis.dataset.DataSetElement">
81+
<expiry><ttl unit="seconds">21600</ttl></expiry>
82+
<resources><heap unit="entries">200000</heap></resources>
83+
</cache>
84+
<cache alias="org.hisp.dhis.category.Category">
85+
<expiry><ttl unit="seconds">21600</ttl></expiry>
86+
<resources><heap unit="entries">20000</heap></resources>
87+
</cache>
88+
<cache alias="org.hisp.dhis.category.CategoryOptionCombo">
89+
<expiry><ttl unit="seconds">21600</ttl></expiry>
90+
<resources><heap unit="entries">500000</heap></resources>
91+
</cache>
92+
<cache alias="org.hisp.dhis.user.User">
93+
<expiry><ttl unit="seconds">21600</ttl></expiry>
94+
<resources><heap unit="entries">100000</heap></resources>
95+
</cache>
96+
<cache alias="org.hisp.dhis.user.UserGroup">
97+
<expiry><ttl unit="seconds">21600</ttl></expiry>
98+
<resources><heap unit="entries">50000</heap></resources>
99+
</cache>
100+
<cache alias="org.hisp.dhis.user.UserRole">
101+
<expiry><ttl unit="seconds">21600</ttl></expiry>
102+
<resources><heap unit="entries">20000</heap></resources>
103+
</cache>
104+
<cache alias="org.hisp.dhis.program.Program">
105+
<expiry><ttl unit="seconds">21600</ttl></expiry>
106+
<resources><heap unit="entries">10000</heap></resources>
107+
</cache>
108+
<cache alias="org.hisp.dhis.period.PeriodType">
109+
<expiry><ttl unit="seconds">21600</ttl></expiry>
110+
<resources><heap unit="entries">1000</heap></resources>
111+
</cache>
112+
<cache alias="org.hisp.dhis.period.Period">
113+
<expiry><ttl unit="seconds">21600</ttl></expiry>
114+
<resources><heap unit="entries">100000</heap></resources>
115+
</cache>
116+
<!-- Tracker-import hot regions (Phase 4 tracker workload: Option region alone takes
117+
~98M gets per 25-min run; missing these from the predefined list left the hottest
118+
path store-by-value) -->
119+
<cache alias="org.hisp.dhis.option.Option">
120+
<expiry><ttl unit="seconds">21600</ttl></expiry>
121+
<resources><heap unit="entries">200000</heap></resources>
122+
</cache>
123+
<cache alias="org.hisp.dhis.trackedentity.TrackedEntityAttribute">
124+
<expiry><ttl unit="seconds">21600</ttl></expiry>
125+
<resources><heap unit="entries">20000</heap></resources>
126+
</cache>
127+
<cache alias="org.hisp.dhis.trackedentity.TrackedEntityType">
128+
<expiry><ttl unit="seconds">21600</ttl></expiry>
129+
<resources><heap unit="entries">1000</heap></resources>
130+
</cache>
131+
<cache alias="org.hisp.dhis.option.OptionSet.options">
132+
<expiry><ttl unit="seconds">21600</ttl></expiry>
133+
<resources><heap unit="entries">20000</heap></resources>
134+
</cache>
135+
136+
137+
<!-- Collection regions (element ids only, entries are small) -->
138+
<cache alias="org.hisp.dhis.organisationunit.OrganisationUnit.children">
139+
<expiry><ttl unit="seconds">21600</ttl></expiry>
140+
<resources><heap unit="entries">200000</heap></resources>
141+
</cache>
142+
<cache alias="org.hisp.dhis.category.CategoryCombo.categories">
143+
<expiry><ttl unit="seconds">21600</ttl></expiry>
144+
<resources><heap unit="entries">20000</heap></resources>
145+
</cache>
146+
<cache alias="org.hisp.dhis.category.CategoryCombo.optionCombos">
147+
<expiry><ttl unit="seconds">21600</ttl></expiry>
148+
<resources><heap unit="entries">20000</heap></resources>
149+
</cache>
150+
<cache alias="org.hisp.dhis.category.Category.categoryOptions">
151+
<expiry><ttl unit="seconds">21600</ttl></expiry>
152+
<resources><heap unit="entries">20000</heap></resources>
153+
</cache>
154+
<cache alias="org.hisp.dhis.category.CategoryOptionCombo.categoryOptions">
155+
<expiry><ttl unit="seconds">21600</ttl></expiry>
156+
<resources><heap unit="entries">500000</heap></resources>
157+
</cache>
158+
<cache alias="org.hisp.dhis.user.UserGroup.managedGroups">
159+
<expiry><ttl unit="seconds">21600</ttl></expiry>
160+
<resources><heap unit="entries">50000</heap></resources>
161+
</cache>
162+
<cache alias="org.hisp.dhis.user.UserRole.restrictions">
163+
<expiry><ttl unit="seconds">21600</ttl></expiry>
164+
<resources><heap unit="entries">20000</heap></resources>
165+
</cache>
166+
<cache alias="org.hisp.dhis.user.UserRole.authorities">
167+
<expiry><ttl unit="seconds">21600</ttl></expiry>
168+
<resources><heap unit="entries">20000</heap></resources>
169+
</cache>
170+
<cache alias="org.hisp.dhis.dataelement.DataElement.dataSetElements">
171+
<expiry><ttl unit="seconds">21600</ttl></expiry>
172+
<resources><heap unit="entries">100000</heap></resources>
173+
</cache>
174+
<cache alias="org.hisp.dhis.dataelement.DataElement.groups">
175+
<expiry><ttl unit="seconds">21600</ttl></expiry>
176+
<resources><heap unit="entries">100000</heap></resources>
177+
</cache>
178+
<cache alias="org.hisp.dhis.dataelement.DataElement.legendSets">
179+
<expiry><ttl unit="seconds">21600</ttl></expiry>
180+
<resources><heap unit="entries">100000</heap></resources>
181+
</cache>
182+
59183
</config>

dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/cache/HibernateEhcacheConfigFileTest.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.hibernate.cache.jcache.internal.JCacheRegionFactory;
4747
import org.hibernate.cache.spi.RegionFactory;
4848
import org.hibernate.engine.spi.SessionFactoryImplementor;
49+
import org.hisp.dhis.attribute.Attribute;
4950
import org.hisp.dhis.cache.HibernateEhcacheConfigFileTest.DhisConfig;
5051
import org.hisp.dhis.external.conf.ConfigurationKey;
5152
import org.hisp.dhis.test.config.PostgresTestConfigOverride;
@@ -86,6 +87,13 @@ public PostgresTestConfigOverride postgresTestConfigOverride() {
8687
/** Heap bound from the ehcache.xml default cache template (jsr107:defaults). */
8788
private static final long EHCACHE_XML_TEMPLATE_HEAP_ENTRIES = 1_000_000;
8889

90+
/**
91+
* Heap bound declared explicitly for the predefined {@code org.hisp.dhis.user.User} region in
92+
* ehcache.xml. Hot regions are declared individually so they can be sized and stored by
93+
* reference; the rest still inherit the default template above.
94+
*/
95+
private static final long EHCACHE_XML_USER_HEAP_ENTRIES = 100_000;
96+
8997
@Autowired private EntityManagerFactory entityManagerFactory;
9098

9199
@Test
@@ -105,10 +113,17 @@ void cacheManagerIsConfiguredFromEhcacheXml() {
105113
void regionsCarryTheEhcacheXmlHeapBounds() {
106114
CacheManager cacheManager = cacheManager();
107115

116+
// Attribute has no explicit <cache> element, so it must inherit the default template.
108117
assertEquals(
109118
EHCACHE_XML_TEMPLATE_HEAP_ENTRIES,
119+
heapEntries(cacheManager, Attribute.class.getName()),
120+
"entity regions without an explicit declaration must carry the heap bound of the"
121+
+ " ehcache.xml default template");
122+
// User is declared explicitly in ehcache.xml and must carry its own bound, not the template's.
123+
assertEquals(
124+
EHCACHE_XML_USER_HEAP_ENTRIES,
110125
heapEntries(cacheManager, User.class.getName()),
111-
"entity regions must carry the heap bound of the ehcache.xml default template");
126+
"explicitly declared entity regions must carry their own ehcache.xml heap bound");
112127
assertEquals(
113128
EHCACHE_XML_TIMESTAMPS_HEAP_ENTRIES,
114129
heapEntries(cacheManager, "default-update-timestamps-region"),

0 commit comments

Comments
 (0)