Skip to content

Commit a07d398

Browse files
authored
Add a node setting to disable recovery source (#111824)
This change adds a new setting called indices.recovery_source.enabled (defaults to true) that can be used to disable the recovery source for engines that don't require peer recovery.
1 parent 0fa6a57 commit a07d398

File tree

8 files changed

+252
-17
lines changed

8 files changed

+252
-17
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import org.elasticsearch.index.store.Store;
8282
import org.elasticsearch.index.translog.Translog;
8383
import org.elasticsearch.index.translog.TranslogStats;
84+
import org.elasticsearch.indices.recovery.RecoverySettings;
8485
import org.elasticsearch.search.suggest.completion.CompletionStats;
8586
import org.elasticsearch.threadpool.ThreadPool;
8687
import org.elasticsearch.transport.Transports;
@@ -139,6 +140,7 @@ public abstract class Engine implements Closeable {
139140
protected final EventListener eventListener;
140141
protected final ReentrantLock failEngineLock = new ReentrantLock();
141142
protected final SetOnce<Exception> failedEngine = new SetOnce<>();
143+
protected final boolean enableRecoverySource;
142144

143145
private final AtomicBoolean isClosing = new AtomicBoolean();
144146
private final SubscribableListener<Void> drainOnCloseListener = new SubscribableListener<>();
@@ -167,6 +169,9 @@ protected Engine(EngineConfig engineConfig) {
167169
// we use the engine class directly here to make sure all subclasses have the same logger name
168170
this.logger = Loggers.getLogger(Engine.class, engineConfig.getShardId());
169171
this.eventListener = engineConfig.getEventListener();
172+
this.enableRecoverySource = RecoverySettings.INDICES_RECOVERY_SOURCE_ENABLED_SETTING.get(
173+
engineConfig.getIndexSettings().getSettings()
174+
);
170175
}
171176

172177
/**

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
import org.elasticsearch.index.translog.TranslogCorruptedException;
9999
import org.elasticsearch.index.translog.TranslogDeletionPolicy;
100100
import org.elasticsearch.index.translog.TranslogStats;
101+
import org.elasticsearch.indices.recovery.RecoverySettings;
101102
import org.elasticsearch.search.suggest.completion.CompletionStats;
102103
import org.elasticsearch.threadpool.ThreadPool;
103104

@@ -3130,6 +3131,13 @@ public Translog.Snapshot newChangesSnapshot(
31303131
boolean singleConsumer,
31313132
boolean accessStats
31323133
) throws IOException {
3134+
if (enableRecoverySource == false) {
3135+
throw new IllegalStateException(
3136+
"Changes snapshot are unavailable when the "
3137+
+ RecoverySettings.INDICES_RECOVERY_SOURCE_ENABLED_SETTING.getKey()
3138+
+ " setting is disabled."
3139+
);
3140+
}
31333141
ensureOpen();
31343142
refreshIfNeeded(source, toSeqNo);
31353143
Searcher searcher = acquireSearcher(source, SearcherScope.INTERNAL);

server/src/main/java/org/elasticsearch/index/mapper/SourceFieldMapper.java

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import java.util.List;
3737
import java.util.Locale;
3838

39+
import static org.elasticsearch.indices.recovery.RecoverySettings.INDICES_RECOVERY_SOURCE_ENABLED_SETTING;
40+
3941
public class SourceFieldMapper extends MetadataFieldMapper {
4042
public static final NodeFeature SYNTHETIC_SOURCE_FALLBACK = new NodeFeature("mapper.source.synthetic_source_fallback");
4143
public static final NodeFeature SYNTHETIC_SOURCE_STORED_FIELDS_ADVANCE_FIX = new NodeFeature(
@@ -61,23 +63,53 @@ private enum Mode {
6163
Explicit.IMPLICIT_TRUE,
6264
Strings.EMPTY_ARRAY,
6365
Strings.EMPTY_ARRAY,
64-
null
66+
null,
67+
true
68+
);
69+
70+
private static final SourceFieldMapper DEFAULT_NO_RECOVERY_SOURCE = new SourceFieldMapper(
71+
null,
72+
Explicit.IMPLICIT_TRUE,
73+
Strings.EMPTY_ARRAY,
74+
Strings.EMPTY_ARRAY,
75+
null,
76+
false
6577
);
6678

6779
private static final SourceFieldMapper TSDB_DEFAULT = new SourceFieldMapper(
6880
Mode.SYNTHETIC,
6981
Explicit.IMPLICIT_TRUE,
7082
Strings.EMPTY_ARRAY,
7183
Strings.EMPTY_ARRAY,
72-
IndexMode.TIME_SERIES
84+
IndexMode.TIME_SERIES,
85+
true
86+
);
87+
88+
private static final SourceFieldMapper TSDB_DEFAULT_NO_RECOVERY_SOURCE = new SourceFieldMapper(
89+
Mode.SYNTHETIC,
90+
Explicit.IMPLICIT_TRUE,
91+
Strings.EMPTY_ARRAY,
92+
Strings.EMPTY_ARRAY,
93+
IndexMode.TIME_SERIES,
94+
false
7395
);
7496

7597
private static final SourceFieldMapper LOGSDB_DEFAULT = new SourceFieldMapper(
7698
Mode.SYNTHETIC,
7799
Explicit.IMPLICIT_TRUE,
78100
Strings.EMPTY_ARRAY,
79101
Strings.EMPTY_ARRAY,
80-
IndexMode.LOGSDB
102+
IndexMode.LOGSDB,
103+
true
104+
);
105+
106+
private static final SourceFieldMapper LOGSDB_DEFAULT_NO_RECOVERY_SOURCE = new SourceFieldMapper(
107+
Mode.SYNTHETIC,
108+
Explicit.IMPLICIT_TRUE,
109+
Strings.EMPTY_ARRAY,
110+
Strings.EMPTY_ARRAY,
111+
IndexMode.LOGSDB,
112+
false
81113
);
82114

83115
/*
@@ -89,7 +121,17 @@ private enum Mode {
89121
Explicit.IMPLICIT_TRUE,
90122
Strings.EMPTY_ARRAY,
91123
Strings.EMPTY_ARRAY,
92-
IndexMode.TIME_SERIES
124+
IndexMode.TIME_SERIES,
125+
true
126+
);
127+
128+
private static final SourceFieldMapper TSDB_LEGACY_DEFAULT_NO_RECOVERY_SOURCE = new SourceFieldMapper(
129+
null,
130+
Explicit.IMPLICIT_TRUE,
131+
Strings.EMPTY_ARRAY,
132+
Strings.EMPTY_ARRAY,
133+
IndexMode.TIME_SERIES,
134+
false
93135
);
94136

95137
public static class Defaults {
@@ -148,11 +190,19 @@ public static class Builder extends MetadataFieldMapper.Builder {
148190

149191
private final boolean supportsNonDefaultParameterValues;
150192

151-
public Builder(IndexMode indexMode, final Settings settings, boolean supportsCheckForNonDefaultParams) {
193+
private final boolean enableRecoverySource;
194+
195+
public Builder(
196+
IndexMode indexMode,
197+
final Settings settings,
198+
boolean supportsCheckForNonDefaultParams,
199+
boolean enableRecoverySource
200+
) {
152201
super(Defaults.NAME);
153202
this.indexMode = indexMode;
154203
this.supportsNonDefaultParameterValues = supportsCheckForNonDefaultParams == false
155204
|| settings.getAsBoolean(LOSSY_PARAMETERS_ALLOWED_SETTING_NAME, true);
205+
this.enableRecoverySource = enableRecoverySource;
156206
}
157207

158208
public Builder setSynthetic() {
@@ -218,7 +268,8 @@ public SourceFieldMapper build() {
218268
enabled.get(),
219269
includes.getValue().toArray(Strings.EMPTY_ARRAY),
220270
excludes.getValue().toArray(Strings.EMPTY_ARRAY),
221-
indexMode
271+
indexMode,
272+
enableRecoverySource
222273
);
223274
if (indexMode != null) {
224275
indexMode.validateSourceFieldMapper(sourceFieldMapper);
@@ -230,23 +281,25 @@ public SourceFieldMapper build() {
230281

231282
public static final TypeParser PARSER = new ConfigurableTypeParser(c -> {
232283
var indexMode = c.getIndexSettings().getMode();
284+
boolean enableRecoverySource = INDICES_RECOVERY_SOURCE_ENABLED_SETTING.get(c.getSettings());
233285
if (indexMode.isSyntheticSourceEnabled()) {
234286
if (indexMode == IndexMode.TIME_SERIES) {
235287
if (c.getIndexSettings().getIndexVersionCreated().onOrAfter(IndexVersions.V_8_7_0)) {
236-
return TSDB_DEFAULT;
288+
return enableRecoverySource ? TSDB_DEFAULT : TSDB_DEFAULT_NO_RECOVERY_SOURCE;
237289
} else {
238-
return TSDB_LEGACY_DEFAULT;
290+
return enableRecoverySource ? TSDB_LEGACY_DEFAULT : TSDB_LEGACY_DEFAULT_NO_RECOVERY_SOURCE;
239291
}
240292
} else if (indexMode == IndexMode.LOGSDB) {
241-
return LOGSDB_DEFAULT;
293+
return enableRecoverySource ? LOGSDB_DEFAULT : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE;
242294
}
243295
}
244-
return DEFAULT;
296+
return enableRecoverySource ? DEFAULT : DEFAULT_NO_RECOVERY_SOURCE;
245297
},
246298
c -> new Builder(
247299
c.getIndexSettings().getMode(),
248300
c.getSettings(),
249-
c.indexVersionCreated().onOrAfter(IndexVersions.SOURCE_MAPPER_LOSSY_PARAMS_CHECK)
301+
c.indexVersionCreated().onOrAfter(IndexVersions.SOURCE_MAPPER_LOSSY_PARAMS_CHECK),
302+
INDICES_RECOVERY_SOURCE_ENABLED_SETTING.get(c.getSettings())
250303
)
251304
);
252305

@@ -299,8 +352,16 @@ public BlockLoader blockLoader(BlockLoaderContext blContext) {
299352
private final SourceFilter sourceFilter;
300353

301354
private final IndexMode indexMode;
302-
303-
private SourceFieldMapper(Mode mode, Explicit<Boolean> enabled, String[] includes, String[] excludes, IndexMode indexMode) {
355+
private final boolean enableRecoverySource;
356+
357+
private SourceFieldMapper(
358+
Mode mode,
359+
Explicit<Boolean> enabled,
360+
String[] includes,
361+
String[] excludes,
362+
IndexMode indexMode,
363+
boolean enableRecoverySource
364+
) {
304365
super(new SourceFieldType((enabled.explicit() && enabled.value()) || (enabled.explicit() == false && mode != Mode.DISABLED)));
305366
assert enabled.explicit() == false || mode == null;
306367
this.mode = mode;
@@ -313,6 +374,7 @@ private SourceFieldMapper(Mode mode, Explicit<Boolean> enabled, String[] include
313374
}
314375
this.complete = stored() && sourceFilter == null;
315376
this.indexMode = indexMode;
377+
this.enableRecoverySource = enableRecoverySource;
316378
}
317379

318380
private static SourceFilter buildSourceFilter(String[] includes, String[] excludes) {
@@ -357,7 +419,7 @@ public void preParse(DocumentParserContext context) throws IOException {
357419
context.doc().add(new StoredField(fieldType().name(), ref.bytes, ref.offset, ref.length));
358420
}
359421

360-
if (originalSource != null && adaptedSource != originalSource) {
422+
if (enableRecoverySource && originalSource != null && adaptedSource != originalSource) {
361423
// if we omitted source or modified it we add the _recovery_source to ensure we have it for ops based recovery
362424
BytesRef ref = originalSource.toBytesRef();
363425
context.doc().add(new StoredField(RECOVERY_SOURCE_NAME, ref.bytes, ref.offset, ref.length));
@@ -385,7 +447,7 @@ protected String contentType() {
385447

386448
@Override
387449
public FieldMapper.Builder getMergeBuilder() {
388-
return new Builder(indexMode, Settings.EMPTY, false).init(this);
450+
return new Builder(indexMode, Settings.EMPTY, false, enableRecoverySource).init(this);
389451
}
390452

391453
/**

server/src/main/java/org/elasticsearch/indices/recovery/RecoverySettings.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.elasticsearch.core.TimeValue;
3030
import org.elasticsearch.index.IndexVersion;
3131
import org.elasticsearch.index.IndexVersions;
32+
import org.elasticsearch.index.mapper.SourceFieldMapper;
3233
import org.elasticsearch.monitor.os.OsProbe;
3334
import org.elasticsearch.node.NodeRoleSettings;
3435

@@ -380,6 +381,16 @@ public Iterator<Setting<?>> settings() {
380381
Setting.Property.NodeScope
381382
);
382383

384+
/**
385+
* Indicates whether the `recovery_source` should be enabled (see {@link SourceFieldMapper}).
386+
* This setting is not registered and should be used exclusively in a serverless environment.
387+
*/
388+
public static final Setting<Boolean> INDICES_RECOVERY_SOURCE_ENABLED_SETTING = Setting.boolSetting(
389+
"indices.recovery.recovery_source.enabled",
390+
true,
391+
Property.NodeScope
392+
);
393+
383394
public static final ByteSizeValue DEFAULT_CHUNK_SIZE = new ByteSizeValue(512, ByteSizeUnit.KB);
384395

385396
private volatile ByteSizeValue maxBytesPerSec;

server/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
import org.elasticsearch.index.translog.TranslogDeletionPolicy;
131131
import org.elasticsearch.index.translog.TranslogOperationsUtils;
132132
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
133+
import org.elasticsearch.indices.recovery.RecoverySettings;
133134
import org.elasticsearch.test.IndexSettingsModule;
134135
import org.elasticsearch.test.index.IndexVersionUtils;
135136
import org.elasticsearch.threadpool.ThreadPool;
@@ -7773,6 +7774,24 @@ protected void commitIndexWriter(IndexWriter writer, Translog translog) throws I
77737774
}
77747775
}
77757776

7777+
public void testDisableRecoverySource() throws Exception {
7778+
Settings settings = Settings.builder()
7779+
.put(defaultSettings.getNodeSettings())
7780+
.put(RecoverySettings.INDICES_RECOVERY_SOURCE_ENABLED_SETTING.getKey(), false)
7781+
.build();
7782+
IndexSettings indexSettings = new IndexSettings(defaultSettings.getIndexMetadata(), settings, defaultSettings.getScopedSettings());
7783+
try (
7784+
Store store = createStore();
7785+
InternalEngine engine = createEngine(indexSettings, store, createTempDir(), NoMergePolicy.INSTANCE)
7786+
) {
7787+
IllegalStateException exc = expectThrows(
7788+
IllegalStateException.class,
7789+
() -> engine.newChangesSnapshot("test", 0, 1000, true, true, true)
7790+
);
7791+
assertThat(exc.getMessage(), containsString("unavailable"));
7792+
}
7793+
}
7794+
77767795
private static void assertCommitGenerations(Map<IndexCommit, Engine.IndexCommitRef> commits, List<Long> expectedGenerations) {
77777796
assertCommitGenerations(commits.values().stream().map(Engine.IndexCommitRef::getIndexCommit).toList(), expectedGenerations);
77787797
}

server/src/test/java/org/elasticsearch/index/mapper/DynamicFieldsBuilderTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public void testCreateDynamicStringFieldAsKeywordForDimension() throws IOExcepti
6868
XContentParser parser = createParser(JsonXContent.jsonXContent, source);
6969
SourceToParse sourceToParse = new SourceToParse("test", new BytesArray(source), XContentType.JSON);
7070

71-
SourceFieldMapper sourceMapper = new SourceFieldMapper.Builder(null, Settings.EMPTY, false).setSynthetic().build();
71+
SourceFieldMapper sourceMapper = new SourceFieldMapper.Builder(null, Settings.EMPTY, false, true).setSynthetic().build();
7272
RootObjectMapper root = new RootObjectMapper.Builder("_doc", Optional.empty()).add(
7373
new PassThroughObjectMapper.Builder("labels").setPriority(0).setContainsDimensions().dynamic(ObjectMapper.Dynamic.TRUE)
7474
).build(MapperBuilderContext.root(false, false));

0 commit comments

Comments
 (0)