Skip to content

Commit 22ff2a6

Browse files
committed
OAK-11934 - segment preloading for PersistentCache
- test coverage, consistent naming
1 parent 92ad096 commit 22ff2a6

File tree

3 files changed

+196
-76
lines changed

3 files changed

+196
-76
lines changed

oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/preloader/SegmentPreloader.java

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@
2121
import org.apache.jackrabbit.oak.commons.Buffer;
2222
import org.apache.jackrabbit.oak.commons.internal.function.Suppliers;
2323
import org.apache.jackrabbit.oak.segment.SegmentId;
24-
import org.apache.jackrabbit.oak.segment.SegmentNotFoundException;
2524
import org.apache.jackrabbit.oak.segment.file.tar.TarFiles;
2625
import org.apache.jackrabbit.oak.segment.spi.persistence.persistentcache.DelegatingPersistentCache;
2726
import org.apache.jackrabbit.oak.segment.spi.persistence.persistentcache.PersistentCache;
2827
import org.apache.jackrabbit.oak.segment.spi.persistence.persistentcache.PersistentCachePreloadingConfiguration;
2928
import org.jetbrains.annotations.NotNull;
3029
import org.jetbrains.annotations.Nullable;
31-
import org.jetbrains.annotations.VisibleForTesting;
3230
import org.slf4j.Logger;
3331
import org.slf4j.LoggerFactory;
3432

@@ -53,7 +51,7 @@
5351

5452
/**
5553
* A {@link PersistentCache} decorator that preloads segments into the cache by
56-
* asynchronously prefetching segments referenced by a segment that is being read
54+
* asynchronously preloading segments referenced by a segment that is being read
5755
* from the cache.
5856
*
5957
* @see PersistentCachePreloadingConfiguration
@@ -70,9 +68,9 @@ public class SegmentPreloader extends DelegatingPersistentCache implements Close
7068

7169
private final ExecutorService dispatchPool;
7270

73-
private final ExecutorService prefetchPool;
71+
private final ExecutorService preloadPool;
7472

75-
private final int prefetchDepth;
73+
private final int preloadDepth;
7674

7775
private final Supplier<TarFiles> tarFiles;
7876

@@ -87,7 +85,7 @@ public class SegmentPreloader extends DelegatingPersistentCache implements Close
8785
* @return the decorated cache or the given {@code delegate} if no preloading is configured
8886
*/
8987
public static @NotNull PersistentCache decorate(@NotNull PersistentCache delegate, @NotNull PersistentCachePreloadingConfiguration config, @NotNull Supplier<TarFiles> tarFiles) {
90-
if (config.getConcurrency() > 0 && config.getPrefetchDepth() > 0) {
88+
if (config.getConcurrency() > 0 && config.getMaxPreloadDepth() > 0) {
9189
return new SegmentPreloader(delegate, config, tarFiles);
9290
}
9391
return delegate;
@@ -98,23 +96,23 @@ private SegmentPreloader(@NotNull PersistentCache delegate, @NotNull PersistentC
9896
this.tarFiles = Suppliers.memoize(tarFiles);
9997
this.inProgressPrefetch = new ConcurrentHashMap<>();
10098
this.graphCache = new ConcurrentHashMap<>();
101-
this.prefetchDepth = config.getPrefetchDepth();
99+
this.preloadDepth = config.getMaxPreloadDepth();
102100
this.dispatchPool = new ThreadPoolExecutor(1,1,
103101
1, TimeUnit.SECONDS,
104102
new PriorityBlockingQueue<>(),
105-
r -> new Thread(r, "segment-prefetch-dispatcher")) {
103+
r -> new Thread(r, "segment-preload-dispatcher")) {
106104
@Override
107105
protected void afterExecute(Runnable r, Throwable t) {
108106
super.afterExecute(r, t);
109107
clearInProgressTask(r);
110108
}
111109
};
112-
int prefetchThreads = config.getConcurrency();
113-
this.prefetchPool = new ThreadPoolExecutor(Math.max(1, prefetchThreads / 4), prefetchThreads,
114-
10, TimeUnit.SECONDS,
115-
new LinkedBlockingQueue<>(prefetchThreads * 4),
110+
int preloadThreads = config.getConcurrency();
111+
ThreadPoolExecutor preloadPool = new ThreadPoolExecutor(Math.max(1, preloadThreads / 4), preloadThreads,
112+
5, TimeUnit.SECONDS,
113+
new LinkedBlockingQueue<>(preloadThreads * 4),
116114
r -> {
117-
String threadName = String.format("segment-prefetch-%s", Long.toHexString(System.nanoTime() & 0xFFFFF));
115+
String threadName = String.format("segment-preload-%s", Long.toHexString(System.nanoTime() & 0xFFFFF));
118116
Thread thread = new Thread(r, threadName);
119117
thread.setUncaughtExceptionHandler((t, e) -> {
120118
if (!(e instanceof InterruptedException)) {
@@ -126,7 +124,7 @@ protected void afterExecute(Runnable r, Throwable t) {
126124
(r, executor) -> {
127125
try {
128126
// force the caller thread to wait for space in the queue (this is always a thread in the dispatchPool)
129-
// this creates back-pressure to the dispatchPool, slowing down the dispatching of new prefetch tasks
127+
// this creates back-pressure to the dispatchPool, slowing down the dispatching of new preload tasks
130128
executor.getQueue().put(r);
131129
} catch (InterruptedException e) {
132130
Thread.currentThread().interrupt();
@@ -139,6 +137,8 @@ protected void afterExecute(Runnable r, Throwable t) {
139137
clearInProgressTask(r);
140138
}
141139
};
140+
preloadPool.allowCoreThreadTimeOut(true);
141+
this.preloadPool = preloadPool;
142142
}
143143

144144
@Override
@@ -148,16 +148,29 @@ protected PersistentCache delegate() {
148148

149149
@Override
150150
public @Nullable Buffer readSegment(long msb, long lsb, @NotNull Callable<Buffer> loader) {
151-
dispatch(tarFiles.get(), msb, lsb);
151+
dispatch(msb, lsb);
152152
return delegate().readSegment(msb, lsb, loader);
153153
}
154154

155-
private void dispatch(@NotNull TarFiles tarFiles, long msb, long lsb) {
156-
dispatch(tarFiles, tarFiles::getIndices, msb, lsb, 0);
155+
private void dispatch(long msb, long lsb) {
156+
dispatch(msb, lsb, 1);
157157
}
158158

159-
private void dispatch(@NotNull TarFiles tarFiles, Supplier<Map<String, Set<UUID>>> indicesSupplier, long msb, long lsb, int depth) {
160-
execute(dispatchPool, new PrefetchDispatchTask(tarFiles, indicesSupplier, msb, lsb, depth));
159+
private void dispatch(long msb, long lsb, int depth) {
160+
execute(dispatchPool, createDispatchTask(msb, lsb, depth));
161+
}
162+
163+
@NotNull SegmentPreloader.DispatchTask createDispatchTask(long msb, long lsb, int depth) {
164+
TarFiles tars = tarFiles.get();
165+
return new DispatchTask(tars, tars::getIndices, msb, lsb, depth);
166+
}
167+
168+
private void preload(long msb, long lsb, int depth) {
169+
execute(preloadPool, createPreloadTask(msb, lsb, depth));
170+
}
171+
172+
@NotNull SegmentPreloader.PreloadTask createPreloadTask(long msb, long lsb, int depth) {
173+
return new PreloadTask(tarFiles.get(), msb, lsb, depth);
161174
}
162175

163176
private void execute(ExecutorService pool, Runnable r) {
@@ -177,22 +190,22 @@ private void clearInProgressTask(Runnable r) {
177190
@Override
178191
public void close() {
179192
try {
180-
prefetchPool.shutdown();
193+
preloadPool.shutdown();
181194
dispatchPool.shutdown();
182-
if (!prefetchPool.awaitTermination(4, TimeUnit.SECONDS)) {
183-
prefetchPool.shutdownNow();
195+
if (!preloadPool.awaitTermination(4, TimeUnit.SECONDS)) {
196+
preloadPool.shutdownNow();
184197
}
185198
if (!dispatchPool.awaitTermination(1, TimeUnit.SECONDS)) {
186199
dispatchPool.shutdownNow();
187200
}
188201
} catch (InterruptedException e) {
189202
Thread.currentThread().interrupt();
190-
prefetchPool.shutdownNow();
203+
preloadPool.shutdownNow();
191204
dispatchPool.shutdownNow();
192205
}
193206
}
194207

195-
private class PrefetchDispatchTask implements Runnable, Comparable<PrefetchDispatchTask> {
208+
class DispatchTask implements Runnable, Comparable<DispatchTask> {
196209

197210
private final TarFiles tarFiles;
198211

@@ -206,13 +219,13 @@ private class PrefetchDispatchTask implements Runnable, Comparable<PrefetchDispa
206219

207220
private final long creationTime = System.nanoTime();
208221

209-
PrefetchDispatchTask(@NotNull TarFiles tarFiles, Supplier<Map<String, Set<UUID>>> indicesSupplier, long msb, long lsb, int depth) {
210-
checkArgument(depth < prefetchDepth, "depth must be < %d, is %d", prefetchDepth, depth);
222+
private DispatchTask(@NotNull TarFiles tarFiles, Supplier<Map<String, Set<UUID>>> indicesSupplier, long msb, long lsb, int depth) {
223+
checkArgument(depth <= preloadDepth, "depth must be <= %d, is %d", preloadDepth, depth);
211224
this.tarFiles = tarFiles;
212225
this.indicesSupplier = indicesSupplier;
213226
this.msb = msb;
214227
this.lsb = lsb;
215-
this.depth = depth + 1;
228+
this.depth = depth;
216229
LOG.debug("Created: {}", this);
217230
}
218231

@@ -239,26 +252,22 @@ public void run() {
239252
long refMsb = reference.getMostSignificantBits();
240253
long refLsb = reference.getLeastSignificantBits();
241254
if (!delegate.containsSegment(refMsb, refLsb)) {
242-
prefetch(tarFiles, () -> indices, refMsb, refLsb, depth);
243-
} else if (depth < prefetchDepth && SegmentId.isDataSegmentId(refLsb)) {
244-
dispatch(tarFiles, () -> indices, refMsb, refLsb, depth);
255+
preload(refMsb, refLsb, depth);
256+
} else if (depth < preloadDepth && SegmentId.isDataSegmentId(refLsb)) {
257+
dispatch(refMsb, refLsb, depth + 1);
245258
}
246259
}
247260
}
248261

249-
private void prefetch(TarFiles tarFiles, Supplier<Map<String, Set<UUID>>> indicesSupplier, long msb, long lsb, int depth) {
250-
execute(prefetchPool, new PrefetchTask(tarFiles, indicesSupplier, msb, lsb, depth));
251-
}
252-
253262
@Override
254263
public boolean equals(Object o) {
255264
if (this == o) {
256265
return true;
257266
}
258-
if (o.getClass() != PrefetchDispatchTask.class) {
267+
if (o.getClass() != DispatchTask.class) {
259268
return false;
260269
}
261-
PrefetchDispatchTask that = (PrefetchDispatchTask) o;
270+
DispatchTask that = (DispatchTask) o;
262271
return msb == that.msb && lsb == that.lsb && depth == that.depth;
263272
}
264273

@@ -269,10 +278,10 @@ public int hashCode() {
269278

270279
@Override
271280
public String toString() {
272-
return "PrefetchDispatchTask{segmentId=" + new UUID(msb, lsb) + ", depth=" + depth + '}';
281+
return "DispatchTask{segmentId=" + new UUID(msb, lsb) + ", depth=" + depth + '}';
273282
}
274283

275-
private int getPrefetchDepth() {
284+
private int getPreloadDepth() {
276285
return depth;
277286
}
278287

@@ -281,30 +290,27 @@ private long getCreationTime() {
281290
}
282291

283292
@Override
284-
public int compareTo(@NotNull SegmentPreloader.PrefetchDispatchTask o) {
293+
public int compareTo(@NotNull SegmentPreloader.DispatchTask o) {
285294
return Comparator
286-
.comparing(PrefetchDispatchTask::getPrefetchDepth)
287-
.thenComparing(PrefetchDispatchTask::getCreationTime)
295+
.comparing(DispatchTask::getPreloadDepth)
296+
.thenComparing(DispatchTask::getCreationTime)
288297
.compare(this, o);
289298
}
290299
}
291300

292-
private class PrefetchTask implements Runnable {
301+
class PreloadTask implements Runnable {
293302

294303
private final TarFiles tarFiles;
295304

296-
private final Supplier<Map<String, Set<UUID>>> indicesSupplier;
297-
298305
private final long msb;
299306

300307
private final long lsb;
301308

302309
private final int depth;
303310

304-
PrefetchTask(TarFiles tarFiles, Supplier<Map<String, Set<UUID>>> indicesSupplier, long msb, long lsb, int depth) {
305-
checkArgument(depth <= prefetchDepth, "depth must be <= %d, is %d", prefetchDepth, depth);
311+
private PreloadTask(TarFiles tarFiles, long msb, long lsb, int depth) {
312+
checkArgument(depth <= preloadDepth, "depth must be <= %d, is %d", preloadDepth, depth);
306313
this.tarFiles = tarFiles;
307-
this.indicesSupplier = indicesSupplier;
308314
this.msb = msb;
309315
this.lsb = lsb;
310316
this.depth = depth;
@@ -314,8 +320,8 @@ private class PrefetchTask implements Runnable {
314320
@Override
315321
public void run() {
316322
LOG.debug("Running: {}", this);
317-
if (depth < prefetchDepth && SegmentId.isDataSegmentId(lsb)) {
318-
dispatch(tarFiles, indicesSupplier, msb, lsb, depth);
323+
if (depth < preloadDepth && SegmentId.isDataSegmentId(lsb)) {
324+
dispatch(msb, lsb, depth + 1);
319325
}
320326
if (!delegate.containsSegment(msb, lsb)) {
321327
Buffer segmentBuffer = tarFiles.readSegment(msb, lsb);
@@ -330,10 +336,10 @@ public boolean equals(Object o) {
330336
if (this == o) {
331337
return true;
332338
}
333-
if (o.getClass() != PrefetchTask.class) {
339+
if (o.getClass() != PreloadTask.class) {
334340
return false;
335341
}
336-
PrefetchTask that = (PrefetchTask) o;
342+
PreloadTask that = (PreloadTask) o;
337343
return msb == that.msb && lsb == that.lsb;
338344
}
339345

@@ -344,7 +350,7 @@ public int hashCode() {
344350

345351
@Override
346352
public String toString() {
347-
return "PrefetchTask{segmentId=" + new UUID(msb, lsb) + ", depth=" + depth + '}';
353+
return "PreloadTask{segmentId=" + new UUID(msb, lsb) + ", depth=" + depth + '}';
348354
}
349355
}
350356
}

oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/PersistentCachePreloadingConfiguration.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,72 +21,72 @@
2121
import java.util.concurrent.Callable;
2222

2323
/**
24-
* Configuration for a segment prefetch mechanism that preloads segments into a
25-
* {@link PersistentCache}. The prefetch mechanism is triggered whenever a segment
24+
* Configuration for a segment preload mechanism that preloads segments into a
25+
* {@link PersistentCache}. The preload mechanism is triggered whenever a segment
2626
* in the cache is {@link PersistentCache#readSegment(long, long, Callable)|accessed}.
2727
* When this happens, all segments referenced by the accessed segment are asynchronously
28-
* prefetched.
28+
* preloaded.
2929
* <p>
30-
* Next to the concurrency level, i.e. how many threads are used for prefetching, the
31-
* {@code prefetchDepth} (default: {@code 1}, which controls how many recursive levels
32-
* of referenced segments are prefetched, can be configured.
30+
* Next to the concurrency level, i.e. how many threads are used for preloading, the
31+
* {@code maxPreloadDepth} (default: {@code 1}, which controls how many recursive levels
32+
* of referenced segments are preloaded, can be configured.
3333
* <p>
3434
* Prefetching is done asynchronously, but it <i>may</i> add some overhead. It is primarily
3535
* recommended to parallelize slow I/O, e.g. when using a remote persistence.
3636
* <p>
37-
* Different scenarios may warrant different prefetching strategies. A short-lived
37+
* Different scenarios may warrant different preloading strategies. A short-lived
3838
* process traversing a repository (e.g. copy, offline-compaction) with an initially
39-
* empty cache may benefit from a more threads and a higher prefetch-depth, while a
39+
* empty cache may benefit from a more threads and a higher preload-depth, while a
4040
* long-running process, e.g. a web application, may perform better with fewer threads
41-
* and a lower prefetch depth.
41+
* and a lower preload depth.
4242
*/
4343
public class PersistentCachePreloadingConfiguration {
4444

4545
private final int concurrency;
4646

47-
private int prefetchDepth;
47+
private int maxPreloadDepth;
4848

49-
private PersistentCachePreloadingConfiguration(int concurrency, int prefetchDepth) {
49+
private PersistentCachePreloadingConfiguration(int concurrency, int preloadDepth) {
5050
this.concurrency = concurrency;
51-
this.prefetchDepth = prefetchDepth;
51+
this.maxPreloadDepth = preloadDepth;
5252
}
5353

5454
/**
5555
* Creates a new {@link PersistentCachePreloadingConfiguration} with the given concurrency
56-
* level and a {@code prefetchDepth} of {@code 1}.
56+
* level and a {@code preloadDepth} of {@code 1}.
5757
*
58-
* @param concurrency number of threads to use for prefetching
58+
* @param concurrency number of threads to use for preloading
5959
* @return a new configuration
6060
*/
6161
public static PersistentCachePreloadingConfiguration withConcurrency(int concurrency) {
6262
return new PersistentCachePreloadingConfiguration(concurrency, 1);
6363
}
6464

6565
/**
66-
* Set how many recursive levels of referenced segments should be prefetched.
66+
* Set how many recursive levels of referenced segments should be preloaded.
6767
*
68-
* @param prefetchDepth depth of the prefetching, i.e. how many levels of referenced
69-
* segments should be prefetched (default: {@code 1})
68+
* @param maxPreloadDepth depth of the preloading, i.e. how many levels of referenced
69+
* segments should be preloaded (default: {@code 1})
7070
* @return this configuration
7171
*/
72-
public PersistentCachePreloadingConfiguration withPrefetchDepth(int prefetchDepth) {
73-
this.prefetchDepth = prefetchDepth;
72+
public PersistentCachePreloadingConfiguration withMaxPreloadDepth(int maxPreloadDepth) {
73+
this.maxPreloadDepth = maxPreloadDepth;
7474
return this;
7575
}
7676

7777
public int getConcurrency() {
7878
return concurrency;
7979
}
8080

81-
public int getPrefetchDepth() {
82-
return prefetchDepth;
81+
public int getMaxPreloadDepth() {
82+
return maxPreloadDepth;
8383
}
8484

8585
@Override
8686
public String toString() {
8787
return "PersistentCachePreloadingConfiguration{" +
8888
"concurrency=" + concurrency +
89-
", prefetchDepth=" + prefetchDepth +
89+
", maxPreloadDepth=" + maxPreloadDepth +
9090
'}';
9191
}
9292
}

0 commit comments

Comments
 (0)