Skip to content

Commit 22a8d53

Browse files
committed
Prevent slow lookups for non-existent fields.
We now track the maximum depth of any JSON field, which allows the JSON field lookup to be short-circuited as soon as that depth is reached. This helps prevent slow lookups when the user is searching over a very deep field that is not in the mappings.
1 parent cdaf8e4 commit 22a8d53

File tree

2 files changed

+92
-3
lines changed

2 files changed

+92
-3
lines changed

server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Collection;
2626
import java.util.HashSet;
2727
import java.util.Iterator;
28+
import java.util.Map;
2829
import java.util.Objects;
2930
import java.util.Set;
3031

@@ -35,20 +36,25 @@ class FieldTypeLookup implements Iterable<MappedFieldType> {
3536

3637
final CopyOnWriteHashMap<String, MappedFieldType> fullNameToFieldType;
3738
private final CopyOnWriteHashMap<String, String> aliasToConcreteName;
39+
3840
private final CopyOnWriteHashMap<String, JsonFieldMapper> fullNameToJsonMapper;
41+
private final int maxJsonFieldDepth;
3942

4043
FieldTypeLookup() {
4144
fullNameToFieldType = new CopyOnWriteHashMap<>();
4245
aliasToConcreteName = new CopyOnWriteHashMap<>();
4346
fullNameToJsonMapper = new CopyOnWriteHashMap<>();
47+
maxJsonFieldDepth = 0;
4448
}
4549

4650
private FieldTypeLookup(CopyOnWriteHashMap<String, MappedFieldType> fullNameToFieldType,
4751
CopyOnWriteHashMap<String, String> aliasToConcreteName,
48-
CopyOnWriteHashMap<String, JsonFieldMapper> fullNameToJsonMapper) {
52+
CopyOnWriteHashMap<String, JsonFieldMapper> fullNameToJsonMapper,
53+
int maxJsonFieldDepth) {
4954
this.fullNameToFieldType = fullNameToFieldType;
5055
this.aliasToConcreteName = aliasToConcreteName;
5156
this.fullNameToJsonMapper = fullNameToJsonMapper;
57+
this.maxJsonFieldDepth = maxJsonFieldDepth;
5258
}
5359

5460
/**
@@ -70,6 +76,7 @@ public FieldTypeLookup copyAndAddAll(String type,
7076
CopyOnWriteHashMap<String, JsonFieldMapper> jsonMappers = this.fullNameToJsonMapper;
7177

7278
for (FieldMapper fieldMapper : fieldMappers) {
79+
String fieldName = fieldMapper.name();
7380
MappedFieldType fieldType = fieldMapper.fieldType();
7481
MappedFieldType fullNameFieldType = fullName.get(fieldType.name());
7582

@@ -78,7 +85,7 @@ public FieldTypeLookup copyAndAddAll(String type,
7885
}
7986

8087
if (fieldMapper instanceof JsonFieldMapper) {
81-
jsonMappers = fullNameToJsonMapper.copyAndPut(fieldType.name(), (JsonFieldMapper) fieldMapper);
88+
jsonMappers = fullNameToJsonMapper.copyAndPut(fieldName, (JsonFieldMapper) fieldMapper);
8289
}
8390
}
8491

@@ -88,7 +95,43 @@ public FieldTypeLookup copyAndAddAll(String type,
8895
aliases = aliases.copyAndPut(aliasName, path);
8996
}
9097

91-
return new FieldTypeLookup(fullName, aliases, jsonMappers);
98+
int maxFieldDepth = getMaxJsonFieldDepth(aliases, jsonMappers);
99+
100+
return new FieldTypeLookup(fullName, aliases, jsonMappers, maxFieldDepth);
101+
}
102+
103+
private static int getMaxJsonFieldDepth(CopyOnWriteHashMap<String, String> aliases,
104+
CopyOnWriteHashMap<String, JsonFieldMapper> jsonMappers) {
105+
int maxFieldDepth = 0;
106+
for (Map.Entry<String, String> entry : aliases.entrySet()) {
107+
String aliasName = entry.getKey();
108+
String path = entry.getValue();
109+
if (jsonMappers.containsKey(path)) {
110+
maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(aliasName));
111+
}
112+
}
113+
114+
for (String fieldName : jsonMappers.keySet()) {
115+
if (jsonMappers.containsKey(fieldName)) {
116+
maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(fieldName));
117+
}
118+
}
119+
120+
return maxFieldDepth;
121+
}
122+
123+
/**
124+
* Computes the total depth of this field by counting the number of parent fields
125+
* in its path. As an example, the field 'parent1.parent2.field' has depth 3.
126+
*/
127+
private static int fieldDepth(String field) {
128+
int numDots = 0;
129+
for (int i = 0; i < field.length(); ++i) {
130+
if (field.charAt(i) == '.') {
131+
numDots++;
132+
}
133+
}
134+
return numDots + 1;
92135
}
93136

94137

@@ -107,9 +150,20 @@ public MappedFieldType get(String field) {
107150
return !fullNameToJsonMapper.isEmpty() ? getKeyedJsonField(field) : null;
108151
}
109152

153+
/**
154+
* Check if the given field corresponds to a keyed JSON field of the form
155+
* 'path_to_json_field.path_to_key'. If so, returns a field type that can
156+
* be used to perform searches on this field.
157+
*/
110158
private MappedFieldType getKeyedJsonField(String field) {
111159
int dotIndex = -1;
160+
int fieldDepth = 0;
161+
112162
while (true) {
163+
if (++fieldDepth > maxJsonFieldDepth) {
164+
return null;
165+
}
166+
113167
dotIndex = field.indexOf('.', dotIndex + 1);
114168
if (dotIndex < 0) {
115169
return null;
@@ -148,4 +202,9 @@ public Collection<String> simpleMatchToFullName(String pattern) {
148202
public Iterator<MappedFieldType> iterator() {
149203
return fullNameToFieldType.values().iterator();
150204
}
205+
206+
// Visible for testing.
207+
int maxJsonFieldDepth() {
208+
return maxJsonFieldDepth;
209+
}
151210
}

server/src/test/java/org/elasticsearch/index/mapper/FieldTypeLookupTests.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,36 @@ public void testJsonFieldTypeWithAlias() {
186186
assertEquals(objectKey, keyedFieldType.key());
187187
}
188188

189+
public void testMaxJsonFieldDepth() {
190+
FieldTypeLookup lookup = new FieldTypeLookup();
191+
assertEquals(0, lookup.maxJsonFieldDepth());
192+
193+
// Add a JSON field.
194+
String jsonFieldName = "object1.object2.field";
195+
JsonFieldMapper jsonField = createJsonMapper(jsonFieldName);
196+
lookup = lookup.copyAndAddAll("type", newList(jsonField), emptyList());
197+
assertEquals(3, lookup.maxJsonFieldDepth());
198+
199+
// Add a short alias to that field.
200+
String aliasName = "alias";
201+
FieldAliasMapper alias = new FieldAliasMapper(aliasName, aliasName, jsonFieldName);
202+
lookup = lookup.copyAndAddAll("type", emptyList(), newList(alias));
203+
assertEquals(3, lookup.maxJsonFieldDepth());
204+
205+
// Add a longer alias to that field.
206+
String longAliasName = "object1.object2.object3.alias";
207+
FieldAliasMapper longAlias = new FieldAliasMapper(longAliasName, longAliasName, jsonFieldName);
208+
lookup = lookup.copyAndAddAll("type", emptyList(), newList(longAlias));
209+
assertEquals(4, lookup.maxJsonFieldDepth());
210+
211+
// Update the long alias to refer to a non-JSON field.
212+
String fieldName = "field";
213+
MockFieldMapper field = new MockFieldMapper(fieldName);
214+
longAlias = new FieldAliasMapper(longAliasName, longAliasName, fieldName);
215+
lookup = lookup.copyAndAddAll("type", newList(field), newList(longAlias));
216+
assertEquals(3, lookup.maxJsonFieldDepth());
217+
}
218+
189219
private JsonFieldMapper createJsonMapper(String fieldName) {
190220
Settings settings = Settings.builder()
191221
.put("index.version.created", Version.CURRENT)

0 commit comments

Comments
 (0)