Skip to content

Create file open hints on IOContext to replace ReadAdvice #14482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public void close() throws IOException {

@Override
public IndexInput openInput(String name, IOContext context) throws IOException {
validateIOContext(context);
ensureOpen();
final String id = IndexFileNames.stripSegmentName(name);
final FileEntry entry = entries.get(id);
Expand All @@ -170,7 +171,7 @@ public IndexInput openInput(String name, IOContext context) throws IOException {
+ entries.keySet()
+ ")");
}
return handle.slice(name, entry.offset, entry.length, context.readAdvice());
return handle.slice(name, entry.offset, entry.length, toReadAdvice(context));
}

/** Returns an array of strings, one for each file in the directory. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public IndexInput openInput(String name, IOContext context) throws IOException {
name,
startOffsets[index],
endOffsets[index] - startOffsets[index],
context.readAdvice());
toReadAdvice(context));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public IndexInput openInput(String name, IOContext context) throws IOException {
+ entries.keySet()
+ ")");
}
return handle.slice(name, entry.offset, entry.length, context.readAdvice());
return handle.slice(name, entry.offset, entry.length, toReadAdvice(context));
}

/** Returns an array of strings, one for each file in the directory. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ public Directory wrapForMerge(OneMerge merge, Directory in) {
return new FilterDirectory(in) {
@Override
public IndexOutput createOutput(String name, IOContext context) throws IOException {
validateIOContext(context);
ensureOpen();

// This Directory is only supposed to be used during merging,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ FlushedSegment flush(DocumentsWriter.FlushNotifications flushNotifications) thro
segmentInfo,
fieldInfos.finish(),
pendingUpdates,
new IOContext(new FlushInfo(numDocsInRAM, lastCommittedBytesUsed)));
IOContext.flush(new FlushInfo(numDocsInRAM, lastCommittedBytesUsed)));
final double startMBUsed = lastCommittedBytesUsed / 1024. / 1024.;

// Apply delete-by-docID now (delete-byDocID only
Expand Down Expand Up @@ -599,7 +599,7 @@ void sealFlushedSegment(
IndexWriter.setDiagnostics(newSegment.info, IndexWriter.SOURCE_FLUSH);

IOContext context =
new IOContext(new FlushInfo(newSegment.info.maxDoc(), newSegment.sizeInBytes()));
IOContext.flush(new FlushInfo(newSegment.info.maxDoc(), newSegment.sizeInBytes()));

boolean success = false;
try {
Expand Down
8 changes: 4 additions & 4 deletions lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3058,7 +3058,7 @@ public long addIndexes(Directory... dirs) throws IOException {
}

IOContext context =
new IOContext(new FlushInfo(info.info.maxDoc(), info.sizeInBytes()));
IOContext.flush(new FlushInfo(info.info.maxDoc(), info.sizeInBytes()));

FieldInfos fis = readFieldInfos(info);
for (FieldInfo fi : fis) {
Expand Down Expand Up @@ -3400,7 +3400,7 @@ public void addIndexesReaderMerge(MergePolicy.OneMerge merge) throws IOException
testReserveDocs(numDocs);

final IOContext context =
new IOContext(
IOContext.merge(
new MergeInfo(Math.toIntExact(numDocs), -1, false, UNBOUNDED_MAX_MERGE_SEGMENTS));

TrackingDirectoryWrapper trackingDir = new TrackingDirectoryWrapper(mergeDirectory);
Expand Down Expand Up @@ -3950,7 +3950,7 @@ public void setMergeInfo(SegmentCommitInfo info) {
boolean closeReaders = true;
try {
for (MergePolicy.OneMerge merge : pointInTimeMerges.merges) {
IOContext context = new IOContext(merge.getStoreMergeInfo());
IOContext context = IOContext.merge(merge.getStoreMergeInfo());
merge.initMergeReaders(
sci -> {
final ReadersAndUpdates rld = getPooledInstance(sci, true);
Expand Down Expand Up @@ -5139,7 +5139,7 @@ private int mergeMiddle(MergePolicy.OneMerge merge, MergePolicy mergePolicy) thr
merge.checkAborted();

Directory mergeDirectory = mergeScheduler.wrapForMerge(merge, directory);
IOContext context = new IOContext(merge.getStoreMergeInfo());
IOContext context = IOContext.merge(merge.getStoreMergeInfo());

final TrackingDirectoryWrapper dirWrapper = new TrackingDirectoryWrapper(mergeDirectory);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ private synchronized void handleDVUpdates(
}
final long nextDocValuesGen = info.getNextDocValuesGen();
final String segmentSuffix = Long.toString(nextDocValuesGen, Character.MAX_RADIX);
final IOContext updatesContext = new IOContext(new FlushInfo(info.info.maxDoc(), bytes));
final IOContext updatesContext = IOContext.flush(new FlushInfo(info.info.maxDoc(), bytes));
final FieldInfo fieldInfo = infos.fieldInfo(field);
assert fieldInfo != null;
fieldInfo.setDocValuesGen(nextDocValuesGen);
Expand Down Expand Up @@ -536,7 +536,7 @@ private synchronized Set<String> writeFieldInfosGen(
// HEADER + FOOTER: 40
// 90 bytes per-field (over estimating long name and attributes map)
final long estInfosSize = 40 + 90L * fieldInfos.size();
final IOContext infosContext = new IOContext(new FlushInfo(info.info.maxDoc(), estInfosSize));
final IOContext infosContext = IOContext.flush(new FlushInfo(info.info.maxDoc(), estInfosSize));
// separately also track which files were created for this gen
final TrackingDirectoryWrapper trackingDir = new TrackingDirectoryWrapper(dir);
infosFormat.write(trackingDir, info.info, segmentSuffix, fieldInfos, infosContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ void flush(
@Override
void initTermVectorsWriter() throws IOException {
if (writer == null) {
IOContext context = new IOContext(new FlushInfo(lastDocID, bytesUsed.get()));
IOContext context = IOContext.flush(new FlushInfo(lastDocID, bytesUsed.get()));
tmpDirectory = new TrackingTmpOutputDirectoryWrapper(directory);
writer = TEMP_TERM_VECTORS_FORMAT.vectorsWriter(tmpDirectory, info, context);
lastDocID = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ void fill(int docID) throws IOException {

void initTermVectorsWriter() throws IOException {
if (writer == null) {
IOContext context = new IOContext(new FlushInfo(lastDocID, bytesUsed.get()));
IOContext context = IOContext.flush(new FlushInfo(lastDocID, bytesUsed.get()));
writer = codec.termVectorsFormat().vectorsWriter(directory, info, context);
lastDocID = 0;
accountable = writer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public boolean fileExists(String name) {

@Override
public IndexOutput createOutput(String name, IOContext context) throws IOException {
validateIOContext(context);
ensureOpen();
FileEntry e = new FileEntry(name);
if (files.putIfAbsent(name, e) != null) {
Expand All @@ -164,6 +165,7 @@ public IndexOutput createOutput(String name, IOContext context) throws IOExcepti
@Override
public IndexOutput createTempOutput(String prefix, String suffix, IOContext context)
throws IOException {
validateIOContext(context);
ensureOpen();
while (true) {
String name = IndexFileNames.segmentFileName(prefix, tempFileName.apply(suffix), "tmp");
Expand Down Expand Up @@ -203,6 +205,7 @@ public void syncMetaData() throws IOException {

@Override
public IndexInput openInput(String name, IOContext context) throws IOException {
validateIOContext(context);
ensureOpen();
FileEntry e = files.get(name);
if (e == null) {
Expand Down
23 changes: 23 additions & 0 deletions lucene/core/src/java/org/apache/lucene/store/DataAccessHint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.store;

/** Hint on the data access pattern likely to be used */
public enum DataAccessHint implements IOContext.FileOpenHint {
RANDOM,
SEQUENTIAL
}
71 changes: 71 additions & 0 deletions lucene/core/src/java/org/apache/lucene/store/DefaultIOContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.store;

import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

record DefaultIOContext(Optional<ReadAdvice> readAdvice, Set<FileOpenHint> hints)
implements IOContext {

public DefaultIOContext {
Objects.requireNonNull(readAdvice);
Objects.requireNonNull(hints);
if (readAdvice.isPresent() && !hints.isEmpty())
throw new IllegalArgumentException("Either ReadAdvice or hints can be specified, not both");
}

public DefaultIOContext(Optional<ReadAdvice> readAdvice, FileOpenHint... hints) {
this(readAdvice, Set.of(hints));
}

@Override
public Context context() {
return Context.DEFAULT;
}

@Override
public MergeInfo mergeInfo() {
return null;
}

@Override
public FlushInfo flushInfo() {
return null;
}

@Override
public IOContext withHints(FileOpenHint... hints) {
if (readAdvice().isPresent())
throw new IllegalArgumentException("ReadAdvice has been specified directly");
// TODO: see if this is needed or not
if (!hints().isEmpty()) throw new IllegalArgumentException("Hints have already been specified");
return new DefaultIOContext(Optional.empty(), hints);
}

private static final DefaultIOContext[] READADVICE_TO_IOCONTEXT =
Arrays.stream(ReadAdvice.values())
.map(r -> new DefaultIOContext(Optional.of(r)))
.toArray(DefaultIOContext[]::new);

@Override
public DefaultIOContext withReadAdvice(ReadAdvice advice) {
return READADVICE_TO_IOCONTEXT[advice.ordinal()];
}
}
29 changes: 29 additions & 0 deletions lucene/core/src/java/org/apache/lucene/store/Directory.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Collection; // for javadocs
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.lucene.index.IndexFileNames;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.IOUtils;

/**
Expand Down Expand Up @@ -79,6 +83,31 @@ public abstract class Directory implements Closeable {
*/
public abstract long fileLength(String name) throws IOException;

protected void validateIOContext(IOContext context) {
Copy link
Member

Choose a reason for hiding this comment

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

I see this method being used, but I don't understand what it is doing. Why is explicit validation needed, and when should subclasses be invoking this method?

Could the validation be moved to IOContext itself, e.g. into IOContext ctor to prevent that "invalid" IOContexts ever get created in the first place?

Map<Class<? extends IOContext.FileOpenHint>, List<IOContext.FileOpenHint>> hintClasses =
context.hints().stream().collect(Collectors.groupingBy(IOContext.FileOpenHint::getClass));

// there should only be one of FileType, FileData, DataAccess
List<IOContext.FileOpenHint> fileTypes =
hintClasses.getOrDefault(FileTypeHint.class, List.of());
if (fileTypes.size() > 1) {
throw new IllegalArgumentException("Multiple file type hints specified: " + fileTypes);
}
List<IOContext.FileOpenHint> fileData = hintClasses.getOrDefault(FileDataHint.class, List.of());
if (fileData.size() > 1) {
throw new IllegalArgumentException("Multiple file data hints specified: " + fileData);
}
List<IOContext.FileOpenHint> dataAccess =
hintClasses.getOrDefault(DataAccessHint.class, List.of());
if (dataAccess.size() > 1) {
throw new IllegalArgumentException("Multiple data access hints specified: " + dataAccess);
}
}

protected ReadAdvice toReadAdvice(IOContext context) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we add javadocs, I think protected method on a class as central as Directory really needs it?

return context.readAdvice().orElse(Constants.DEFAULT_READADVICE);
}

/**
* Creates a new, empty file in the directory and returns an {@link IndexOutput} instance for
* appending data to this file.
Expand Down
2 changes: 2 additions & 0 deletions lucene/core/src/java/org/apache/lucene/store/FSDirectory.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ public long fileLength(String name) throws IOException {

@Override
public IndexOutput createOutput(String name, IOContext context) throws IOException {
validateIOContext(context);
ensureOpen();
maybeDeletePendingFiles();
// If this file was pending delete, we are now bringing it back to life:
Expand All @@ -218,6 +219,7 @@ public IndexOutput createOutput(String name, IOContext context) throws IOExcepti
@Override
public IndexOutput createTempOutput(String prefix, String suffix, IOContext context)
throws IOException {
validateIOContext(context);
ensureOpen();
maybeDeletePendingFiles();
while (true) {
Expand Down
24 changes: 24 additions & 0 deletions lucene/core/src/java/org/apache/lucene/store/FileDataHint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.store;

/** Hints on the type of data stored in the file */
public enum FileDataHint implements IOContext.FileOpenHint {
POSTINGS,
STORED_FIELDS,
VECTORS
}
24 changes: 24 additions & 0 deletions lucene/core/src/java/org/apache/lucene/store/FileTypeHint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.store;

/** Hints on the type of file being opened */
public enum FileTypeHint implements IOContext.FileOpenHint {
METADATA,
DATA,
INDEX
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ public long fileLength(String name) throws IOException {
return in.fileLength(name);
}

@Override
protected void validateIOContext(IOContext context) {
in.validateIOContext(context);
}

@Override
public IndexOutput createOutput(String name, IOContext context) throws IOException {
return in.createOutput(name, context);
Expand Down
Loading