Skip to content

Commit 0cc8770

Browse files
authored
Subclass NIOFSDirectory instead of using FileSwitchDirectory (#37140)
We don't want two FSDirectories manage pending deletes separately and optimize file listing. This confuses IndexWriter and causes exceptions when files are deleted twice but are pending for deletion. This change move to using a NIOFS subclass that only delegates to MMAP for opening files all metadata and pending deletes are managed on top. Closes #37111 Relates to #36668
1 parent 0bac64f commit 0cc8770

File tree

2 files changed

+47
-19
lines changed

2 files changed

+47
-19
lines changed

server/src/main/java/org/elasticsearch/index/store/FsDirectoryService.java

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.apache.lucene.store.Directory;
2323
import org.apache.lucene.store.FSDirectory;
2424
import org.apache.lucene.store.FileSwitchDirectory;
25+
import org.apache.lucene.store.IOContext;
26+
import org.apache.lucene.store.IndexInput;
2527
import org.apache.lucene.store.LockFactory;
2628
import org.apache.lucene.store.MMapDirectory;
2729
import org.apache.lucene.store.NIOFSDirectory;
@@ -31,25 +33,18 @@
3133
import org.elasticsearch.common.inject.Inject;
3234
import org.elasticsearch.common.settings.Setting;
3335
import org.elasticsearch.common.settings.Setting.Property;
34-
import org.elasticsearch.common.util.set.Sets;
36+
import org.elasticsearch.core.internal.io.IOUtils;
3537
import org.elasticsearch.index.IndexModule;
3638
import org.elasticsearch.index.IndexSettings;
3739
import org.elasticsearch.index.shard.ShardPath;
3840

3941
import java.io.IOException;
4042
import java.nio.file.Files;
4143
import java.nio.file.Path;
42-
import java.util.Collections;
4344
import java.util.HashSet;
4445
import java.util.Set;
4546

4647
public class FsDirectoryService extends DirectoryService {
47-
/*
48-
* We are mmapping norms, docvalues as well as term dictionaries, all other files are served through NIOFS
49-
* this provides good random access performance and does not lead to page cache thrashing.
50-
*/
51-
private static final Set<String> PRIMARY_EXTENSIONS = Collections.unmodifiableSet(Sets.newHashSet("nvd", "dvd", "tim"));
52-
5348
protected final IndexStore indexStore;
5449
public static final Setting<LockFactory> INDEX_LOCK_FACTOR_SETTING = new Setting<>("index.store.fs.fs_lock", "native", (s) -> {
5550
switch (s) {
@@ -97,13 +92,7 @@ protected Directory newFSDirectory(Path location, LockFactory lockFactory) throw
9792
// Use Lucene defaults
9893
final FSDirectory primaryDirectory = FSDirectory.open(location, lockFactory);
9994
if (primaryDirectory instanceof MMapDirectory) {
100-
return new FileSwitchDirectory(PRIMARY_EXTENSIONS, primaryDirectory, new NIOFSDirectory(location, lockFactory), true) {
101-
@Override
102-
public String[] listAll() throws IOException {
103-
// Avoid doing listAll twice:
104-
return primaryDirectory.listAll();
105-
}
106-
};
95+
return new HybridDirectory(location, lockFactory, primaryDirectory);
10796
} else {
10897
return primaryDirectory;
10998
}
@@ -139,4 +128,44 @@ public String[] listAll() throws IOException {
139128
}
140129
return directory;
141130
}
131+
132+
static final class HybridDirectory extends NIOFSDirectory {
133+
private final FSDirectory randomAccessDirectory;
134+
135+
HybridDirectory(Path location, LockFactory lockFactory, FSDirectory randomAccessDirectory) throws IOException {
136+
super(location, lockFactory);
137+
this.randomAccessDirectory = randomAccessDirectory;
138+
}
139+
140+
@Override
141+
public IndexInput openInput(String name, IOContext context) throws IOException {
142+
String extension = FileSwitchDirectory.getExtension(name);
143+
switch(extension) {
144+
// We are mmapping norms, docvalues as well as term dictionaries, all other files are served through NIOFS
145+
// this provides good random access performance and does not lead to page cache thrashing.
146+
case "nvd":
147+
case "dvd":
148+
case "tim":
149+
// we need to do these checks on the outer directory since the inner doesn't know about pending deletes
150+
ensureOpen();
151+
ensureCanRead(name);
152+
// we only use the mmap to open inputs. Everything else is managed by the NIOFSDirectory otherwise
153+
// we might run into trouble with files that are pendingDelete in one directory but still
154+
// listed in listAll() from the other. We on the other hand don't want to list files from both dirs
155+
// and intersect for perf reasons.
156+
return randomAccessDirectory.openInput(name, context);
157+
default:
158+
return super.openInput(name, context);
159+
}
160+
}
161+
162+
@Override
163+
public void close() throws IOException {
164+
IOUtils.close(super::close, randomAccessDirectory);
165+
}
166+
167+
Directory getRandomAccessDirectory() {
168+
return randomAccessDirectory;
169+
}
170+
}
142171
}

server/src/test/java/org/elasticsearch/index/store/IndexStoreTests.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
package org.elasticsearch.index.store;
2020

2121
import org.apache.lucene.store.Directory;
22-
import org.apache.lucene.store.FileSwitchDirectory;
2322
import org.apache.lucene.store.MMapDirectory;
2423
import org.apache.lucene.store.NIOFSDirectory;
2524
import org.apache.lucene.store.NoLockFactory;
@@ -93,8 +92,8 @@ private void doTestStoreDirectory(Index index, Path tempDir, String typeSettingV
9392
}
9493

9594
private void assertHybridDirectory(Directory directory) {
96-
assertTrue(directory.toString(), directory instanceof FileSwitchDirectory);
97-
Directory primaryDirectory = ((FileSwitchDirectory) directory).getPrimaryDir();
98-
assertTrue("primary directory " + primaryDirectory.toString(), primaryDirectory instanceof MMapDirectory);
95+
assertTrue(directory.toString(), directory instanceof FsDirectoryService.HybridDirectory);
96+
Directory randomAccessDirectory = ((FsDirectoryService.HybridDirectory) directory).getRandomAccessDirectory();
97+
assertTrue("randomAccessDirectory: " + randomAccessDirectory.toString(), randomAccessDirectory instanceof MMapDirectory);
9998
}
10099
}

0 commit comments

Comments
 (0)