Skip to content

Commit 8e93bc4

Browse files
committed
[RCI] Add NoOpEngine for closed indices (#33903)
This commit adds a new NoOpEngine implementation based on the current ReadOnlyEngine. This new implementation uses an empty DirectoryReader with no segments readers and will always returns 0 docs. The NoOpEngine is the default Engine created for IndexShards of closed indices. It expects an empty translog when it is instantiated. Relates to #33888
1 parent 33345d9 commit 8e93bc4

File tree

7 files changed

+423
-4
lines changed

7 files changed

+423
-4
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.index.engine;
21+
22+
import org.apache.lucene.index.DirectoryReader;
23+
import org.apache.lucene.index.IndexCommit;
24+
import org.apache.lucene.index.IndexWriter;
25+
import org.apache.lucene.index.LeafReader;
26+
import org.apache.lucene.store.Directory;
27+
import org.elasticsearch.common.Nullable;
28+
import org.elasticsearch.core.internal.io.IOUtils;
29+
import org.elasticsearch.index.translog.Translog;
30+
import org.elasticsearch.index.translog.TranslogConfig;
31+
import org.elasticsearch.index.translog.TranslogCorruptedException;
32+
import org.elasticsearch.index.translog.TranslogDeletionPolicy;
33+
34+
import java.io.IOException;
35+
import java.util.List;
36+
import java.util.Map;
37+
import java.util.function.LongSupplier;
38+
import java.util.stream.Stream;
39+
40+
/**
41+
* NoOpEngine is an engine implementation that does nothing but the bare minimum
42+
* required in order to have an engine. All attempts to do something (search,
43+
* index, get), throw {@link UnsupportedOperationException}. This does maintain
44+
* a translog with a deletion policy so that when flushing, no translog is
45+
* retained on disk (setting a retention size and age of 0).
46+
*
47+
* It's also important to notice that this does list the commits of the Store's
48+
* Directory so that the last commit's user data can be read for the historyUUID
49+
* and last committed segment info.
50+
*/
51+
public final class NoOpEngine extends ReadOnlyEngine {
52+
53+
public NoOpEngine(EngineConfig engineConfig) {
54+
super(engineConfig, null, null, true, directoryReader -> directoryReader);
55+
boolean success = false;
56+
try {
57+
// The deletion policy for the translog should not keep any translogs around, so the min age/size is set to -1
58+
final TranslogDeletionPolicy translogDeletionPolicy = new TranslogDeletionPolicy(-1, -1);
59+
60+
// The translog is opened and closed to validate that the translog UUID from lucene is the same as the one in the translog
61+
try (Translog translog = openTranslog(engineConfig, translogDeletionPolicy, engineConfig.getGlobalCheckpointSupplier())) {
62+
final int nbOperations = translog.totalOperations();
63+
if (nbOperations != 0) {
64+
throw new IllegalArgumentException("Expected 0 translog operations but there were " + nbOperations);
65+
}
66+
}
67+
success = true;
68+
} catch (IOException | TranslogCorruptedException e) {
69+
throw new EngineCreationFailureException(shardId, "failed to create engine", e);
70+
} finally {
71+
if (success == false) {
72+
IOUtils.closeWhileHandlingException(this);
73+
}
74+
}
75+
}
76+
77+
@Override
78+
protected DirectoryReader open(final Directory directory) throws IOException {
79+
final List<IndexCommit> indexCommits = DirectoryReader.listCommits(directory);
80+
assert indexCommits.size() == 1 : "expected only one commit point";
81+
IndexCommit indexCommit = indexCommits.get(indexCommits.size() - 1);
82+
return new DirectoryReader(directory, new LeafReader[0]) {
83+
@Override
84+
protected DirectoryReader doOpenIfChanged() throws IOException {
85+
return null;
86+
}
87+
88+
@Override
89+
protected DirectoryReader doOpenIfChanged(IndexCommit commit) throws IOException {
90+
return null;
91+
}
92+
93+
@Override
94+
protected DirectoryReader doOpenIfChanged(IndexWriter writer, boolean applyAllDeletes) throws IOException {
95+
return null;
96+
}
97+
98+
@Override
99+
public long getVersion() {
100+
return 0;
101+
}
102+
103+
@Override
104+
public boolean isCurrent() throws IOException {
105+
return true;
106+
}
107+
108+
@Override
109+
public IndexCommit getIndexCommit() throws IOException {
110+
return indexCommit;
111+
}
112+
113+
@Override
114+
protected void doClose() throws IOException {
115+
}
116+
117+
@Override
118+
public CacheHelper getReaderCacheHelper() {
119+
return null;
120+
}
121+
};
122+
}
123+
124+
private Translog openTranslog(EngineConfig engineConfig, TranslogDeletionPolicy translogDeletionPolicy,
125+
LongSupplier globalCheckpointSupplier) throws IOException {
126+
final TranslogConfig translogConfig = engineConfig.getTranslogConfig();
127+
final String translogUUID = loadTranslogUUIDFromLastCommit();
128+
// We expect that this shard already exists, so it must already have an existing translog else something is badly wrong!
129+
return new Translog(translogConfig, translogUUID, translogDeletionPolicy, globalCheckpointSupplier,
130+
engineConfig.getPrimaryTermSupplier());
131+
}
132+
133+
/**
134+
* Reads the current stored translog ID from the last commit data.
135+
*/
136+
@Nullable
137+
private String loadTranslogUUIDFromLastCommit() {
138+
final Map<String, String> commitUserData = getLastCommittedSegmentInfos().getUserData();
139+
if (commitUserData.containsKey(Translog.TRANSLOG_GENERATION_KEY) == false) {
140+
throw new IllegalStateException("Commit doesn't contain translog generation id");
141+
}
142+
return commitUserData.get(Translog.TRANSLOG_UUID_KEY);
143+
}
144+
145+
@Override
146+
public boolean ensureTranslogSynced(Stream<Translog.Location> locations) {
147+
throw new UnsupportedOperationException("Translog synchronization should never be needed");
148+
}
149+
}

server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
*
5858
* @see #ReadOnlyEngine(EngineConfig, SeqNoStats, TranslogStats, boolean, Function)
5959
*/
60-
public final class ReadOnlyEngine extends Engine {
60+
public class ReadOnlyEngine extends Engine {
6161

6262
private final SegmentInfos lastCommittedSegmentInfos;
6363
private final SeqNoStats seqNoStats;

server/src/main/java/org/elasticsearch/indices/IndicesService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import org.elasticsearch.index.engine.CommitStats;
8585
import org.elasticsearch.index.engine.EngineFactory;
8686
import org.elasticsearch.index.engine.InternalEngineFactory;
87+
import org.elasticsearch.index.engine.NoOpEngine;
8788
import org.elasticsearch.index.fielddata.IndexFieldDataCache;
8889
import org.elasticsearch.index.flush.FlushStats;
8990
import org.elasticsearch.index.get.GetStats;
@@ -517,6 +518,12 @@ private synchronized IndexService createIndexService(final String reason,
517518
}
518519

519520
private EngineFactory getEngineFactory(final IndexSettings idxSettings) {
521+
final IndexMetaData indexMetaData = idxSettings.getIndexMetaData();
522+
if (indexMetaData != null && indexMetaData.getState() == IndexMetaData.State.CLOSE) {
523+
// NoOpEngine takes precedence as long as the index is closed
524+
return NoOpEngine::new;
525+
}
526+
520527
final List<Optional<EngineFactory>> engineFactories =
521528
engineFactoryProviders
522529
.stream()

0 commit comments

Comments
 (0)