You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by da...@apache.org on 2026/10/06 08:32:40 UTC
(jackrabbit-oak) branch issue/OAK-12437 created (now bb16cf5e02)
This is an automated email from the ASF dual-hosted git repository.
rishabhdaim pushed a change to branch issue/OAK-12437
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
at bb16cf5e02 OAK-12437: gate Document async maintenance behind feature toggle
This branch includes the following new commits:
new bb16cf5e02 OAK-12437: gate Document async maintenance behind feature toggle
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(jackrabbit-oak) 01/01: OAK-12437: gate Document async maintenance behind feature toggle
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rishabhdaim pushed a commit to branch issue/OAK-12437
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit bb16cf5e0204dd33d27cf98e718abbd254383f83
Author: rishabhdaim <ri...@gmail.com>
AuthorDate: Tue Oct 6 14:02:15 2026 +0530
OAK-12437: gate Document async maintenance behind feature toggle
---
.../plugins/document/DocumentNodeStoreBuilder.java | 43 +++++++++++++++++--
.../plugins/document/DocumentNodeStoreService.java | 5 +++
.../document/DocumentCacheConcurrencyTest.java | 18 +++++---
.../DocumentCacheFeatureLifecycleTest.java | 15 +++++++
.../document/DocumentCacheMaintenanceTest.java | 49 +++++++++++++++++++---
.../oak/plugins/document/MemoryDiffCacheTest.java | 19 +++++++--
specs/oak-12425-cache-maintenance/PLAN.md | 6 +--
7 files changed, 134 insertions(+), 21 deletions(-)
diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java
index 6601d40184..7dbcbc0b63 100644
--- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java
+++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java
@@ -124,6 +124,12 @@ public class DocumentNodeStoreBuilder<T extends DocumentNodeStoreBuilder<T>> {
static final AtomicBoolean FT_CAFFEINE_CACHE_ENABLED = new AtomicBoolean(
Boolean.parseBoolean(System.getProperty("oak.documentMK.caffeineCache", "true")));
+ /** Feature toggle for opting individual Document caches into asynchronous maintenance. */
+ static final String FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE = "FT_OAK-12437";
+
+ /** When disabled, configured asynchronous modes are ignored for every Document cache. */
+ static final AtomicBoolean FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED = new AtomicBoolean();
+
/**
* Number of content updates that need to happen before the updates
* are automatically purged to the private branch.
@@ -156,6 +162,7 @@ public class DocumentNodeStoreBuilder<T extends DocumentNodeStoreBuilder<T>> {
private int diffCachePercentage = DEFAULT_DIFF_CACHE_PERCENTAGE;
private int cacheSegmentCount = DEFAULT_CACHE_SEGMENT_COUNT;
private int cacheStackMoveDistance = DEFAULT_CACHE_STACK_MOVE_DISTANCE;
+ private final Map<CacheType, CacheBuilder.MaintenanceMode> cacheMaintenanceModes = new HashMap<>();
private boolean useSimpleRevision;
private boolean disableBranches;
private boolean prefetchExternalChanges;
@@ -621,6 +628,21 @@ public class DocumentNodeStoreBuilder<T extends DocumentNodeStoreBuilder<T>> {
return thisBuilder();
}
+ /**
+ * Sets the requested Caffeine maintenance mode for one DocumentNodeStore cache. ASYNC is
+ * honored only while {@link #FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE} is enabled; otherwise the
+ * cache uses SYNC. Unconfigured caches use SYNC.
+ *
+ * @param cacheType the cache to configure
+ * @param maintenanceMode the requested maintenance execution mode
+ * @return this builder
+ */
+ public T setCacheMaintenanceMode(@NotNull CacheType cacheType,
+ @NotNull CacheBuilder.MaintenanceMode maintenanceMode) {
+ cacheMaintenanceModes.put(requireNonNull(cacheType), requireNonNull(maintenanceMode));
+ return thisBuilder();
+ }
+
public int getAsyncDelay() {
return asyncDelay;
}
@@ -1049,11 +1071,16 @@ public class DocumentNodeStoreBuilder<T extends DocumentNodeStoreBuilder<T>> {
if (p == null) {
p = getPersistentCache();
}
+ CacheBuilder.MaintenanceMode mode = cacheMaintenanceModes.getOrDefault(cacheType,
+ CacheBuilder.MaintenanceMode.SYNC);
+ if (!FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.get()) {
+ mode = CacheBuilder.MaintenanceMode.SYNC;
+ }
if (p == null || !p.isCacheEnabled(cacheType)) {
- return buildCache(cacheType.name(), maxWeight, new CopyOnWriteArraySet<>(), false);
+ return buildCache(cacheType.name(), maxWeight, new CopyOnWriteArraySet<>(), false, mode);
}
Set<EvictionListener<K, CacheEntry<V>>> listeners = new CopyOnWriteArraySet<>();
- Cache<K, CacheEntry<V>> entries = buildCache(cacheType.name(), maxWeight, listeners, true);
+ Cache<K, CacheEntry<V>> entries = buildCache(cacheType.name(), maxWeight, listeners, true, mode);
Cache<K, V> cache = p.wrap(docNodeStore, docStore, entries, cacheType, statisticsProvider);
listeners.add((EvictionListener<K, CacheEntry<V>>) cache);
PersistentCacheStats stats = PersistentCache.getPersistentCacheStats(cache);
@@ -1098,6 +1125,16 @@ public class DocumentNodeStoreBuilder<T extends DocumentNodeStoreBuilder<T>> {
long maxWeight,
final Set<EvictionListener<K, V>> listeners,
boolean registerEvictionListener) {
+ return buildCache(module, maxWeight, listeners, registerEvictionListener,
+ CacheBuilder.MaintenanceMode.SYNC);
+ }
+
+ private <K extends CacheValue, V extends CacheValue> Cache<K, V> buildCache(
+ String module,
+ long maxWeight,
+ final Set<EvictionListener<K, V>> listeners,
+ boolean registerEvictionListener,
+ CacheBuilder.MaintenanceMode maintenanceMode) {
// do not use LIRS cache when maxWeight is zero (OAK-6953)
if (!FT_CAFFEINE_CACHE_ENABLED.get() && LIRS_CACHE && maxWeight > 0) {
return CacheLIRS.<K, V>newBuilder()
@@ -1117,7 +1154,7 @@ public class DocumentNodeStoreBuilder<T extends DocumentNodeStoreBuilder<T>> {
}
CacheBuilder<K, V> builder = CacheBuilder.<K, V>newBuilder()
.maximumWeight(maxWeight)
- .maintenanceMode(CacheBuilder.MaintenanceMode.SYNC)
+ .maintenanceMode(maintenanceMode)
.weigher(this::weighCacheValue)
.recordStats();
if (registerEvictionListener) {
diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
index f11676763b..a611a248e1 100644
--- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
+++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
@@ -312,6 +312,11 @@ public class DocumentNodeStoreService {
new FeatureToggle(DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE,
DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED),
Collections.emptyMap()));
+ DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.set(false);
+ addRegistration(whiteboard.register(FeatureToggle.class,
+ new FeatureToggle(DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE,
+ DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED),
+ Collections.emptyMap()));
registerNodeStoreIfPossible();
}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheConcurrencyTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheConcurrencyTest.java
index ea9ddd59b8..6bd2996605 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheConcurrencyTest.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheConcurrencyTest.java
@@ -25,6 +25,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import org.apache.jackrabbit.oak.cache.api.CacheBuilder.MaintenanceMode;
+import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType;
import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
@@ -44,19 +46,25 @@ public class DocumentCacheConcurrencyTest {
@Test
public void coldNodeReadCompletesDuringChildrenLoad() throws Exception {
boolean previous = DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED.getAndSet(true);
+ boolean previousAsync = DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.getAndSet(true);
try {
- verifyColdNodeRead();
+ for (MaintenanceMode mode : MaintenanceMode.values()) {
+ verifyColdNodeRead(mode);
+ }
} finally {
DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED.set(previous);
+ DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.set(previousAsync);
}
}
- private void verifyColdNodeRead() throws Exception {
+ private void verifyColdNodeRead(MaintenanceMode mode) throws Exception {
PausingDocumentStore documents = new PausingDocumentStore();
DocumentNodeStoreBuilder<?> builder = new DocumentNodeStoreBuilder<>()
.setDocumentStore(documents)
.setAsyncDelay(0)
- .setPersistentCache(temporaryFolder.newFolder().getAbsolutePath());
+ .setPersistentCache(temporaryFolder.newFolder().getAbsolutePath())
+ .setCacheMaintenanceMode(CacheType.NODE, mode)
+ .setCacheMaintenanceMode(CacheType.CHILDREN, mode);
DocumentNodeStore store = builder.build();
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
@@ -68,11 +76,11 @@ public class DocumentCacheConcurrencyTest {
store.getNodeChildrenCache().invalidateAll();
documents.pause = true;
Future<DocumentNodeState.Children> children = executor.submit(() -> store.getChildren(parent, "", 10));
- Assert.assertTrue("Children loader did not start",
+ Assert.assertTrue("Children loader did not start: " + mode,
documents.entered.await(10, TimeUnit.SECONDS));
Future<DocumentNodeState> node = executor.submit(() ->
store.getNode(Path.fromString("/a"), parent.getLastRevision()));
- Assert.assertNotNull("Node read must complete while children load is paused",
+ Assert.assertNotNull("Node read must complete while children load is paused: " + mode,
node.get(5, TimeUnit.SECONDS));
documents.release.countDown();
Assert.assertTrue(children.get(10, TimeUnit.SECONDS).children.contains("a"));
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheFeatureLifecycleTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheFeatureLifecycleTest.java
index 0d5afc6d61..cbdc08b48c 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheFeatureLifecycleTest.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheFeatureLifecycleTest.java
@@ -53,6 +53,7 @@ public class DocumentCacheFeatureLifecycleTest {
"jdbc:h2:" + folder.newFolder().getAbsolutePath() + "/repository", "sa", "");
DocumentNodeStoreService service = null;
boolean previous = DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED.getAndSet(true);
+ boolean previousAsync = DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.getAndSet(false);
try {
context.registerService(StatisticsProvider.class, StatisticsProvider.NOOP);
context.registerInjectActivateService(new DocumentNodeStoreService.Preset());
@@ -64,6 +65,8 @@ public class DocumentCacheFeatureLifecycleTest {
config.put("repository.home", folder.newFolder().getAbsolutePath());
MockOsgi.setConfigForPid(context.bundleContext(), DocumentNodeStoreService.class.getName(), config);
service = activateService();
+ Assert.assertFalse(asyncToggle().isEnabled());
+ asyncToggle().setEnabled(true);
Cache<PathRev, DocumentNodeState> existing = context.getService(DocumentNodeStore.class).getNodeCache();
Assert.assertTrue(existing instanceof CaffeineCacheAdapter);
toggle().setEnabled(false);
@@ -76,8 +79,12 @@ public class DocumentCacheFeatureLifecycleTest {
service = null;
Assert.assertFalse(Arrays.stream(context.getServices(FeatureToggle.class, null))
.anyMatch(candidate -> DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE.equals(candidate.getName())));
+ Assert.assertFalse(Arrays.stream(context.getServices(FeatureToggle.class, null))
+ .anyMatch(candidate -> DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE
+ .equals(candidate.getName())));
service = activateService();
Assert.assertFalse(toggle().isEnabled());
+ Assert.assertFalse(asyncToggle().isEnabled());
Assert.assertEquals("LirsLoadingCacheAdapter",
context.getService(DocumentNodeStore.class).getNodeCache().getClass().getSimpleName());
} finally {
@@ -86,6 +93,7 @@ public class DocumentCacheFeatureLifecycleTest {
}
dataSource.dispose();
DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED.set(previous);
+ DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.set(previousAsync);
}
}
@@ -101,4 +109,11 @@ public class DocumentCacheFeatureLifecycleTest {
.filter(candidate -> DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE.equals(candidate.getName()))
.findFirst().orElseThrow(() -> new AssertionError("Cache feature toggle is not registered"));
}
+
+ private FeatureToggle asyncToggle() {
+ return Arrays.stream(context.getServices(FeatureToggle.class, null))
+ .filter(candidate -> DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE
+ .equals(candidate.getName()))
+ .findFirst().orElseThrow(() -> new AssertionError("Async cache maintenance toggle is not registered"));
+ }
}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheMaintenanceTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheMaintenanceTest.java
index c5027febb5..ec6a60bb9a 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheMaintenanceTest.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCacheMaintenanceTest.java
@@ -17,11 +17,15 @@
package org.apache.jackrabbit.oak.plugins.document;
import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.jackrabbit.oak.cache.CacheValue;
import org.apache.jackrabbit.oak.cache.api.Cache;
+import org.apache.jackrabbit.oak.cache.api.CacheBuilder;
import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheEntry;
+import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType;
import org.apache.jackrabbit.oak.plugins.document.persistentCache.EvictionListener;
import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache;
import org.apache.jackrabbit.oak.plugins.document.util.StringValue;
@@ -39,10 +43,20 @@ public class DocumentCacheMaintenanceTest {
@Test
public void documentDiffCacheUsesSynchronousMaintenance() throws Exception {
- checkCallbackThread();
+ checkCallbackThread(true, false);
}
- private void checkCallbackThread() throws Exception {
+ @Test
+ public void explicitDiffAsyncRunsItsEvictionCallbackOffTheCaller() throws Exception {
+ checkCallbackThread(true, true);
+ }
+
+ @Test
+ public void disablingFeatureIgnoresAsyncOverridesForAllCaches() throws Exception {
+ checkCallbackThread(false, true);
+ }
+
+ private void checkCallbackThread(boolean featureEnabled, boolean diffAsync) throws Exception {
RecordingCallback callbackThread = new RecordingCallback();
PersistentCache persistence = Mockito.spy(new PersistentCache(folder.newFolder().getAbsolutePath() + ",+asyncDiff"));
Mockito.doAnswer(invocation -> {
@@ -55,19 +69,40 @@ public class DocumentCacheMaintenanceTest {
return AdditionalAnswers.delegatesTo(real).answer(callback);
}));
}).when(persistence).wrap(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
- DocumentNodeStoreBuilder<?> builder = new RecordingBuilder(persistence).memoryCacheSize(1000);
- boolean previous = DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED.getAndSet(true);
+ DocumentNodeStoreBuilder<?> builder = new RecordingBuilder(persistence).memoryCacheSize(1000)
+ .setCacheMaintenanceMode(CacheType.DOCUMENT, CacheBuilder.MaintenanceMode.ASYNC);
+ if (diffAsync) {
+ builder.setCacheMaintenanceMode(CacheType.DIFF, CacheBuilder.MaintenanceMode.ASYNC);
+ }
+ boolean previousCaffeine = DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED.getAndSet(true);
+ boolean previousAsync = DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED
+ .getAndSet(featureEnabled);
try {
Cache<CacheValue, StringValue> cache = builder.buildMemoryDiffCache();
Thread caller = Thread.currentThread();
cache.get(key(), k -> new StringValue("value"));
- Assert.assertSame(caller, callbackThread.thread.get());
+ if (featureEnabled && diffAsync) {
+ Assert.assertTrue("eviction callback did not arrive", callbackThread.called.await(5, TimeUnit.SECONDS));
+ Assert.assertNotSame(caller, callbackThread.thread.get());
+ } else {
+ Assert.assertSame(caller, callbackThread.thread.get());
+ }
} finally {
builder.getPersistentCache().close();
- DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED.set(previous);
+ DocumentNodeStoreBuilder.FT_CAFFEINE_CACHE_ENABLED.set(previousCaffeine);
+ DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.set(previousAsync);
}
}
+ @Test
+ public void configurationRejectsNullCacheTypeAndMode() {
+ DocumentNodeStoreBuilder<?> builder = DocumentNodeStoreBuilder.newDocumentNodeStoreBuilder();
+ Assert.assertThrows(NullPointerException.class,
+ () -> builder.setCacheMaintenanceMode(null, CacheBuilder.MaintenanceMode.SYNC));
+ Assert.assertThrows(NullPointerException.class,
+ () -> builder.setCacheMaintenanceMode(CacheType.DIFF, null));
+ }
+
@Test
public void customWeigherReceivesTheRawValueAndAccountsForEntryOverhead() throws Exception {
DocumentNodeStoreBuilder<?> builder = DocumentNodeStoreBuilder.newDocumentNodeStoreBuilder()
@@ -104,10 +139,12 @@ public class DocumentCacheMaintenanceTest {
}
private static final class RecordingCallback {
+ private final CountDownLatch called = new CountDownLatch(1);
private final AtomicReference<Thread> thread = new AtomicReference<>();
private void record() {
thread.set(Thread.currentThread());
+ called.countDown();
}
}
}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheTest.java
index cb2f1d255d..fa840d61af 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheTest.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheTest.java
@@ -21,6 +21,8 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.jackrabbit.oak.cache.AbstractCacheStats;
+import org.apache.jackrabbit.oak.cache.api.CacheBuilder.MaintenanceMode;
+import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType;
import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache;
import org.junit.Rule;
import org.junit.Test;
@@ -41,15 +43,23 @@ public class MemoryDiffCacheTest {
@Test
public void loadedDiffShouldSurviveAsyncEvictionAndReopen() throws Exception {
- assertLoadedDiffSurvivesEviction();
+ boolean previous = DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.getAndSet(true);
+ try {
+ for (MaintenanceMode mode : MaintenanceMode.values()) {
+ assertLoadedDiffSurvivesEviction(mode);
+ }
+ } finally {
+ DocumentNodeStoreBuilder.FT_DOCUMENT_CACHE_ASYNC_MAINTENANCE_ENABLED.set(previous);
+ }
}
- private void assertLoadedDiffSurvivesEviction() throws Exception {
+ private void assertLoadedDiffSurvivesEviction(MaintenanceMode mode) throws Exception {
String directory = temporaryFolder.newFolder().getAbsolutePath() + ",+asyncDiff";
RevisionVector from = new RevisionVector(new Revision(1, 0, 1));
RevisionVector to = new RevisionVector(new Revision(2, 0, 1));
String changes = "^\"child\":{}";
- DocumentNodeStoreBuilder<?> builder = builderProvider.newBuilder().setPersistentCache(directory);
+ DocumentNodeStoreBuilder<?> builder = builderProvider.newBuilder().setPersistentCache(directory)
+ .setCacheMaintenanceMode(CacheType.DIFF, mode);
PersistentCache persistence = builder.getPersistentCache();
try {
MemoryDiffCache cache = new MemoryDiffCache(builder);
@@ -64,7 +74,8 @@ public class MemoryDiffCacheTest {
} finally {
persistence.close();
}
- DocumentNodeStoreBuilder<?> reopened = builderProvider.newBuilder().setPersistentCache(directory);
+ DocumentNodeStoreBuilder<?> reopened = builderProvider.newBuilder().setPersistentCache(directory)
+ .setCacheMaintenanceMode(CacheType.DIFF, mode);
try {
assertEquals(changes, new MemoryDiffCache(reopened).getChanges(from, to, Path.ROOT, null));
} finally {
diff --git a/specs/oak-12425-cache-maintenance/PLAN.md b/specs/oak-12425-cache-maintenance/PLAN.md
index 86278edba6..6090e8802b 100644
--- a/specs/oak-12425-cache-maintenance/PLAN.md
+++ b/specs/oak-12425-cache-maintenance/PLAN.md
@@ -24,7 +24,7 @@ The shared Caffeine API supports SYNC and ASYNC maintenance and defaults to ASYN
1. **OAK-12436 / PR #3185**: add SYNC/ASYNC selection to CacheBuilder, based on the latest origin/trunk. Preserve the deprecated toggle for callers that have not migrated yet; explicit per-cache selection takes precedence. Refresh remains ASYNC and zero-capacity caches remain inline.
2. **OAK-12438 / PR #3186**: move FT_OAK-12290 into oak-segment-tar. SegmentCache and RecordCache default to ASYNC and can opt into SYNC. The override is local to Segment Tar. Test both production cache constructors, cross-bundle isolation, and feature registration and cleanup.
3. **OAK-12425 / PR #3176**: migrate DocumentNodeStore to Caffeine and store access/persistence-origin metadata in each cache entry. Construct every Document cache with SYNC maintenance. Do not expose or apply a Document-level ASYNC override in this PR. Preserve raw Cache<K,V> values and live map behavior, weighing, statistics, and the persistent file format. Remove the parallel metadata map and its coordination protocol.
-4. **OAK-12437 / follow-up PR**: add explicit per-CacheType ASYNC selection for DocumentNodeStore on top of #3176. Reuse the already implemented builder override and integration tests by moving them out of #3176. Unconfigured Document caches continue to use SYNC; selecting ASYNC for one cache leaves other Document caches synchronous. Preserve entry-owned metadata correctness for both modes.
+4. **OAK-12437 / follow-up PR**: add explicit per-CacheType ASYNC selection for DocumentNodeStore on top of #3176. Reuse the already implemented builder override and integration tests by moving them out of #3176. Guard all Document ASYNC maintenance behind the bundle-local `FT_OAK-12437`, disabled by default. When disabled, even explicitly requested ASYNC modes resolve to SYNC. The mode and toggle are sampled when a cache is constructed; restart/recreate the DocumentNodeStore after chang [...]
5. **OAK-12426 / PR #3174**, also tracking the comparison work: compare LIRS, Caffeine SYNC, and Caffeine ASYNC through the production DocumentNodeStore APIs, based on the OAK-12437 follow-up. Keep weights, workloads, persistent-write behavior and JVM settings identical. Report throughput, latency, cache statistics and backend loads. Repeated results inform the later rollout decision.
6. Publish and maintain the native `gh stack` in this order: **#3185 → #3186 → #3176 → OAK-12437 follow-up → #3174**. Rebase each dependent PR on its direct parent. Review the published changes from the base upward and push verified fixes.
@@ -33,7 +33,7 @@ The shared Caffeine API supports SYNC and ASYNC maintenance and defaults to ASYN
- Core: ASYNC API default, explicit SYNC/ASYNC independence, null validation, legacy migration, refresh and zero-capacity exceptions.
- Segment Tar: both cache constructors in both modes; toggle state restored by tests; no changes to other bundles' caches.
- Document migration (#3176): Caffeine construction uses SYNC for all Document cache types, including persistent wrappers; verify synchronous eviction behavior and metadata correctness, raw map behavior, weighing and persistent compatibility.
-- Document ASYNC follow-up (OAK-12437): configure each CacheType independently; verify an ASYNC cache runs maintenance off the caller while an unconfigured cache remains SYNC. Re-run metadata, loading, invalidation, delayed-callback and persistence-ordering behavior in both modes.
+- Document ASYNC follow-up (OAK-12437): with `FT_OAK-12437` enabled, configure each CacheType independently and verify an ASYNC cache runs maintenance off the caller while an unconfigured cache remains SYNC. With the FT disabled, verify every cache remains SYNC despite configured ASYNC overrides. Re-run metadata, loading, invalidation, delayed-callback and persistence-ordering behavior in both modes. Verify the FT is registered disabled by default and removed on service deactivation.
- Live `asMap` exposes raw values and supports concurrent `putIfAbsent`, replace, conditional remove, compute, iterator removal and clear without bypassing entry metadata.
- Cold node loading inspects only in-memory children hints so it cannot acquire a children-loading monitor while holding a node-loading monitor.
- Cancelled asynchronous persistent writes do not count as successful puts or estimated disk usage.
@@ -46,4 +46,4 @@ OAK-12425 delivers entry-owned metadata and the SYNC-only DocumentNodeStore Caff
## Progress
-The original implementation stack was published and reviewed as #3185 → #3186 → #3176 → #3174. Following the revised scope, #3176 is being narrowed to SYNC maintenance; the Document ASYNC builder override and its production integration coverage move to a new PR for OAK-12437, and #3174 will depend on that PR. The metadata design and correctness fixes remain in #3176. Re-review the affected PRs after the stack is restacked. Use `java17` for validation. Repeated comparative performance mea [...]
+The original implementation stack was published and reviewed as #3185 → #3186 → #3176 → #3174. Following the revised scope, #3176 is narrowed to SYNC maintenance; the Document ASYNC builder override and production integration coverage move to a new OAK-12437 PR, guarded by a disabled-by-default FT that forces all Document caches back to SYNC when off. #3174 will depend on that PR. Entry-owned metadata and its correctness fixes remain in #3176. Re-review the affected PRs after restacking. [...]