Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize ImmutableOpenMap.Builder #85184

Merged
merged 10 commits into from
Mar 22, 2022

Conversation

joegallo
Copy link
Contributor

@joegallo joegallo commented Mar 21, 2022

Related to #77466 (also kindof related to #82708, in that that's what got me thinking about this). Also shoutout to @jakelandis and also @original-brownbear for daydreaming about this with me earlier today.

Consider Metadata.Builder, it builds four ImmutableOpenMap.Builders from four ImmutableOpenMaps:

this.indices = ImmutableOpenMap.builder(metadata.indices);
this.aliasedIndices = ImmutableOpenMap.builder(metadata.aliasedIndices);
this.templates = ImmutableOpenMap.builder(metadata.templates);
this.customs = ImmutableOpenMap.builder(metadata.customs);

In the current code (before this branch), each of those builder(someMap) invocations results in an ObjectObjectHashMap#clone() behind the scenes. Then, later, those ObjectObjectHashMap clones are used to construct new ImmutableOpenMaps in Metadata.Builder#build(). None of that is predicated on actually touching those maps, so Metadata.builder(metadata).version(2).build() will clone and copy four maps.

This PR changes that in two ways. First, it makes a no-op trip of some map through an ImmutableOpenMap.Builder return the exact same reference, so myMap == ImmutableOpenMap.builder(myMap).build(). Second, it defers cloneing of a passed-in map until the point where a mutable map reference is needed for either a read or a write, so the runtime cost in memory and cpu of a no-op trip through a builder is essentially zero.

Given the way that we frequently use ImmutableOpenMap.Builders in other Builders to refer to maps that often do not change (see grep output that follows), this should make many cluster state updates quite a bit cheaper.

joegallo@galactic:~/Code/elastic/elasticsearch/server/src/main/java/org/elasticsearch/cluster $ git grep 'ImmutableOpenMap.builder(' | grep -v 'ImmutableOpenMap.builder()'
ClusterState.java:            this.customs = ImmutableOpenMap.builder(state.customs());
DiffableUtils.java:            ImmutableOpenMap.Builder<K, T> builder = ImmutableOpenMap.builder(map);
RestoreInProgress.java:        final ImmutableOpenMap.Builder<String, Entry> entriesBuilder = ImmutableOpenMap.builder(count);
SnapshotsInProgress.java:                final ImmutableOpenMap.Builder<RepositoryShardId, ShardSnapshotStatus> byRepoShardIdBuilder = ImmutableOpenMap.builder(
block/ClusterBlocks.java:            ImmutableOpenMap.Builder<String, Set<ClusterBlock>> indicesBuilder = ImmutableOpenMap.builder(indices.size());
metadata/IndexMetadata.java:            this.aliases = ImmutableOpenMap.builder(indexMetadata.aliases);
metadata/IndexMetadata.java:            this.customMetadata = ImmutableOpenMap.builder(indexMetadata.customData);
metadata/IndexMetadata.java:            this.rolloverInfos = ImmutableOpenMap.builder(indexMetadata.rolloverInfos);
metadata/IndexTemplateMetadata.java:            mappings = ImmutableOpenMap.builder(indexTemplateMetadata.mappings);
metadata/IndexTemplateMetadata.java:            aliases = ImmutableOpenMap.builder(indexTemplateMetadata.aliases());
metadata/Metadata.java:            this.indices = ImmutableOpenMap.builder(metadata.indices);
metadata/Metadata.java:            this.aliasedIndices = ImmutableOpenMap.builder(metadata.aliasedIndices);
metadata/Metadata.java:            this.templates = ImmutableOpenMap.builder(metadata.templates);
metadata/Metadata.java:            this.customs = ImmutableOpenMap.builder(metadata.customs);
metadata/MetadataDeleteIndexService.java:            ImmutableOpenMap.Builder<String, ClusterState.Custom> builder = ImmutableOpenMap.builder(customs);
node/DiscoveryNodes.java:        ImmutableOpenMap.Builder<String, DiscoveryNode> nodes = ImmutableOpenMap.builder(dataNodes);
node/DiscoveryNodes.java:        ImmutableOpenMap.Builder<String, DiscoveryNode> nodes = ImmutableOpenMap.builder(this.nodes);
routing/allocation/AllocationService.java:                ImmutableOpenMap.Builder<String, ClusterState.Custom> customsBuilder = ImmutableOpenMap.builder(allocation.getCustoms());

For example, in a very ordinary ILM cluster state update, we change the customMetadata on an IndexMetadata but not the aliases or the rolloverInfos, and then we put that into a new Metadata so we'd be changing the indices but not the templates or customs. Currently, cluster state updates that touch that kind of object tree are paying a price for these things whether they change anything in them or not. With this PR, all those things we don't touch now cost nothing.

Optimizes the case of a noop builder -- builder(someMap).build()
becomes essentially free. The real win here is when the constructing
of the builder and the final invocation of build are separated by lots
of conditionals and the runtime behavior happens to be that the
builder is a noop.
@joegallo joegallo added >enhancement :Core/Infra/Core Core issues without another label v8.2.0 labels Mar 21, 2022
@elasticmachine elasticmachine added the Team:Core/Infra Meta label for core/infra team label Mar 21, 2022
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra (Team:Core/Infra)

@elasticsearchmachine
Copy link
Collaborator

Hi @joegallo, I've created a changelog YAML for you.

@joegallo joegallo force-pushed the optimize-immutable-open-map-builder branch from 5f51021 to d900342 Compare March 21, 2022 20:06
Copy link
Member

@original-brownbear original-brownbear left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, benchmarked this and it offers some unexpected speedups indeed by avoiding copying the large indices map in the metadata builder whenever we're updating a separate part of the metadata (I think that's where most of the speedups are from). Thanks Joe!

@joegallo joegallo merged commit 292e660 into elastic:master Mar 22, 2022
@joegallo joegallo deleted the optimize-immutable-open-map-builder branch March 22, 2022 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Core/Infra/Core Core issues without another label >enhancement Team:Core/Infra Meta label for core/infra team v8.2.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants