Skip to content

Speed up DiscoveryNodeFilters.trimTier #78170

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.core.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

public class DiscoveryNodeFilters {

Expand Down Expand Up @@ -49,7 +49,10 @@ public enum OpType {
};

public static DiscoveryNodeFilters buildFromKeyValue(OpType opType, Map<String, String> filters) {
Map<String, String[]> bFilters = new HashMap<>();
if (filters.isEmpty()) {
return null;
}
Map<String, String[]> bFilters = new HashMap<>(filters.size());
for (Map.Entry<String, String> entry : filters.entrySet()) {
String[] values = Strings.tokenizeToStringArray(entry.getValue(), ",");
if (values.length > 0) {
Expand All @@ -68,7 +71,7 @@ public static DiscoveryNodeFilters buildFromKeyValue(OpType opType, Map<String,

DiscoveryNodeFilters(OpType opType, Map<String, String[]> filters) {
this.opType = opType;
this.filters = filters;
this.filters = Map.copyOf(filters);
}

private boolean matchByIP(String[] values, @Nullable String hostIp, @Nullable String publishIp) {
Expand All @@ -82,6 +85,8 @@ private boolean matchByIP(String[] values, @Nullable String hostIp, @Nullable St
return false;
}

private static final String TIER_PREFERENCE = "_tier_preference";
Copy link
Member

Choose a reason for hiding this comment

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

Can you move this up to the top where the other class variables are please?


/**
* Removes any filters that should not be considered, returning a new
* {@link DiscoveryNodeFilters} object. If the filtered object has no
Expand All @@ -92,20 +97,14 @@ public static DiscoveryNodeFilters trimTier(@Nullable DiscoveryNodeFilters origi
if (original == null) {
return null;
}

Map<String, String[]> newFilters = original.filters.entrySet().stream()
if (original.filters.containsKey(TIER_PREFERENCE)) {
// Remove all entries that use "_tier_preference", as these will be handled elsewhere
.filter(entry -> {
String attr = entry.getKey();
return attr != null && attr.equals("_tier_preference") == false;
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

if (newFilters.size() == 0) {
return null;
} else {
return new DiscoveryNodeFilters(original.opType, newFilters);
if (original.filters.size() == 1) {
return null;
}
return new DiscoveryNodeFilters(original.opType, Maps.copyMapWithRemovedEntry(original.filters, TIER_PREFERENCE));
}
return original;
}

public boolean match(DiscoveryNode node) {
Expand Down
20 changes: 17 additions & 3 deletions server/src/main/java/org/elasticsearch/common/util/Maps.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,27 @@ public static <K, V> Map<K, V> copyMapWithAddedOrReplacedEntry(final Map<K, V> m
* @param <V> the type of the values in the map
* @return an immutable map that contains the items from the specified map with the provided key removed
*/
@SuppressWarnings("unchecked")
public static <K, V> Map<K, V> copyMapWithRemovedEntry(final Map<K, V> map, final K key) {
Objects.requireNonNull(map);
Objects.requireNonNull(key);
assert checkIsImmutableMap(map, key, map.get(key));
return map.entrySet().stream().filter(k -> key.equals(k.getKey()) == false)
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
if (map.containsKey(key) == false) {
return map;
}
Comment on lines +89 to +91
Copy link
Member

Choose a reason for hiding this comment

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

This subtly changes the behavior because someone would assume that the returned value is always a copied map (since it's in the name), and not the original map

final int size = map.size();
if (size == 1) {
return Map.of();
}
@SuppressWarnings("rawtypes")
final Map.Entry<K, V>[] entries = new Map.Entry[size - 1];
int i = 0;
for (Map.Entry<K, V> entry : map.entrySet()) {
if (key.equals(entry.getKey()) == false) {
entries[i++] = entry;
}
}
return Map.ofEntries(entries);
Comment on lines +96 to +104
Copy link
Member

Choose a reason for hiding this comment

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

This feels a lot like premature optimization, is this really a bottleneck here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This whole thing showed up hot during cluster restart + reroute tests on large shard count benchmarks. The problem with those really is that all these stream things look nice and their overhead might be irrelevant most of the time, now show up all over the place when they get nested inside other loops and we simply only have a single master update thread :)
I think the 7.x code is different for the data tier allocation decider that's why it shows up there, for 8.x this is less of a relevant change probably but as you point out will help with the filter decider.

}

// map classes that are known to be immutable, used to speed up immutability check in #assertImmutableMap
Expand Down Expand Up @@ -154,7 +169,6 @@ public static <K, V> boolean deepEquals(Map<K, V> left, Map<K, V> right) {
* @param map - input to be flattened
* @param flattenArrays - if false, arrays will be ignored
* @param ordered - if true the resulted map will be sorted
* @return
*/
public static Map<String, Object> flatten(Map<String, Object> map, boolean flattenArrays, boolean ordered) {
return flatten(map, flattenArrays, ordered, null);
Expand Down
11 changes: 11 additions & 0 deletions server/src/test/java/org/elasticsearch/common/util/MapsTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import static java.util.Map.entry;
import static java.util.stream.Collectors.toMap;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItem;
Expand Down Expand Up @@ -190,6 +191,16 @@ public void testFlatten() {
}
}

public void testCopyMapWithAddedOrReplacedEntry() {
final Map<String, String> start = Map.of("foo", "bar", "blub", "bla");
final Map<String, String> removeMissing = Maps.copyMapWithRemovedEntry(start, "missing");
assertSame(start, removeMissing);
final Map<String, String> removeFoo = Maps.copyMapWithRemovedEntry(start, "foo");
assertThat(removeFoo, equalTo(Map.of("blub", "bla")));
final Map<String, String> removeBlub = Maps.copyMapWithRemovedEntry(removeFoo, "blub");
assertThat(removeBlub, anEmptyMap());
}

@SuppressWarnings("unchecked")
private static Object deepGet(String path, Object obj) {
Object cur = obj;
Expand Down