Skip to content

Commit b8958b7

Browse files
authored
Reduce number of ProjectMetadatas built during template validation (#135214)
We're currently building `ProjectMetadata` instances with minor changes a few times during the index/component template validation. On large clusters with a large cluster state/project metadata, building a `ProjectMetadata` becomes quite expensive. It turns out that we can do the exact same validation without building all these intermediate projects. A follow-up PR will move some of the validation logic to the transport action to reduce processing on the cluster state update thread.
1 parent 7f1d2dc commit b8958b7

File tree

2 files changed

+51
-66
lines changed

2 files changed

+51
-66
lines changed

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

Lines changed: 40 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import org.elasticsearch.index.mapper.MapperService;
4848
import org.elasticsearch.index.mapper.MapperService.MergeReason;
4949
import org.elasticsearch.index.mapper.RoutingFieldMapper;
50-
import org.elasticsearch.index.shard.IndexLongFieldRange;
5150
import org.elasticsearch.indices.IndexTemplateMissingException;
5251
import org.elasticsearch.indices.IndicesService;
5352
import org.elasticsearch.indices.InvalidIndexTemplateException;
@@ -173,9 +172,12 @@ private abstract static class TemplateClusterStateUpdateTask implements ClusterS
173172
}
174173

175174
public final ClusterState execute(ClusterState currentState) throws Exception {
176-
ProjectMetadata metadata = currentState.metadata().getProject(projectId);
177-
ProjectMetadata newMetadata = execute(metadata);
178-
return metadata == newMetadata ? currentState : ClusterState.builder(currentState).putProjectMetadata(newMetadata).build();
175+
ProjectMetadata currentProject = currentState.metadata().getProject(projectId);
176+
ProjectMetadata newProject = execute(currentProject);
177+
if (currentProject == newProject) {
178+
return currentState;
179+
}
180+
return ClusterState.builder(currentState).metadata(currentState.metadata().withUpdatedProject(newProject)).build();
179181
}
180182

181183
public abstract ProjectMetadata execute(ProjectMetadata currentProject) throws Exception;
@@ -384,19 +386,17 @@ public ProjectMetadata addComponentTemplate(
384386
}
385387

386388
validateTemplate(finalSettings, wrappedMappings, indicesService);
387-
validate(name, finalComponentTemplate);
389+
validate(name, finalComponentTemplate.template(), List.of(), null);
388390

391+
ProjectMetadata projectWithComponentTemplateAdded = ProjectMetadata.builder(project).put(name, finalComponentTemplate).build();
389392
// Validate all composable index templates that use this component template
390-
if (templatesUsingComponent.size() > 0) {
391-
ProjectMetadata tempProjectWithComponentTemplateAdded = ProjectMetadata.builder(project)
392-
.put(name, finalComponentTemplate)
393-
.build();
393+
if (templatesUsingComponent.isEmpty() == false) {
394394
Exception validationFailure = null;
395395
for (Map.Entry<String, ComposableIndexTemplate> entry : templatesUsingComponent.entrySet()) {
396396
final String composableTemplateName = entry.getKey();
397397
final ComposableIndexTemplate composableTemplate = entry.getValue();
398398
try {
399-
validateIndexTemplateV2(tempProjectWithComponentTemplateAdded, composableTemplateName, composableTemplate);
399+
validateIndexTemplateV2(projectWithComponentTemplateAdded, composableTemplateName, composableTemplate);
400400
} catch (Exception e) {
401401
if (validationFailure == null) {
402402
validationFailure = new IllegalArgumentException(
@@ -426,7 +426,7 @@ public ProjectMetadata addComponentTemplate(
426426
}
427427

428428
logger.info("{} component template [{}]", existing == null ? "adding" : "updating", name);
429-
return ProjectMetadata.builder(project).put(name, finalComponentTemplate).build();
429+
return projectWithComponentTemplateAdded;
430430
}
431431

432432
/**
@@ -784,8 +784,9 @@ void validateIndexTemplateV2(ProjectMetadata projectMetadata, String name, Compo
784784
var finalTemplate = indexTemplate.template();
785785
final var now = instantSource.instant();
786786

787-
final var combinedMappings = collectMappings(indexTemplate, projectMetadata.componentTemplates(), "tmp_idx");
788-
final var combinedSettings = resolveSettings(indexTemplate, projectMetadata.componentTemplates());
787+
final var componentTemplates = projectMetadata.componentTemplates();
788+
final var combinedMappings = collectMappings(indexTemplate, componentTemplates, "tmp_idx");
789+
final var combinedSettings = resolveSettings(indexTemplate, componentTemplates);
789790
var additionalSettingsBuilder = Settings.builder();
790791
ImmutableOpenMap.Builder<String, Map<String, String>> customMetadataBuilder = ImmutableOpenMap.builder();
791792
for (var provider : indexSettingProviders) {
@@ -820,11 +821,11 @@ void validateIndexTemplateV2(ProjectMetadata projectMetadata, String name, Compo
820821

821822
validate(name, templateToValidate, additionalSettings);
822823
validateDataStreamsStillReferenced(projectMetadata, name, templateToValidate);
823-
validateLifecycle(projectMetadata, name, templateToValidate, globalRetentionSettings.get(false));
824-
validateDataStreamOptions(projectMetadata, name, templateToValidate, globalRetentionSettings.get(true));
824+
validateLifecycle(componentTemplates, name, templateToValidate, globalRetentionSettings.get(false));
825+
validateDataStreamOptions(componentTemplates, name, templateToValidate, globalRetentionSettings.get(true));
825826

826827
if (templateToValidate.isDeprecated() == false) {
827-
validateUseOfDeprecatedComponentTemplates(name, templateToValidate, projectMetadata.componentTemplates());
828+
validateUseOfDeprecatedComponentTemplates(name, templateToValidate, componentTemplates);
828829
validateUseOfDeprecatedIngestPipelines(name, projectMetadata.custom(IngestMetadata.TYPE), combinedSettings);
829830
// TODO come up with a plan how to validate usage of deprecated ILM policies
830831
// we don't have access to the core/main plugin here so we can't use the IndexLifecycleMetadata type
@@ -900,12 +901,12 @@ private void emitWarningIfPipelineIsDeprecated(String name, Map<String, Pipeline
900901

901902
// Visible for testing
902903
static void validateLifecycle(
903-
ProjectMetadata project,
904+
Map<String, ComponentTemplate> componentTemplates,
904905
String indexTemplateName,
905906
ComposableIndexTemplate template,
906907
@Nullable DataStreamGlobalRetention globalRetention
907908
) {
908-
DataStreamLifecycle.Builder builder = resolveLifecycle(template, project.componentTemplates());
909+
DataStreamLifecycle.Builder builder = resolveLifecycle(template, componentTemplates);
909910
if (builder != null) {
910911
if (template.getDataStreamTemplate() == null) {
911912
throw new IllegalArgumentException(
@@ -925,12 +926,12 @@ static void validateLifecycle(
925926

926927
// Visible for testing
927928
static void validateDataStreamOptions(
928-
ProjectMetadata projectMetadata,
929+
Map<String, ComponentTemplate> componentTemplates,
929930
String indexTemplateName,
930931
ComposableIndexTemplate template,
931932
DataStreamGlobalRetention globalRetention
932933
) {
933-
DataStreamOptions.Builder dataStreamOptionsBuilder = resolveDataStreamOptions(template, projectMetadata.componentTemplates());
934+
DataStreamOptions.Builder dataStreamOptionsBuilder = resolveDataStreamOptions(template, componentTemplates);
934935
if (dataStreamOptionsBuilder != null) {
935936
if (template.getDataStreamTemplate() == null) {
936937
throw new IllegalArgumentException(
@@ -971,18 +972,18 @@ private static void validateDataStreamsStillReferenced(
971972
.map(Map.Entry::getKey)
972973
.collect(Collectors.toSet());
973974

974-
Function<ProjectMetadata, Set<String>> findUnreferencedDataStreams = meta -> {
975+
Function<Map<String, ComposableIndexTemplate>, Set<String>> findUnreferencedDataStreams = composableTemplates -> {
975976
final Set<String> unreferenced = new HashSet<>();
976977
// For each data stream that we have, see whether it's covered by a different
977978
// template (which is great), or whether it's now uncovered by any template
978979
for (String dataStream : dataStreams) {
979-
final String matchingTemplate = findV2Template(meta, dataStream, false);
980+
final String matchingTemplate = findV2Template(project, composableTemplates.entrySet(), dataStream, false, false);
980981
if (matchingTemplate == null) {
981982
unreferenced.add(dataStream);
982983
} else {
983984
// We found a template that still matches, great! Buuuuttt... check whether it
984985
// is a data stream template, as it's only useful if it has a data stream definition
985-
if (meta.templatesV2().get(matchingTemplate).getDataStreamTemplate() == null) {
986+
if (composableTemplates.get(matchingTemplate).getDataStreamTemplate() == null) {
986987
unreferenced.add(dataStream);
987988
}
988989
}
@@ -991,12 +992,13 @@ private static void validateDataStreamsStillReferenced(
991992
};
992993

993994
// Find data streams that are currently unreferenced
994-
final Set<String> currentlyUnreferenced = findUnreferencedDataStreams.apply(project);
995+
final Set<String> currentlyUnreferenced = findUnreferencedDataStreams.apply(project.templatesV2());
995996

996-
// Generate a metadata as if the new template were actually in the cluster state
997-
final ProjectMetadata updatedMetadata = ProjectMetadata.builder(project).put(templateName, newTemplate).build();
997+
// Generate a map as if the new template were actually in the cluster state
998+
final var updatedTemplatesMap = new HashMap<>(project.templatesV2());
999+
updatedTemplatesMap.put(templateName, newTemplate);
9981000
// Find the data streams that would be unreferenced now that the template is updated/added
999-
final Set<String> newlyUnreferenced = findUnreferencedDataStreams.apply(updatedMetadata);
1001+
final Set<String> newlyUnreferenced = findUnreferencedDataStreams.apply(updatedTemplatesMap);
10001002

10011003
// If we found any data streams that used to be covered, but will no longer be covered by
10021004
// changing this template, then blow up with as much helpful information as we can muster
@@ -1226,7 +1228,7 @@ static Set<String> dataStreamsExclusivelyUsingTemplates(final ProjectMetadata pr
12261228
return candidates.stream()
12271229
.noneMatch(
12281230
template -> templateNames.contains(template.v1()) == false
1229-
&& isGlobalAndHasIndexHiddenSetting(projectMetadata, template.v2(), template.v1()) == false
1231+
&& isGlobalAndHasIndexHiddenSetting(template.v2(), projectMetadata.componentTemplates()) == false
12301232
);
12311233
})
12321234
.map(DataStream::getName)
@@ -1514,7 +1516,7 @@ private static String findV2Template(
15141516
// a restored index cluster state that modified a component template used by this global template such that it has this setting)
15151517
// we will fail and the user will have to update the index template and remove this setting or update the corresponding component
15161518
// template that contributes to the index template resolved settings
1517-
if (isGlobalAndHasIndexHiddenSetting(projectMetadata, winner, winnerName)) {
1519+
if (isGlobalAndHasIndexHiddenSetting(winner, projectMetadata.componentTemplates())) {
15181520
throw new IllegalStateException(
15191521
"global index template ["
15201522
+ winnerName
@@ -1586,12 +1588,11 @@ private static boolean areTemplatesSorted(Collection<Map.Entry<String, Composabl
15861588
// Checks if a global template specifies the `index.hidden` setting. This check is important because a global
15871589
// template shouldn't specify the `index.hidden` setting, we leave it up to the caller to handle this situation.
15881590
private static boolean isGlobalAndHasIndexHiddenSetting(
1589-
ProjectMetadata projectMetadata,
15901591
ComposableIndexTemplate template,
1591-
String templateName
1592+
Map<String, ComponentTemplate> componentTemplates
15921593
) {
15931594
return anyMatch(template.indexPatterns(), Regex::isMatchAllPattern)
1594-
&& IndexMetadata.INDEX_HIDDEN_SETTING.exists(resolveSettings(projectMetadata, templateName));
1595+
&& IndexMetadata.INDEX_HIDDEN_SETTING.exists(resolveSettings(template, componentTemplates));
15951596
}
15961597

15971598
/**
@@ -1962,10 +1963,8 @@ private static void validateCompositeTemplate(
19621963
final NamedXContentRegistry xContentRegistry,
19631964
final SystemIndices systemIndices
19641965
) throws Exception {
1965-
final ProjectMetadata projectMetadataWithTemplate = ProjectMetadata.builder(project).put(templateName, template).build();
1966-
19671966
final String temporaryIndexName = "validate-template-" + UUIDs.randomBase64UUID().toLowerCase(Locale.ROOT);
1968-
Settings resolvedSettings = resolveSettings(projectMetadataWithTemplate, templateName);
1967+
Settings resolvedSettings = resolveSettings(template, project.componentTemplates());
19691968

19701969
// use the provided values, otherwise just pick valid dummy values
19711970
int dummyPartitionSize = IndexMetadata.INDEX_ROUTING_PARTITION_SIZE_SETTING.get(resolvedSettings);
@@ -1985,23 +1984,17 @@ private static void validateCompositeTemplate(
19851984
.build();
19861985

19871986
// Validate index metadata (settings)
1988-
final ProjectMetadata projectMetadataWithIndex = ProjectMetadata.builder(projectMetadataWithTemplate)
1989-
.put(
1990-
IndexMetadata.builder(temporaryIndexName)
1991-
// necessary to pass asserts in ClusterState constructor
1992-
.eventIngestedRange(IndexLongFieldRange.UNKNOWN)
1993-
.settings(finalResolvedSettings)
1994-
.putCustom(customMetadata)
1995-
)
1987+
final IndexMetadata tmpIndexMetadata = IndexMetadata.builder(temporaryIndexName)
1988+
.settings(finalResolvedSettings)
1989+
.putCustom(customMetadata)
19961990
.build();
1997-
final IndexMetadata tmpIndexMetadata = projectMetadataWithIndex.index(temporaryIndexName);
19981991
indicesService.withTempIndexService(tmpIndexMetadata, tempIndexService -> {
19991992
// Validate aliases
20001993
MetadataCreateIndexService.resolveAndValidateAliases(
20011994
temporaryIndexName,
20021995
Collections.emptySet(),
2003-
MetadataIndexTemplateService.resolveAliases(projectMetadataWithIndex, templateName),
2004-
projectMetadataWithIndex,
1996+
MetadataIndexTemplateService.resolveAliases(project, template),
1997+
project,
20051998
// the context is only used for validation so it's fine to pass fake values for the
20061999
// shard id and the current timestamp
20072000
xContentRegistry,
@@ -2014,7 +2007,7 @@ private static void validateCompositeTemplate(
20142007
String indexName = DataStream.BACKING_INDEX_PREFIX + temporaryIndexName;
20152008
// Parse mappings to ensure they are valid after being composed
20162009

2017-
List<CompressedXContent> mappings = collectMappings(projectMetadataWithIndex, templateName, indexName);
2010+
List<CompressedXContent> mappings = collectMappings(template, project.componentTemplates(), indexName);
20182011
try {
20192012
MapperService mapperService = tempIndexService.mapperService();
20202013
mapperService.merge(MapperService.SINGLE_MAPPING_NAME, mappings, MapperService.MergeReason.INDEX_TEMPLATE);
@@ -2061,10 +2054,6 @@ public static void validateTemplate(Settings validateSettings, CompressedXConten
20612054
});
20622055
}
20632056

2064-
public void validate(String name, ComponentTemplate template) {
2065-
validate(name, template.template(), Collections.emptyList(), null);
2066-
}
2067-
20682057
private void validate(String name, ComposableIndexTemplate template, @Nullable Settings systemProvided) {
20692058
validate(name, template.template(), template.indexPatterns(), systemProvided);
20702059
}

server/src/test/java/org/elasticsearch/cluster/metadata/DataStreamLifecycleWithRetentionWarningsTests.java

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public void testValidateLifecycleIndexTemplateWithWarning() {
169169
HeaderWarning.setThreadContext(threadContext);
170170
TimeValue defaultRetention = randomTimeValue(2, 100, TimeUnit.DAYS);
171171
MetadataIndexTemplateService.validateLifecycle(
172-
ProjectMetadata.builder(randomProjectIdOrDefault()).build(),
172+
Map.of(),
173173
randomAlphaOfLength(10),
174174
ComposableIndexTemplate.builder()
175175
.template(Template.builder().lifecycle(DataStreamLifecycle.Template.DATA_DEFAULT))
@@ -195,7 +195,7 @@ public void testValidateInternalDataStreamRetentionWithoutWarning() {
195195
HeaderWarning.setThreadContext(threadContext);
196196
TimeValue defaultRetention = randomTimeValue(2, 100, TimeUnit.DAYS);
197197
MetadataIndexTemplateService.validateLifecycle(
198-
ProjectMetadata.builder(randomProjectIdOrDefault()).build(),
198+
Map.of(),
199199
randomAlphaOfLength(10),
200200
ComposableIndexTemplate.builder()
201201
.template(Template.builder().lifecycle(DataStreamLifecycle.Template.DATA_DEFAULT))
@@ -216,20 +216,16 @@ public void testValidateLifecycleComponentTemplateWithWarning() {
216216
HeaderWarning.setThreadContext(threadContext);
217217
TimeValue defaultRetention = randomTimeValue(2, 100, TimeUnit.DAYS);
218218
MetadataIndexTemplateService.validateLifecycle(
219-
ProjectMetadata.builder(randomProjectIdOrDefault())
220-
.componentTemplates(
221-
Map.of(
222-
"component-template",
223-
new ComponentTemplate(
224-
Template.builder()
225-
.lifecycle(DataStreamLifecycle.dataLifecycleBuilder().dataRetention(randomTimeValue(2, 100, TimeUnit.DAYS)))
226-
.build(),
227-
null,
228-
null
229-
)
230-
)
219+
Map.of(
220+
"component-template",
221+
new ComponentTemplate(
222+
Template.builder()
223+
.lifecycle(DataStreamLifecycle.dataLifecycleBuilder().dataRetention(randomTimeValue(2, 100, TimeUnit.DAYS)))
224+
.build(),
225+
null,
226+
null
231227
)
232-
.build(),
228+
),
233229
randomAlphaOfLength(10),
234230
ComposableIndexTemplate.builder()
235231
.template(Template.builder().lifecycle(DataStreamLifecycle.Template.DATA_DEFAULT))

0 commit comments

Comments
 (0)