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

Script: Metadata validateMetadata optimization #93333

Merged
5 changes: 5 additions & 0 deletions docs/changelog/93333.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 93333
summary: "Script: Metadata `validateMetadata` optimization"
area: Infra/Scripting
type: enhancement
issues: []
36 changes: 30 additions & 6 deletions server/src/main/java/org/elasticsearch/script/Metadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,50 @@ public class Metadata {
public static FieldProperty<Number> LongField = new FieldProperty<>(Number.class).withValidation(FieldProperty.LONGABLE_NUMBER);

protected final Map<String, Object> map;
protected final Map<String, FieldProperty<?>> properties;
private final Map<String, FieldProperty<?>> properties;
protected static final FieldProperty<?> BAD_KEY = new FieldProperty<>(null, false, false, null);

public Metadata(Map<String, Object> map, Map<String, FieldProperty<?>> properties) {
/**
* Constructs a new Metadata object represented by the given map and properties.
* <p>
* The passed-in map is used directly -- subsequent modifications to it outside the methods of this class may result in
stu-elastic marked this conversation as resolved.
Show resolved Hide resolved
* undefined behavior.
* <p>
* The properties map is used directly as well, but we verify at runtime that it <b>must</b> be an immutable map (i.e. constructed
* via a call to {@link Map#of()} (or similar) in production, or via {@link Map#copyOf(Map)}} in tests). Since it must be an
* immutable map, subsequent modifications are not possible.
*
* @param map the backing map for this metadata instance
* @param properties the immutable map of defined properties for the type of metadata represented by this instance
*/
protected Metadata(Map<String, Object> map, Map<String, FieldProperty<?>> properties) {
this.map = map;
this.properties = Collections.unmodifiableMap(properties);
// we can't tell the compiler that properties must be a java.util.ImmutableCollections.AbstractImmutableMap, but
// we can use this copyOf + assert to verify that at runtime.
this.properties = Map.copyOf(properties);
assert this.properties == properties : "properties map must be constructed via Map.of(...) or Map.copyOf(...)";
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a test for this assert?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gah, yeah, I fall upon my sword. 0c97aa3

validateMetadata();
}

// a 'not found' sentinel value for use in validateMetadata below
private static final Object NOT_FOUND = new Object();

/**
* Check that all metadata map contains only valid metadata and no extraneous keys
*/
protected void validateMetadata() {
int numMetadata = 0;
for (Map.Entry<String, FieldProperty<?>> entry : properties.entrySet()) {
String key = entry.getKey();
if (map.containsKey(key)) {
Object value = map.getOrDefault(key, NOT_FOUND);
if (value != NOT_FOUND) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you flip this condition? I know why it's written this way atm but not NOT_FOUND doesn't read nicely.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, yeah, it's way better as the reverse. Thanks! fdd1f27

numMetadata++;
// check whether it's permissible to have the value for the property
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment can be dropped, the other is sufficient. Consider adding a comment of the form "getOrDefault is faster than hasKey + get".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

++, fdd1f27

entry.getValue().check(MapOperation.INIT, key, value);
} else {
// check whether it's permissible to *not* have a value for the property
entry.getValue().check(MapOperation.INIT, key, null);
}
entry.getValue().check(MapOperation.INIT, key, map.get(key));
}
if (numMetadata < map.size()) {
Set<String> keys = new HashSet<>(map.keySet());
Expand Down Expand Up @@ -239,7 +263,7 @@ public int size() {

@Override
public Metadata clone() {
// properties is an UnmodifiableMap, no need to create a copy
// properties is an unmodifiable map, no need to create a copy here
return new Metadata(new HashMap<>(map), properties);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class CtxMapTests extends ESTestCase {
@Before
public void setUp() throws Exception {
super.setUp();
map = new CtxMap<>(new HashMap<>(), new Metadata(new HashMap<>(), new HashMap<>()));
map = new CtxMap<>(new HashMap<>(), new Metadata(Map.of(), Map.of()));
}

public void testAddingJunkToCtx() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private static Map<String, Metadata.FieldProperty<?>> allowAllValidators(String.
for (String key : keys) {
validators.put(key, Metadata.FieldProperty.ALLOW_ALL);
}
return validators;
return Map.copyOf(validators);
}

public void testValidateMetadata() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

public class TestIngestCtxMetadata extends IngestDocMetadata {
public TestIngestCtxMetadata(Map<String, Object> map, Map<String, FieldProperty<?>> properties) {
super(map, properties, null);
super(map, Map.copyOf(properties), null);
}

public static TestIngestCtxMetadata withNullableVersion(Map<String, Object> map) {
Expand Down