Skip to content

OAK-12210 : added benchmark for segment cache with cachelirs, caffein…#2898

Open
rishabhdaim wants to merge 15 commits into
trunkfrom
OAK-12210
Open

OAK-12210 : added benchmark for segment cache with cachelirs, caffein…#2898
rishabhdaim wants to merge 15 commits into
trunkfrom
OAK-12210

Conversation

@rishabhdaim
Copy link
Copy Markdown
Contributor

…e & guava cache

@rishabhdaim
Copy link
Copy Markdown
Contributor Author

Result is :

SegmentCachePolicyBenchmark cacheCapacity~=1000 pool=10000 zipf=1.0

  • Scenario A: Zipfian steady-state (AbstractTest timed run) ---

CAFFEINE miss%= 26.0 hits=2,068,329 misses= 727,671 evictions= 726,671 evict%= 26.0
LIRS miss%= 27.1 hits=2,038,869 misses= 757,131 evictions=1,511,455 evict%= 54.1
GUAVA miss%= 32.5 hits=1,888,396 misses= 907,604 evictions= 906,604 evict%= 32.4

  • Scenario B: scan (50,000 segs) then Zipfian (warmup=20,000 measure=200,000 ops) ---

CAFFEINE miss%= 26.7 hits= 146,553 misses= 53,447 evictions= 53,447 evict%= 26.7
LIRS miss%= 27.0 hits= 146,063 misses= 53,937 evictions= 107,873 evict%= 53.9
GUAVA miss%= 32.4 hits= 135,119 misses= 64,881 evictions= 64,881 evict%= 32.4

  • Scenario C: cold-start regression (scan=9,000 working-set=3,000 measure=100,000 ops) ---

    scan fills TinyLFU sketch at freq=1; working-set entries start at freq=0

CAFFEINE miss%= 66.9 hits= 33,060 misses= 66,940 evictions= 66,940 evict%= 66.9
LIRS miss%= 67.8 hits= 32,187 misses= 67,813 evictions= 135,918 evict%=135.9
GUAVA miss%= 67.1 hits= 32,947 misses= 67,053 evictions= 67,053 evict%= 67.1

--- Scenario D: uniform random / cache thrash (pool=25,000 = ~25x cache measure=200,000 ops) ---
no hot data — uniform access over pool 25x cache; expected miss ~95%%

CAFFEINE miss%= 96.0 hits= 8,043 misses= 191,957 evictions= 191,957 evict%= 96.0
LIRS miss%= 96.1 hits= 7,866 misses= 192,134 evictions= 384,291 evict%=192.1
GUAVA miss%= 96.0 hits= 7,972 misses= 192,028 evictions= 192,028 evict%= 96.0

So caffeine is efficient than Guava/CacheLIRS is all scenarios.

@rishabhdaim
Copy link
Copy Markdown
Contributor Author

SegmentCachePolicyBenchmark cacheCapacity~=1023 pool=10000 zipf=1.0

  • Scenario A: Zipfian steady-state (AbstractTest timed run) ---

CAFFEINE miss%= 26.0 hits=4,083,694 misses=1,437,306 evictions=1,436,285 evict%= 26.0
LIRS miss%= 27.5 hits=4,002,834 misses=1,518,166 evictions=3,033,157 evict%= 54.9
GUAVA miss%= 32.4 hits=3,734,365 misses=1,786,635 evictions=1,785,626 evict%= 32.3

  • Scenario B: scan (50,000 segs) then Zipfian (warmup=20,000 measure=200,000 ops) ---

CAFFEINE miss%= 25.3 hits= 149,331 misses= 50,669 evictions= 50,731 evict%= 25.4
LIRS miss%= 27.5 hits= 144,914 misses= 55,086 evictions= 110,176 evict%= 55.1
GUAVA miss%= 32.1 hits= 135,768 misses= 64,232 evictions= 64,244 evict%= 32.1

  • Scenario C: cold-start regression (scan=9,000 working-set=3,000 measure=100,000 ops) ---

    scan fills TinyLFU sketch at freq=1; working-set entries start at freq=0

CAFFEINE miss%= 64.1 hits= 35,946 misses= 64,054 evictions= 63,963 evict%= 64.0
LIRS miss%= 62.9 hits= 37,084 misses= 62,916 evictions= 127,912 evict%=127.9
GUAVA miss%= 66.0 hits= 33,979 misses= 66,021 evictions= 66,028 evict%= 66.0

  • Scenario D: uniform random / cache thrash (pool=25,000 = ~24x cache measure=200,000 ops) ---

    no hot data — uniform access over pool 25x cache; expected miss ~95%%

CAFFEINE miss%= 95.5 hits= 9,091 misses= 190,909 evictions= 191,026 evict%= 95.5
LIRS miss%= 93.5 hits= 13,062 misses= 186,938 evictions= 373,890 evict%=186.9
GUAVA miss%= 95.9 hits= 8,194 misses= 191,806 evictions= 191,787 evict%= 95.9

  • Scenario E: burst new content (burst=500 segs × 20 hits warmup=50,000 measure=100,000 ops) ---

    warm Zipfian cache hit by burst of new segments; measures working-set miss rate after burst subsides

CAFFEINE miss%= 27.1 hits= 72,891 misses= 27,109 evictions= 27,110 evict%= 27.1
LIRS miss%= 27.8 hits= 72,235 misses= 27,765 evictions= 55,038 evict%= 55.0
GUAVA miss%= 32.3 hits= 67,699 misses= 32,301 evictions= 32,315 evict%= 32.3

  • Scenario F: periodic GC/diff alternation (cycles=10 zipf/cycle=10,000 scan/cycle=2,000 measure=100,000 ops) ---

    repeated small scans interleaved with Zipfian; cumulative sketch pollution vs LRU recency aging

CAFFEINE miss%= 25.3 hits= 74,680 misses= 25,320 evictions= 25,255 evict%= 25.3
LIRS miss%= 27.8 hits= 72,235 misses= 27,765 evictions= 56,165 evict%= 56.2
GUAVA miss%= 32.4 hits= 67,562 misses= 32,438 evictions= 32,446 evict%= 32.4

  • Scenario G: write-heavy import then read-back (import=50,000 recent-window=2,000 measure=100,000 ops) ---

    large sequential import followed by random reads of recently-imported segments; recency (LRU) vs frequency (Caffeine) post-import

CAFFEINE miss%= 47.2 hits= 52,801 misses= 47,199 evictions= 47,445 evict%= 47.4
LIRS miss%= 46.9 hits= 53,052 misses= 46,948 evictions= 96,454 evict%= 96.5
GUAVA miss%= 48.5 hits= 51,521 misses= 48,479 evictions= 48,477 evict%= 48.5

  • Scenario H: sliding window / temporal locality (window=1,200 ~117% of cache slide=200 pool=20,000 hits/item=2 measure=150,000 ops) ---

    hot window slides forward; pure recency (LRU) is optimal; window > cache forces evictions on every slide

CAFFEINE miss%= 63.5 hits= 54,790 misses= 95,210 evictions= 95,218 evict%= 63.5
LIRS miss%= 56.6 hits= 65,037 misses= 84,963 evictions= 169,800 evict%=113.2
GUAVA miss%= 73.2 hits= 40,259 misses= 109,741 evictions= 109,758 evict%= 73.2

@ChlineSaurus
Copy link
Copy Markdown
Contributor

Can you try the ones added here: https://github.com/ChlineSaurus/jackrabbit-oak/tree/mw/pr-2898-tmg-repro

@ben-manes
Copy link
Copy Markdown

not sure if it helps, but I have a list of traces where these are my favorites as they show a variety of scenarios. I added a patch and examples if you want to play with it.

jackrabbit.patch
diff --git a/simulator/build.gradle.kts b/simulator/build.gradle.kts
index 3083461cf..8c92fb767 100644
--- a/simulator/build.gradle.kts
+++ b/simulator/build.gradle.kts
@@ -26,6 +26,7 @@ dependencies {
   implementation(libs.ehcache3)
   implementation(libs.fastutil)
   implementation(libs.hazelcast)
+  implementation(libs.jackrabbit)
   implementation(libs.jfreechart)
   implementation(libs.fast.filter)
   implementation(libs.ascii.table)
diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/Registry.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/Registry.java
index 54d620e84..fe12a6a51 100644
--- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/Registry.java
+++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/Registry.java
@@ -61,6 +61,7 @@ import com.github.benmanes.caffeine.cache.simulator.policy.product.Ehcache3Polic
 import com.github.benmanes.caffeine.cache.simulator.policy.product.ExpiringMapPolicy;
 import com.github.benmanes.caffeine.cache.simulator.policy.product.GuavaPolicy;
 import com.github.benmanes.caffeine.cache.simulator.policy.product.HazelcastPolicy;
+import com.github.benmanes.caffeine.cache.simulator.policy.product.JackrabbitLirsPolicy;
 import com.github.benmanes.caffeine.cache.simulator.policy.product.TCachePolicy;
 import com.github.benmanes.caffeine.cache.simulator.policy.sampled.SampledPolicy;
 import com.github.benmanes.caffeine.cache.simulator.policy.sketch.WindowTinyLfuPolicy;
@@ -232,6 +233,7 @@ public final class Registry {
     registerMany(TCachePolicy.class, TCachePolicy::policies);
     registerMany(CoherencePolicy.class, CoherencePolicy::policies);
     registerMany(HazelcastPolicy.class, HazelcastPolicy::policies);
+    register(JackrabbitLirsPolicy.class, JackrabbitLirsPolicy::new);
     registerMany(ExpiringMapPolicy.class, ExpiringMapPolicy::policies);
   }
 
diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/JackrabbitLirsPolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/JackrabbitLirsPolicy.java
new file mode 100644
index 000000000..afb05f01c
--- /dev/null
+++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/product/JackrabbitLirsPolicy.java
@@ -0,0 +1,59 @@
+package com.github.benmanes.caffeine.cache.simulator.policy.product;
+
+import static com.github.benmanes.caffeine.cache.simulator.policy.Policy.Characteristic.WEIGHTED;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.cache.CacheLIRS;
+
+import com.github.benmanes.caffeine.cache.simulator.BasicSettings;
+import com.github.benmanes.caffeine.cache.simulator.policy.AccessEvent;
+import com.github.benmanes.caffeine.cache.simulator.policy.Policy;
+import com.github.benmanes.caffeine.cache.simulator.policy.Policy.PolicySpec;
+import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
+import com.typesafe.config.Config;
+
+@PolicySpec(name = "product.JackrabbitLirs", characteristics = WEIGHTED)
+public final class JackrabbitLirsPolicy implements Policy {
+  private final CacheLIRS<Long, AccessEvent> cache;
+  private final PolicyStats policyStats;
+
+  public JackrabbitLirsPolicy(Config config, Set<Characteristic> characteristics) {
+    var settings = new BasicSettings(config);
+    this.policyStats = new PolicyStats(name());
+    var builder = CacheLIRS.<Long, AccessEvent>newBuilder()
+        .evictionCallback((_, _, _) -> policyStats.recordEviction())
+        .recordStats();
+    if (characteristics.contains(WEIGHTED)) {
+      builder.maximumWeight(settings.maximumSize());
+      builder.weigher((Long _, AccessEvent value) -> value.weight());
+    } else {
+      builder.maximumSize(settings.maximumSize());
+    }
+    this.cache = builder.build();
+  }
+
+  @Override
+  public void record(AccessEvent event) {
+    Object value = cache.getUnchecked(event.longKey());
+    if (value == null) {
+      cache.put(event.longKey(), event);
+      policyStats.recordMiss();
+    } else {
+      policyStats.recordHit();
+    }
+  }
+
+  @Override
+  public PolicyStats stats() {
+    return policyStats;
+  }
+
+  @Override
+  public void finished() {
+    var stats = cache.stats();
+    checkState(policyStats.hitCount() == stats.hitCount());
+    checkState(policyStats.missCount() == stats.missCount());
+  }
+}
diff --git a/simulator/src/main/resources/reference.conf b/simulator/src/main/resources/reference.conf
index 427efa526..fc854adcd 100644
--- a/simulator/src/main/resources/reference.conf
+++ b/simulator/src/main/resources/reference.conf
@@ -107,6 +107,7 @@ caffeine.simulator {
     product.Coherence,
     product.Hazelcast,
     product.ExpiringMap,
+    product.JackrabbitLirs,
   ]
 
   # The admission policy (opposite of eviction policy)
SQL Database
./gradlew simulator:simulate \
  --maximumSize=1_000_000,2_000_000,3_000_000,4_000_000,5_000_000,6_000_000,7_000_000,8_000_000 \
  -Dcaffeine.simulator.files.paths.0="arc:/Users/ben/Documents/traces/arc/DS1.lis.xz" \
  -Dcaffeine.simulator.policies.0="product.JackrabbitLirs" \
  -Dcaffeine.simulator.policies.1="product.Caffeine" \
  -Dcaffeine.simulator.policies.2="product.Guava" \
  -Dcaffeine.simulator.policies.3="irr.Lirs" \
  -Dcaffeine.simulator.policies.4="opt.Clairvoyant" \
  --title="Database" -PjvmArgs=-Xmx12g
hit_rate
Stress test
./gradlew simulator:run -q \
  -Dcaffeine.simulator.policies.0="product.JackrabbitLirs" \
  -Dcaffeine.simulator.policies.1="product.Caffeine" \
  -Dcaffeine.simulator.policies.2="product.Guava" \
  -Dcaffeine.simulator.policies.3="irr.Lirs" \
  -Dcaffeine.simulator.policies.4="opt.Clairvoyant" \
  -Dcaffeine.simulator.files.paths.0="corda:trace_vaultservice_large.gz" \
  -Dcaffeine.simulator.files.paths.1="lirs:loop.trace.gz" \
  -Dcaffeine.simulator.files.paths.2="lirs:loop.trace.gz" \
  -Dcaffeine.simulator.files.paths.3="lirs:loop.trace.gz" \
  -Dcaffeine.simulator.files.paths.4="lirs:loop.trace.gz" \
  -Dcaffeine.simulator.files.paths.5="lirs:loop.trace.gz" \
  -Dcaffeine.simulator.files.paths.6="corda:trace_vaultservice_large.gz"
╔════════════════════════╤══════════╤═══════════╤═══════════╤═══════════╤═══════════╤════════════╤═══════════╤══════════╗
║ Policy                 │ Hit Rate │ Miss Rate │ Hits      │ Misses    │ Requests  │ Evictions  │ Steps     │ Time     ║
╠════════════════════════╪══════════╪═══════════╪═══════════╪═══════════╪═══════════╪════════════╪═══════════╪══════════╣
║ irr.Lirs               │  20.16 % │   79.84 % │ 1,264,261 │ 5,007,883 │ 6,272,144 │  5,007,371 │ 8,782,107 │ 451.6 ms ║
╟────────────────────────┼──────────┼───────────┼───────────┼───────────┼───────────┼────────────┼───────────┼──────────╢
║ opt.Clairvoyant        │  40.30 % │   59.70 % │ 2,527,704 │ 3,744,440 │ 6,272,144 │  3,743,928 │           │    1.7 s ║
╟────────────────────────┼──────────┼───────────┼───────────┼───────────┼───────────┼────────────┼───────────┼──────────╢
║ product.Caffeine       │  38.51 % │   61.49 % │ 2,415,227 │ 3,856,917 │ 6,272,144 │  3,856,405 │           │    1.2 s ║
╟────────────────────────┼──────────┼───────────┼───────────┼───────────┼───────────┼────────────┼───────────┼──────────╢
║ product.Guava          │  19.90 % │   80.10 % │ 1,248,214 │ 5,023,930 │ 6,272,144 │  5,023,418 │           │    1.1 s ║
╟────────────────────────┼──────────┼───────────┼───────────┼───────────┼───────────┼────────────┼───────────┼──────────╢
║ product.JackrabbitLirs │   0.48 % │   99.52 % │    29,838 │ 6,242,306 │ 6,272,144 │ 12,483,264 │           │    1.1 s ║
╚════════════════════════╧══════════╧═══════════╧═══════════╧═══════════╧═══════════╧════════════╧═══════════╧══════════╝

@rishabhdaim
Copy link
Copy Markdown
Contributor Author

rishabhdaim commented May 18, 2026

Build is failing due to resource leakage in DocumentNodeStoreIT test. To be fixed by : #2905

Copy link
Copy Markdown
Contributor

@ChlineSaurus ChlineSaurus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI didn't flag anything major.
Minor findings: Little test coverage, some real world use cases might not be covered by benchmarks, fallback to CAFFEINE_WITH_EXPIRY as a default might not be expected and is nowhere documented/mentioned.

@rishabhdaim rishabhdaim force-pushed the OAK-12210 branch 4 times, most recently from 955e703 to 126104d Compare May 19, 2026 05:00
rishabhdaim and others added 13 commits May 19, 2026 13:28
JIRA-1: CaffeineCacheAdapter.invalidateAll() now calls cleanUp() so
pending eviction notifications are flushed synchronously before the
map is cleared, preventing stale weight in getCacheStats().

JIRA-3: SegmentCache.clear() resets the weight counter; recordHit()
signature changed to recordHit(SegmentId) so L1 hits are propagated
to L2 (FT_NOTIFY_L2_ON_L1_HIT toggle, default enabled), keeping
W-TinyLFU frequency/LRU recency accurate for memoised segments.
SegmentId.onAccess widened from Runnable to Consumer<SegmentId>.

JIRA-5: PersistentDiskCache fixes for dirty-read and size accounting.

JIRA-6: Benchmark warmup raised to 200K ops to saturate W-TinyLFU
sketch to freq=15; SKIP_CACHE_CLEAR replaced by
-Doak.benchmark.clearCacheOnCompaction toggle; scenario descriptions
updated with measured freeze numbers and JIRA-4 fix reference.
AbstractFileStore exposes clearSegmentCache() for benchmark use.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d GUAVA policy

Two branches in SegmentCache were untested:
- recordHit() path when FT_NOTIFY_L2_ON_L1_HIT is disabled (stats still counted)
- newSegmentCache(long, GUAVA) path: GuavaCacheAdapter + buildGuavaCache

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rishabhdaim rishabhdaim changed the title OAK-12210 : added benchmark doe segment cache with cachelirs, caffein… OAK-12210 : added benchmark for segment cache with cachelirs, caffein… May 21, 2026
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
19 Security Hotspots
0.8% Coverage on New Code (required ≥ 80%)
8.5% Duplication on New Code (required ≤ 3%)
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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.

4 participants