Skip to content

Commit cefc6c7

Browse files
authored
Change default ReadAdvice from RANDOM to NORMAL (#15040)
This commit changes the default ReadAdvice from RANDOM to NORMAL. After this change MMapDirectory will not set any specific read advice out-of-the-box. Rather there is a straightforward way to enable either previous behaviour ( in 10.2 ), by setting either: 1. dir.setReadAdvice(MMapDirectory.ADVISE_BY_CONTEXT), or 2. setting the system property -Dorg.apache.lucene.store.defaultReadAdvice=RANDOM. closes #14408
1 parent a43c02d commit cefc6c7

File tree

4 files changed

+98
-26
lines changed

4 files changed

+98
-26
lines changed

lucene/CHANGES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ Changes in Runtime Behavior
243243
tier from 10 to 8. This is expected to result in slightly faster search and
244244
slightly slower indexing. (Adrien Grand)
245245

246+
* GITHUB#15040: The default ReadAdvice has been changed from RANDOM to NORMAL. MMapDirectory
247+
will no longer set any specific read advice out-of-the-box. Rather there is a straightforward
248+
way to enable previous behaviour (in 10.2), either by setting:
249+
`dir.setReadAdvice(MMapDirectory.ADVISE_BY_CONTEXT)`, or setting the system property
250+
`-Dorg.apache.lucene.store.defaultReadAdvice=RANDOM`. (Chris Hegarty)
251+
246252
Bug Fixes
247253
---------------------
248254
* GITHUB*15025: flush segments in addIndexes, not in addIndexesReaderMerge;

lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,40 @@ public class MMapDirectory extends FSDirectory {
141141
return Optional.of(groupKey);
142142
};
143143

144-
private BiFunction<String, IOContext, Optional<ReadAdvice>> readAdviceOverride =
144+
/**
145+
* Argument for {@link #setReadAdvice(BiFunction)} that configures the read advice based on the
146+
* context type and hints.
147+
*
148+
* <p>Specifically, the advice is determined by evaluating the following conditions in order:
149+
*
150+
* <ol>
151+
* <li>If the context is either of {@link IOContext.Context#MERGE} or {@link
152+
* IOContext.Context#FLUSH}, then {@link ReadAdvice#SEQUENTIAL},
153+
* <li>If the context {@link IOContext#hints() hints} contains {@link DataAccessHint#RANDOM},
154+
* then {@link ReadAdvice#RANDOM},
155+
* <li>If the context {@link IOContext#hints() hints} contains {@link
156+
* DataAccessHint#SEQUENTIAL}, then {@link ReadAdvice#SEQUENTIAL},
157+
* <li>Otherwise, {@link Constants#DEFAULT_READADVICE} is returned.
158+
* </ol>
159+
*/
160+
public static final BiFunction<String, IOContext, Optional<ReadAdvice>> ADVISE_BY_CONTEXT =
161+
(String _, IOContext context) -> {
162+
if (context.context() == IOContext.Context.MERGE
163+
|| context.context() == IOContext.Context.FLUSH) {
164+
return Optional.of(ReadAdvice.SEQUENTIAL);
165+
}
166+
if (context.hints().contains(DataAccessHint.RANDOM)) {
167+
return Optional.of(ReadAdvice.RANDOM);
168+
}
169+
if (context.hints().contains(DataAccessHint.SEQUENTIAL)) {
170+
return Optional.of(ReadAdvice.SEQUENTIAL);
171+
}
172+
return Optional.of(Constants.DEFAULT_READADVICE);
173+
};
174+
175+
private BiFunction<String, IOContext, Optional<ReadAdvice>> readAdvice =
145176
(_, _) -> Optional.empty();
177+
146178
private BiPredicate<String, IOContext> preload = NO_FILES;
147179

148180
/**
@@ -248,17 +280,16 @@ public void setPreload(BiPredicate<String, IOContext> preload) {
248280
}
249281

250282
/**
251-
* Configure {@link ReadAdvice} overrides for certain files. If the function returns {@code
252-
* Optional.empty()}, a default {@link ReadAdvice} will be used
283+
* Configure {@link ReadAdvice} for certain files. The default implementation uses the {@link
284+
* Constants#DEFAULT_READADVICE}.
253285
*
254286
* @param toReadAdvice a {@link Function} whose first argument is the file name, and second
255287
* argument is the {@link IOContext} used to open the file. Returns {@code
256288
* Optional.of(ReadAdvice)} to use a specific read advice, or {@code Optional.empty()} if a
257289
* default should be used
258290
*/
259-
public void setReadAdviceOverride(
260-
BiFunction<String, IOContext, Optional<ReadAdvice>> toReadAdvice) {
261-
this.readAdviceOverride = toReadAdvice;
291+
public void setReadAdvice(BiFunction<String, IOContext, Optional<ReadAdvice>> toReadAdvice) {
292+
this.readAdvice = toReadAdvice;
262293
}
263294

264295
/**
@@ -285,22 +316,6 @@ public final long getMaxChunkSize() {
285316
return 1L << chunkSizePower;
286317
}
287318

288-
private static ReadAdvice toReadAdvice(IOContext context) {
289-
if (context.context() == IOContext.Context.MERGE
290-
|| context.context() == IOContext.Context.FLUSH) {
291-
return ReadAdvice.SEQUENTIAL;
292-
}
293-
294-
if (context.hints().contains(DataAccessHint.RANDOM)) {
295-
return ReadAdvice.RANDOM;
296-
}
297-
if (context.hints().contains(DataAccessHint.SEQUENTIAL)) {
298-
return ReadAdvice.SEQUENTIAL;
299-
}
300-
301-
return Constants.DEFAULT_READADVICE;
302-
}
303-
304319
/** Creates an IndexInput for the file with the given name. */
305320
@Override
306321
public IndexInput openInput(String name, IOContext context) throws IOException {
@@ -314,7 +329,7 @@ public IndexInput openInput(String name, IOContext context) throws IOException {
314329

315330
final boolean confined = context.hints().contains(ReadOnceHint.INSTANCE);
316331
Function<IOContext, ReadAdvice> toReadAdvice =
317-
c -> readAdviceOverride.apply(name, c).orElseGet(() -> toReadAdvice(c));
332+
c -> readAdvice.apply(name, c).orElse(Constants.DEFAULT_READADVICE);
318333
final Arena arena = confined ? Arena.ofConfined() : getSharedArena(name, arenas);
319334
try (var fc = FileChannel.open(path, StandardOpenOption.READ)) {
320335
final long fileSize = fc.size();

lucene/core/src/java/org/apache/lucene/util/Constants.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,13 @@ private static boolean hasFastCompressMaskCast() {
176176

177177
/**
178178
* The default {@link ReadAdvice} used for opening index files. It will be {@link
179-
* ReadAdvice#RANDOM} by default, unless set by system property {@code
179+
* ReadAdvice#NORMAL} by default, unless set by system property {@code
180180
* org.apache.lucene.store.defaultReadAdvice}.
181181
*/
182182
public static final ReadAdvice DEFAULT_READADVICE =
183183
Optional.ofNullable(getSysProp("org.apache.lucene.store.defaultReadAdvice"))
184184
.map(a -> ReadAdvice.valueOf(a.toUpperCase(Locale.ROOT)))
185-
.orElse(ReadAdvice.RANDOM);
185+
.orElse(ReadAdvice.NORMAL);
186186

187187
private static String getSysProp(String property) {
188188
return System.getProperty(property);

lucene/core/src/test/org/apache/lucene/store/TestMMapDirectory.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.IOException;
2222
import java.nio.file.Path;
2323
import java.util.ArrayList;
24+
import java.util.Arrays;
2425
import java.util.Collections;
2526
import java.util.List;
2627
import java.util.Optional;
@@ -32,6 +33,7 @@
3233
import java.util.concurrent.Future;
3334
import java.util.function.Supplier;
3435
import java.util.stream.IntStream;
36+
import java.util.stream.Stream;
3537
import org.apache.lucene.tests.store.BaseDirectoryTestCase;
3638
import org.apache.lucene.util.Constants;
3739
import org.apache.lucene.util.NamedThreadFactory;
@@ -122,7 +124,7 @@ public void testWithNormal() throws Exception {
122124
out.writeBytes(bytes, 0, bytes.length);
123125
}
124126

125-
dir.setReadAdviceOverride((_, _) -> Optional.of(ReadAdvice.NORMAL));
127+
dir.setReadAdvice((_, _) -> Optional.of(ReadAdvice.NORMAL));
126128
try (final IndexInput in = dir.openInput("test", IOContext.DEFAULT)) {
127129
final byte[] readBytes = new byte[size];
128130
in.readBytes(readBytes, 0, readBytes.length);
@@ -131,6 +133,55 @@ public void testWithNormal() throws Exception {
131133
}
132134
}
133135

136+
public void testAdviseByContextFunc() throws IOException {
137+
var func = MMapDirectory.ADVISE_BY_CONTEXT;
138+
var flush = IOContext.flush(new FlushInfo(1, 2));
139+
var merge = IOContext.merge(new MergeInfo(1, 2, true, 4));
140+
var random = new DefaultIOContext(DataAccessHint.RANDOM);
141+
var sequential = new DefaultIOContext(DataAccessHint.SEQUENTIAL);
142+
assertEquals(ReadAdvice.SEQUENTIAL, func.apply("a", merge).get());
143+
assertEquals(ReadAdvice.SEQUENTIAL, func.apply("b", flush).get());
144+
assertEquals(ReadAdvice.SEQUENTIAL, func.apply("c", sequential).get());
145+
assertEquals(ReadAdvice.RANDOM, func.apply("d", random).get());
146+
147+
// hint types other than DataAccessHint
148+
List<IOContext.FileOpenHint> hints = new ArrayList<>();
149+
hints.addAll(Arrays.asList(FileDataHint.values()));
150+
hints.addAll(Arrays.asList(FileTypeHint.values()));
151+
hints.addAll(Arrays.asList(PreloadHint.values()));
152+
hints.addAll(Arrays.asList(ReadOnceHint.values()));
153+
for (var hint : hints) {
154+
var context = new DefaultIOContext(hint);
155+
assertEquals(Constants.DEFAULT_READADVICE, func.apply("e", context).get());
156+
}
157+
158+
var l1 = List.of(flush, merge, random, sequential, IOContext.DEFAULT, IOContext.READONCE);
159+
var l2 = hints.stream().map(DefaultIOContext::new);
160+
testWithAdviseByContext(Stream.concat(l1.stream(), l2).toList());
161+
}
162+
163+
// trivially exercises MMapDirectory.ADVISE_BY_CONTEXT with different contexts
164+
void testWithAdviseByContext(List<IOContext> contexts) throws IOException {
165+
final int size = 8 * 1024;
166+
byte[] bytes = new byte[size];
167+
random().nextBytes(bytes);
168+
169+
try (MMapDirectory dir = new MMapDirectory(createTempDir("testWithAdviseByContext"))) {
170+
try (IndexOutput out = dir.createOutput("test", IOContext.DEFAULT)) {
171+
out.writeBytes(bytes, 0, bytes.length);
172+
}
173+
dir.setReadAdvice(MMapDirectory.ADVISE_BY_CONTEXT);
174+
175+
for (var context : contexts) {
176+
try (final IndexInput in = dir.openInput("test", context)) {
177+
final byte[] readBytes = new byte[size];
178+
in.readBytes(readBytes, 0, readBytes.length);
179+
assertArrayEquals(bytes, readBytes);
180+
}
181+
}
182+
}
183+
}
184+
134185
public void testPreload() throws Exception {
135186
assumeTrue("madvise for preloading only works on linux/mac", MMapDirectory.supportsMadvise());
136187

0 commit comments

Comments
 (0)