diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java index 1d762230b5df0..c6d0944ca6d90 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java @@ -36,7 +36,6 @@ import org.elasticsearch.common.xcontent.ChunkedToXContent; import org.elasticsearch.common.xcontent.ChunkedToXContentHelper; import org.elasticsearch.common.xcontent.XContentParserUtils; -import org.elasticsearch.core.Assertions; import org.elasticsearch.core.FixForMultiProject; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Tuple; @@ -1662,19 +1661,12 @@ public Metadata build() { } public Metadata build(boolean skipNameCollisionChecks) { - if (projectMetadata.isEmpty()) { - createDefaultProject(); - } else if (Assertions.ENABLED) { - projectMetadata.forEach((id, project) -> { - assert project.getId().equals(id) : "project id mismatch key=[" + id + "] builder=[" + project.getId() + "]"; - }); - } return new Metadata( clusterUUID, clusterUUIDCommitted, version, coordinationMetadata, - Collections.unmodifiableMap(Maps.transformValues(projectMetadata, m -> m.build(skipNameCollisionChecks))), + buildProjectMetadata(skipNameCollisionChecks), transientSettings, persistentSettings, Settings.builder().put(persistentSettings).put(transientSettings).build(), @@ -1684,10 +1676,32 @@ public Metadata build(boolean skipNameCollisionChecks) { ); } + private Map buildProjectMetadata(boolean skipNameCollisionChecks) { + if (projectMetadata.isEmpty()) { + createDefaultProject(); + } + assert assertProjectIdAndProjectMetadataConsistency(); + if (projectMetadata.size() == 1) { + final var entry = projectMetadata.entrySet().iterator().next(); + // Map.of() with a single entry is highly optimized + // so we want take advantage of that performance boost for this common case of a single project + return Map.of(entry.getKey(), entry.getValue().build(skipNameCollisionChecks)); + } else { + return Collections.unmodifiableMap(Maps.transformValues(projectMetadata, m -> m.build(skipNameCollisionChecks))); + } + } + private ProjectMetadata.Builder createDefaultProject() { return projectMetadata.put(DEFAULT_PROJECT_ID, new ProjectMetadata.Builder(Map.of(), 0).id(DEFAULT_PROJECT_ID)); } + private boolean assertProjectIdAndProjectMetadataConsistency() { + projectMetadata.forEach((id, project) -> { + assert project.getId().equals(id) : "project id mismatch key=[" + id + "] builder=[" + project.getId() + "]"; + }); + return true; + } + /** * There are a set of specific custom sections that have moved from top-level sections to project-level sections * as part of the multi-project refactor. Enumerate them here so we can move them to the right place