Skip to content

Commit ceb53f7

Browse files
authored
[8.x] Add a new index setting to skip recovery source when synthetic source is enabled (elastic#118417)
This change adds a new undocumented index settings that allows to use synthetic source for recovery and CCR without storing a recovery source.
1 parent 6946a3d commit ceb53f7

File tree

46 files changed

+1993
-895
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1993
-895
lines changed

docs/changelog/114618.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 114618
2+
summary: Add a new index setting to skip recovery source when synthetic source is enabled
3+
area: Logs
4+
type: enhancement
5+
issues: []

server/src/internalClusterTest/java/org/elasticsearch/action/admin/indices/create/CloneIndexIT.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.common.ValidationException;
1616
import org.elasticsearch.common.settings.Settings;
1717
import org.elasticsearch.index.IndexVersion;
18+
import org.elasticsearch.index.IndexVersions;
1819
import org.elasticsearch.index.query.TermsQueryBuilder;
1920
import org.elasticsearch.index.seqno.SeqNoStats;
2021
import org.elasticsearch.test.ESIntegTestCase;
@@ -26,6 +27,7 @@
2627
import static org.elasticsearch.action.admin.indices.create.ShrinkIndexIT.assertNoResizeSourceIndexSettings;
2728
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
2829
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
30+
import static org.hamcrest.Matchers.anyOf;
2931
import static org.hamcrest.Matchers.containsString;
3032
import static org.hamcrest.Matchers.equalTo;
3133

@@ -143,6 +145,51 @@ public void testResizeChangeSyntheticSource() {
143145
assertThat(error.getMessage(), containsString("can't change setting [index.mapping.source.mode] during resize"));
144146
}
145147

148+
public void testResizeChangeRecoveryUseSyntheticSource() {
149+
prepareCreate("source").setSettings(
150+
indexSettings(between(1, 5), 0).put("index.mode", "logsdb")
151+
.put(
152+
"index.version.created",
153+
IndexVersionUtils.randomVersionBetween(
154+
random(),
155+
IndexVersions.USE_SYNTHETIC_SOURCE_FOR_RECOVERY_BACKPORT,
156+
IndexVersion.current()
157+
)
158+
)
159+
).setMapping("@timestamp", "type=date", "host.name", "type=keyword").get();
160+
updateIndexSettings(Settings.builder().put("index.blocks.write", true), "source");
161+
IllegalArgumentException error = expectThrows(IllegalArgumentException.class, () -> {
162+
indicesAdmin().prepareResizeIndex("source", "target")
163+
.setResizeType(ResizeType.CLONE)
164+
.setSettings(
165+
Settings.builder()
166+
.put(
167+
"index.version.created",
168+
IndexVersionUtils.randomVersionBetween(
169+
random(),
170+
IndexVersions.USE_SYNTHETIC_SOURCE_FOR_RECOVERY_BACKPORT,
171+
IndexVersion.current()
172+
)
173+
)
174+
.put("index.recovery.use_synthetic_source", true)
175+
.put("index.mode", "logsdb")
176+
.putNull("index.blocks.write")
177+
.build()
178+
)
179+
.get();
180+
});
181+
// The index.recovery.use_synthetic_source setting requires either index.mode or index.mapping.source.mode
182+
// to be present in the settings. Since these are all unmodifiable settings with a non-deterministic evaluation
183+
// order, any of them may trigger a failure first.
184+
assertThat(
185+
error.getMessage(),
186+
anyOf(
187+
containsString("can't change setting [index.mode] during resize"),
188+
containsString("can't change setting [index.recovery.use_synthetic_source] during resize")
189+
)
190+
);
191+
}
192+
146193
public void testResizeChangeIndexSorts() {
147194
prepareCreate("source").setSettings(indexSettings(between(1, 5), 0))
148195
.setMapping("@timestamp", "type=date", "host.name", "type=keyword")

server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,15 @@ public void testShardChangesWithDefaultDocType() throws Exception {
715715
}
716716
IndexShard shard = indexService.getShard(0);
717717
try (
718-
Translog.Snapshot luceneSnapshot = shard.newChangesSnapshot("test", 0, numOps - 1, true, randomBoolean(), randomBoolean());
718+
Translog.Snapshot luceneSnapshot = shard.newChangesSnapshot(
719+
"test",
720+
0,
721+
numOps - 1,
722+
true,
723+
randomBoolean(),
724+
randomBoolean(),
725+
randomLongBetween(1, ByteSizeValue.ofMb(32).getBytes())
726+
);
719727
Translog.Snapshot translogSnapshot = getTranslog(shard).newSnapshot()
720728
) {
721729
List<Translog.Operation> opsFromLucene = TestTranslog.drainSnapshot(luceneSnapshot, true);

server/src/internalClusterTest/java/org/elasticsearch/indices/recovery/IndexRecoveryIT.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@
147147
import static org.elasticsearch.action.support.ActionTestUtils.assertNoFailureListener;
148148
import static org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING;
149149
import static org.elasticsearch.index.seqno.SequenceNumbers.NO_OPS_PERFORMED;
150-
import static org.elasticsearch.node.RecoverySettingsChunkSizePlugin.CHUNK_SIZE_SETTING;
151150
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
152151
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
153152
import static org.hamcrest.Matchers.empty;
@@ -247,7 +246,7 @@ private void assertOnGoingRecoveryState(
247246
public Settings.Builder createRecoverySettingsChunkPerSecond(long chunkSizeBytes) {
248247
return Settings.builder()
249248
// Set the chunk size in bytes
250-
.put(CHUNK_SIZE_SETTING.getKey(), new ByteSizeValue(chunkSizeBytes, ByteSizeUnit.BYTES))
249+
.put(RecoverySettings.INDICES_RECOVERY_CHUNK_SIZE.getKey(), new ByteSizeValue(chunkSizeBytes, ByteSizeUnit.BYTES))
251250
// Set one chunk of bytes per second.
252251
.put(RecoverySettings.INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING.getKey(), chunkSizeBytes, ByteSizeUnit.BYTES);
253252
}
@@ -270,7 +269,7 @@ private void unthrottleRecovery() {
270269
Settings.builder()
271270
// 200mb is an arbitrary number intended to be large enough to avoid more throttling.
272271
.put(RecoverySettings.INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING.getKey(), "200mb")
273-
.put(CHUNK_SIZE_SETTING.getKey(), RecoverySettings.DEFAULT_CHUNK_SIZE)
272+
.put(RecoverySettings.INDICES_RECOVERY_CHUNK_SIZE.getKey(), RecoverySettings.DEFAULT_CHUNK_SIZE)
274273
);
275274
}
276275

server/src/internalClusterTest/java/org/elasticsearch/recovery/TruncatedRecoveryIT.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.elasticsearch.indices.recovery.PeerRecoveryTargetService;
2525
import org.elasticsearch.indices.recovery.RecoveryFileChunkRequest;
2626
import org.elasticsearch.indices.recovery.RecoveryFilesInfoRequest;
27-
import org.elasticsearch.node.RecoverySettingsChunkSizePlugin;
27+
import org.elasticsearch.indices.recovery.RecoverySettings;
2828
import org.elasticsearch.plugins.Plugin;
2929
import org.elasticsearch.test.ESIntegTestCase;
3030
import org.elasticsearch.test.transport.MockTransportService;
@@ -41,7 +41,6 @@
4141
import java.util.concurrent.atomic.AtomicBoolean;
4242
import java.util.function.Function;
4343

44-
import static org.elasticsearch.node.RecoverySettingsChunkSizePlugin.CHUNK_SIZE_SETTING;
4544
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
4645
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
4746
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -52,7 +51,7 @@ public class TruncatedRecoveryIT extends ESIntegTestCase {
5251

5352
@Override
5453
protected Collection<Class<? extends Plugin>> nodePlugins() {
55-
return Arrays.asList(MockTransportService.TestPlugin.class, RecoverySettingsChunkSizePlugin.class);
54+
return Arrays.asList(MockTransportService.TestPlugin.class);
5655
}
5756

5857
/**
@@ -63,7 +62,11 @@ protected Collection<Class<? extends Plugin>> nodePlugins() {
6362
*/
6463
public void testCancelRecoveryAndResume() throws Exception {
6564
updateClusterSettings(
66-
Settings.builder().put(CHUNK_SIZE_SETTING.getKey(), new ByteSizeValue(randomIntBetween(50, 300), ByteSizeUnit.BYTES))
65+
Settings.builder()
66+
.put(
67+
RecoverySettings.INDICES_RECOVERY_CHUNK_SIZE.getKey(),
68+
new ByteSizeValue(randomIntBetween(50, 300), ByteSizeUnit.BYTES)
69+
)
6770
);
6871

6972
NodesStatsResponse nodeStats = clusterAdmin().prepareNodesStats().get();

server/src/internalClusterTest/java/org/elasticsearch/snapshots/RestoreSnapshotIT.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,24 @@ public void testRestoreChangeSyntheticSource() {
800800
assertThat(error.getMessage(), containsString("cannot modify setting [index.mapping.source.mode] on restore"));
801801
}
802802

803+
public void testRestoreChangeRecoveryUseSyntheticSource() {
804+
Client client = client();
805+
createRepository("test-repo", "fs");
806+
String indexName = "test-idx";
807+
assertAcked(client.admin().indices().prepareCreate(indexName).setSettings(Settings.builder().put(indexSettings())));
808+
createSnapshot("test-repo", "test-snap", Collections.singletonList(indexName));
809+
cluster().wipeIndices(indexName);
810+
var error = expectThrows(SnapshotRestoreException.class, () -> {
811+
client.admin()
812+
.cluster()
813+
.prepareRestoreSnapshot(TEST_REQUEST_TIMEOUT, "test-repo", "test-snap")
814+
.setIndexSettings(Settings.builder().put("index.recovery.use_synthetic_source", true))
815+
.setWaitForCompletion(true)
816+
.get();
817+
});
818+
assertThat(error.getMessage(), containsString("cannot modify setting [index.recovery.use_synthetic_source] on restore"));
819+
}
820+
803821
public void testRestoreChangeIndexSorts() {
804822
Client client = client();
805823
createRepository("test-repo", "fs");

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,6 +1572,7 @@ static void validateCloneIndex(
15721572
private static final Set<String> UNMODIFIABLE_SETTINGS_DURING_RESIZE = Set.of(
15731573
IndexSettings.MODE.getKey(),
15741574
SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(),
1575+
IndexSettings.RECOVERY_USE_SYNTHETIC_SOURCE_SETTING.getKey(),
15751576
IndexSortConfig.INDEX_SORT_FIELD_SETTING.getKey(),
15761577
IndexSortConfig.INDEX_SORT_ORDER_SETTING.getKey(),
15771578
IndexSortConfig.INDEX_SORT_MODE_SETTING.getKey(),

server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ public void apply(Settings value, Settings current, Settings previous) {
257257
RecoverySettings.INDICES_RECOVERY_USE_SNAPSHOTS_SETTING,
258258
RecoverySettings.INDICES_RECOVERY_MAX_CONCURRENT_SNAPSHOT_FILE_DOWNLOADS,
259259
RecoverySettings.INDICES_RECOVERY_MAX_CONCURRENT_SNAPSHOT_FILE_DOWNLOADS_PER_NODE,
260+
RecoverySettings.INDICES_RECOVERY_CHUNK_SIZE,
260261
RecoverySettings.NODE_BANDWIDTH_RECOVERY_FACTOR_READ_SETTING,
261262
RecoverySettings.NODE_BANDWIDTH_RECOVERY_FACTOR_WRITE_SETTING,
262263
RecoverySettings.NODE_BANDWIDTH_RECOVERY_OPERATOR_FACTOR_SETTING,

server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
189189
IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_WRITE_SETTING,
190190
IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_READ_SETTING,
191191
SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING,
192+
IndexSettings.RECOVERY_USE_SYNTHETIC_SOURCE_SETTING,
192193

193194
// validate that built-in similarities don't get redefined
194195
Setting.groupSetting("index.similarity.", (s) -> {

server/src/main/java/org/elasticsearch/index/IndexSettings.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.Collections;
3939
import java.util.Iterator;
4040
import java.util.List;
41+
import java.util.Locale;
4142
import java.util.Map;
4243
import java.util.concurrent.TimeUnit;
4344
import java.util.function.Consumer;
@@ -52,6 +53,7 @@
5253
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING;
5354
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING;
5455
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING;
56+
import static org.elasticsearch.index.mapper.SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING;
5557

5658
/**
5759
* This class encapsulates all index level settings and handles settings updates.
@@ -656,6 +658,62 @@ public Iterator<Setting<?>> settings() {
656658
Property.Final
657659
);
658660

661+
public static final Setting<Boolean> RECOVERY_USE_SYNTHETIC_SOURCE_SETTING = Setting.boolSetting(
662+
"index.recovery.use_synthetic_source",
663+
false,
664+
new Setting.Validator<>() {
665+
@Override
666+
public void validate(Boolean value) {}
667+
668+
@Override
669+
public void validate(Boolean enabled, Map<Setting<?>, Object> settings) {
670+
if (enabled == false) {
671+
return;
672+
}
673+
674+
// Verify if synthetic source is enabled on the index; fail if it is not
675+
var indexMode = (IndexMode) settings.get(MODE);
676+
if (indexMode.defaultSourceMode() != SourceFieldMapper.Mode.SYNTHETIC) {
677+
var sourceMode = (SourceFieldMapper.Mode) settings.get(INDEX_MAPPER_SOURCE_MODE_SETTING);
678+
if (sourceMode != SourceFieldMapper.Mode.SYNTHETIC) {
679+
throw new IllegalArgumentException(
680+
String.format(
681+
Locale.ROOT,
682+
"The setting [%s] is only permitted when [%s] is set to [%s]. Current mode: [%s].",
683+
RECOVERY_USE_SYNTHETIC_SOURCE_SETTING.getKey(),
684+
INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(),
685+
SourceFieldMapper.Mode.SYNTHETIC.name(),
686+
sourceMode.name()
687+
)
688+
);
689+
}
690+
}
691+
692+
// Verify that all nodes can handle this setting
693+
var version = (IndexVersion) settings.get(SETTING_INDEX_VERSION_CREATED);
694+
if (version.before(IndexVersions.USE_SYNTHETIC_SOURCE_FOR_RECOVERY_BACKPORT)) {
695+
throw new IllegalArgumentException(
696+
String.format(
697+
Locale.ROOT,
698+
"The setting [%s] is unavailable on this cluster because some nodes are running older "
699+
+ "versions that do not support it. Please upgrade all nodes to the latest version "
700+
+ "and try again.",
701+
RECOVERY_USE_SYNTHETIC_SOURCE_SETTING.getKey()
702+
)
703+
);
704+
}
705+
}
706+
707+
@Override
708+
public Iterator<Setting<?>> settings() {
709+
List<Setting<?>> res = List.of(INDEX_MAPPER_SOURCE_MODE_SETTING, SETTING_INDEX_VERSION_CREATED, MODE);
710+
return res.iterator();
711+
}
712+
},
713+
Property.IndexScope,
714+
Property.Final
715+
);
716+
659717
/**
660718
* Returns <code>true</code> if TSDB encoding is enabled. The default is <code>true</code>
661719
*/
@@ -827,6 +885,7 @@ private void setRetentionLeaseMillis(final TimeValue retentionLease) {
827885
private volatile boolean skipIgnoredSourceRead;
828886
private final SourceFieldMapper.Mode indexMappingSourceMode;
829887
private final boolean recoverySourceEnabled;
888+
private final boolean recoverySourceSyntheticEnabled;
830889

831890
/**
832891
* The maximum number of refresh listeners allows on this shard.
@@ -987,8 +1046,9 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti
9871046
es87TSDBCodecEnabled = scopedSettings.get(TIME_SERIES_ES87TSDB_CODEC_ENABLED_SETTING);
9881047
skipIgnoredSourceWrite = scopedSettings.get(IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_WRITE_SETTING);
9891048
skipIgnoredSourceRead = scopedSettings.get(IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_READ_SETTING);
990-
indexMappingSourceMode = scopedSettings.get(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING);
1049+
indexMappingSourceMode = scopedSettings.get(INDEX_MAPPER_SOURCE_MODE_SETTING);
9911050
recoverySourceEnabled = RecoverySettings.INDICES_RECOVERY_SOURCE_ENABLED_SETTING.get(nodeSettings);
1051+
recoverySourceSyntheticEnabled = scopedSettings.get(RECOVERY_USE_SYNTHETIC_SOURCE_SETTING);
9921052

9931053
scopedSettings.addSettingsUpdateConsumer(
9941054
MergePolicyConfig.INDEX_COMPOUND_FORMAT_SETTING,
@@ -1680,6 +1740,13 @@ public boolean isRecoverySourceEnabled() {
16801740
return recoverySourceEnabled;
16811741
}
16821742

1743+
/**
1744+
* @return Whether recovery source should always be bypassed in favor of using synthetic source.
1745+
*/
1746+
public boolean isRecoverySourceSyntheticEnabled() {
1747+
return recoverySourceSyntheticEnabled;
1748+
}
1749+
16831750
/**
16841751
* The bounds for {@code @timestamp} on this index or
16851752
* {@code null} if there are no bounds.

0 commit comments

Comments
 (0)