Skip to content

Commit 8762cd6

Browse files
authored
Fix bug in circuit-breaker check for geoshape grid aggregations (#57962)
There was a bug in the geoshape circuit-breaker check where the hash values array was being allocated before its new size was accounted for by the circuit breaker. Fixes #57847.
1 parent 69c66a9 commit 8762cd6

File tree

9 files changed

+88
-43
lines changed

9 files changed

+88
-43
lines changed

server/src/main/java/org/elasticsearch/index/fielddata/AbstractSortingNumericDocValues.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.lucene.search.DocIdSetIterator;
2323

2424
import java.io.IOException;
25+
import java.util.function.LongConsumer;
2526

2627
/**
2728
* Base implementation that throws an {@link IOException} for the
@@ -31,6 +32,14 @@
3132
*/
3233
public abstract class AbstractSortingNumericDocValues extends SortingNumericDocValues {
3334

35+
public AbstractSortingNumericDocValues() {
36+
super();
37+
}
38+
39+
public AbstractSortingNumericDocValues(LongConsumer circuitBreakerConsumer) {
40+
super(circuitBreakerConsumer);
41+
}
42+
3443
@Override
3544
public int docID() {
3645
throw new UnsupportedOperationException();

server/src/main/java/org/elasticsearch/index/fielddata/SortingNumericDocValues.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.apache.lucene.util.InPlaceMergeSorter;
2525
import org.apache.lucene.util.Sorter;
2626

27+
import java.util.function.LongConsumer;
28+
2729
/**
2830
* Base class for building {@link SortedNumericDocValues} instances based on unsorted content.
2931
*/
@@ -33,8 +35,13 @@ public abstract class SortingNumericDocValues extends SortedNumericDocValues {
3335
protected long[] values;
3436
protected int valuesCursor;
3537
private final Sorter sorter;
38+
private LongConsumer circuitBreakerConsumer;
3639

3740
protected SortingNumericDocValues() {
41+
this(l -> {});
42+
}
43+
44+
protected SortingNumericDocValues(LongConsumer circuitBreakerConsumer) {
3845
values = new long[1];
3946
valuesCursor = 0;
4047
sorter = new InPlaceMergeSorter() {
@@ -51,6 +58,9 @@ protected int compare(int i, int j) {
5158
return Long.compare(values[i], values[j]);
5259
}
5360
};
61+
this.circuitBreakerConsumer = circuitBreakerConsumer;
62+
// account for initial values size of 1
63+
this.circuitBreakerConsumer.accept(Long.BYTES);
5464
}
5565

5666
/**
@@ -59,8 +69,25 @@ protected int compare(int i, int j) {
5969
*/
6070
protected final void resize(int newSize) {
6171
count = newSize;
62-
values = ArrayUtil.grow(values, count);
6372
valuesCursor = 0;
73+
74+
if (newSize <= values.length) {
75+
return;
76+
}
77+
78+
// Array is expected to grow so increment the circuit breaker
79+
// to include both the additional bytes used by the grown array
80+
// as well as the overhead of keeping both arrays in memory while
81+
// copying.
82+
long oldValuesSizeInBytes = values.length * Long.BYTES;
83+
int newValuesLength = ArrayUtil.oversize(newSize, Long.BYTES);
84+
circuitBreakerConsumer.accept(newValuesLength * Long.BYTES);
85+
86+
// resize
87+
values = ArrayUtil.growExact(values, newValuesLength);
88+
89+
// account for freeing the old values array
90+
circuitBreakerConsumer.accept(-oldValuesSizeInBytes);
6491
}
6592

6693
/**

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AllCellValues.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
import org.elasticsearch.xpack.spatial.index.fielddata.MultiGeoShapeValues;
1010

1111
import java.io.IOException;
12-
import java.util.function.Consumer;
12+
import java.util.function.LongConsumer;
1313

1414
/** Sorted numeric doc values for precision 0 */
1515
class AllCellValues extends ByteTrackingSortingNumericDocValues {
1616
private MultiGeoShapeValues geoValues;
1717

18-
protected AllCellValues(MultiGeoShapeValues geoValues, GeoGridTiler tiler, Consumer<Long> circuitBreakerConsumer) {
18+
protected AllCellValues(MultiGeoShapeValues geoValues, GeoGridTiler tiler, LongConsumer circuitBreakerConsumer) {
19+
super(circuitBreakerConsumer);
1920
this.geoValues = geoValues;
2021
resize(1);
2122
values[0] = tiler.encode(0, 0, 0);
22-
circuitBreakerConsumer.accept((long) Long.BYTES);
2323
}
2424

2525
@Override

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/ByteTrackingSortingNumericDocValues.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@
88

99
import org.elasticsearch.index.fielddata.AbstractSortingNumericDocValues;
1010

11+
import java.util.function.LongConsumer;
12+
13+
/**
14+
* Wrapper class for GeoGrid to expose the protected values array for testing
15+
*/
1116
abstract class ByteTrackingSortingNumericDocValues extends AbstractSortingNumericDocValues {
1217

13-
public long getValuesBytes() {
18+
ByteTrackingSortingNumericDocValues(LongConsumer circuitBreakerConsumer) {
19+
super(circuitBreakerConsumer);
20+
}
21+
22+
long getValuesBytes() {
1423
return values.length * Long.BYTES;
1524
}
1625
}

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellIdSource.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource;
1717
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
1818

19-
import java.util.function.Consumer;
19+
import java.util.function.LongConsumer;
2020

2121
public class GeoShapeCellIdSource extends ValuesSource.Numeric {
2222
private final GeoShapeValuesSource valuesSource;
2323
private final int precision;
2424
private final GeoGridTiler encoder;
25-
private Consumer<Long> circuitBreakerConsumer;
25+
private LongConsumer circuitBreakerConsumer;
2626

2727
public GeoShapeCellIdSource(GeoShapeValuesSource valuesSource, int precision, GeoGridTiler encoder) {
2828
this.valuesSource = valuesSource;
@@ -36,7 +36,7 @@ public GeoShapeCellIdSource(GeoShapeValuesSource valuesSource, int precision, Ge
3636
* accessible from within the values-source. Problem is that this values-source needs to
3737
* be created and passed to the aggregator before we have access to this functionality.
3838
*/
39-
public void setCircuitBreakerConsumer(Consumer<Long> circuitBreakerConsumer) {
39+
public void setCircuitBreakerConsumer(LongConsumer circuitBreakerConsumer) {
4040
this.circuitBreakerConsumer = circuitBreakerConsumer;
4141
}
4242

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellValues.java

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,20 @@
99
import org.elasticsearch.xpack.spatial.index.fielddata.MultiGeoShapeValues;
1010

1111
import java.io.IOException;
12-
import java.util.function.Consumer;
12+
import java.util.function.LongConsumer;
1313

1414
/** Sorted numeric doc values for geo shapes */
1515
class GeoShapeCellValues extends ByteTrackingSortingNumericDocValues {
1616
private final MultiGeoShapeValues geoShapeValues;
17-
private final Consumer<Long> circuitBreakerConsumer;
1817
protected int precision;
1918
protected GeoGridTiler tiler;
2019

2120
protected GeoShapeCellValues(MultiGeoShapeValues geoShapeValues, int precision, GeoGridTiler tiler,
22-
Consumer<Long> circuitBreakerConsumer) {
21+
LongConsumer circuitBreakerConsumer) {
22+
super(circuitBreakerConsumer);
2323
this.geoShapeValues = geoShapeValues;
2424
this.precision = precision;
2525
this.tiler = tiler;
26-
this.circuitBreakerConsumer = circuitBreakerConsumer;
27-
circuitBreakerConsumer.accept((long) Long.BYTES);
2826
}
2927

3028
@Override
@@ -45,18 +43,13 @@ protected long[] getValues() {
4543
return values;
4644
}
4745

48-
protected void add(int idx, long value) {
49-
values[idx] = value;
50-
}
51-
5246
void resizeCell(int newSize) {
53-
int oldValuesLength = values.length;
5447
resize(newSize);
55-
int newValuesLength = values.length;
56-
if (newValuesLength > oldValuesLength) {
57-
long bytesDiff = (newValuesLength - oldValuesLength) * Long.BYTES;
58-
circuitBreakerConsumer.accept(bytesDiff);
59-
}
48+
}
49+
50+
51+
protected void add(int idx, long value) {
52+
values[idx] = value;
6053
}
6154

6255
/**

x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTilerTests.java

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.elasticsearch.geometry.Geometry;
1717
import org.elasticsearch.geometry.LinearRing;
1818
import org.elasticsearch.geometry.MultiLine;
19-
import org.elasticsearch.geometry.MultiPoint;
2019
import org.elasticsearch.geometry.MultiPolygon;
2120
import org.elasticsearch.geometry.Point;
2221
import org.elasticsearch.geometry.Polygon;
@@ -34,10 +33,11 @@
3433
import org.elasticsearch.xpack.spatial.index.fielddata.TriangleTreeReader;
3534

3635
import java.io.IOException;
36+
import java.util.ArrayList;
3737
import java.util.Arrays;
3838
import java.util.Collections;
3939
import java.util.List;
40-
import java.util.function.Consumer;
40+
import java.util.function.LongConsumer;
4141

4242
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.LATITUDE_MASK;
4343
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.NORMALIZED_LATITUDE_MASK;
@@ -51,7 +51,7 @@
5151
public class GeoGridTilerTests extends ESTestCase {
5252
private static final GeoTileGridTiler GEOTILE = new GeoTileGridTiler();
5353
private static final GeoHashGridTiler GEOHASH = new GeoHashGridTiler();
54-
private static final Consumer<Long> NOOP_BREAKER = (l) -> {};
54+
private static final LongConsumer NOOP_BREAKER = (l) -> {};
5555

5656
public void testGeoTile() throws Exception {
5757
double x = randomDouble();
@@ -465,33 +465,40 @@ public void testGeoTileGridCircuitBreaker() throws IOException {
465465
}
466466

467467
private void testCircuitBreaker(GeoGridTiler tiler) throws IOException {
468-
MultiPoint multiPoint = GeometryTestUtils.randomMultiPoint(false);
469-
int precision = randomIntBetween(0, 6);
470-
TriangleTreeReader reader = triangleTreeReader(multiPoint, GeoShapeCoordinateEncoder.INSTANCE);
468+
Polygon polygon = GeometryTestUtils.randomPolygon(false);
469+
int precision = randomIntBetween(0, 7);
470+
TriangleTreeReader reader = triangleTreeReader(polygon, GeoShapeCoordinateEncoder.INSTANCE);
471471
MultiGeoShapeValues.GeoShapeValue value = new MultiGeoShapeValues.GeoShapeValue(reader);
472472

473-
final long numBytes;
473+
List<Long> byteChangeHistory = new ArrayList<>();
474474
if (precision == 0) {
475-
AllCellValues values = new AllCellValues(null, tiler, NOOP_BREAKER);
476-
numBytes = values.getValuesBytes();
475+
new AllCellValues(null, tiler, byteChangeHistory::add);
477476
} else {
478-
GeoShapeCellValues values = new GeoShapeCellValues(null, precision, tiler, NOOP_BREAKER);
477+
GeoShapeCellValues values = new GeoShapeCellValues(null, precision, tiler, byteChangeHistory::add);
479478
tiler.setValues(values, value, precision);
480-
numBytes = values.getValuesBytes();
479+
}
480+
481+
final long maxNumBytes;
482+
final long curNumBytes;
483+
if (byteChangeHistory.size() == 1) {
484+
curNumBytes = maxNumBytes = byteChangeHistory.get(byteChangeHistory.size() - 1);
485+
} else {
486+
long oldNumBytes = -byteChangeHistory.get(byteChangeHistory.size() - 1);
487+
curNumBytes = byteChangeHistory.get(byteChangeHistory.size() - 2);
488+
maxNumBytes = oldNumBytes + curNumBytes;
481489
}
482490

483491
CircuitBreakerService service = new HierarchyCircuitBreakerService(Settings.EMPTY,
484-
Collections.singletonList(new BreakerSettings("limited", numBytes - 1, 1.0)),
492+
Collections.singletonList(new BreakerSettings("limited", maxNumBytes - 1, 1.0)),
485493
new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));
486494
CircuitBreaker limitedBreaker = service.getBreaker("limited");
487495

488-
Consumer<Long> circuitBreakerConsumer = (l) -> limitedBreaker.addEstimateBytesAndMaybeBreak(l, "agg");
496+
LongConsumer circuitBreakerConsumer = (l) -> limitedBreaker.addEstimateBytesAndMaybeBreak(l, "agg");
489497
expectThrows(CircuitBreakingException.class, () -> {
490498
GeoShapeCellValues values = new GeoShapeCellValues(null, precision, tiler, circuitBreakerConsumer);
491499
tiler.setValues(values, value, precision);
492-
assertThat(values.getValuesBytes(), equalTo(numBytes));
493-
assertThat(limitedBreaker.getUsed(), equalTo(numBytes));
500+
assertThat(values.getValuesBytes(), equalTo(curNumBytes));
501+
assertThat(limitedBreaker.getUsed(), equalTo(curNumBytes));
494502
});
495-
496503
}
497504
}

x-pack/plugin/spatial/src/test/resources/rest-api-spec/test/30_geotile_grid.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
indices.refresh: {}
8181

8282
- do:
83-
catch: /data for \[<agg \[grid\]>\] would be \[28648\/27.9kb\], which is larger than the limit of \[25600\/25kb\]/
83+
catch: /data for \[<agg \[grid\]>\] would be \[42760\/41.7kb\], which is larger than the limit of \[25600\/25kb\]/
8484
search:
8585
rest_total_hits_as_int: true
8686
index: locations
@@ -93,7 +93,7 @@
9393
field: location
9494

9595
- do:
96-
catch: /data for \[<agg \[grid\]>\] would be \[28648\/27.9kb\], which is larger than the limit of \[25600\/25kb\]/
96+
catch: /data for \[<agg \[grid\]>\] would be \[42760\/41.7kb\], which is larger than the limit of \[25600\/25kb\]/
9797
search:
9898
rest_total_hits_as_int: true
9999
index: locations

x-pack/plugin/spatial/src/test/resources/rest-api-spec/test/40_geohash_grid.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
indices.refresh: {}
8383

8484
- do:
85-
catch: /data for \[<agg \[grid\]>\] would be \[26344\/25.7kb\], which is larger than the limit of \[25600\/25kb\]/
85+
catch: /data for \[<agg \[grid\]>\] would be \[30160\/29.4kb\], which is larger than the limit of \[25600\/25kb\]/
8686
search:
8787
rest_total_hits_as_int: true
8888
index: locations
@@ -95,7 +95,7 @@
9595
field: location
9696

9797
- do:
98-
catch: /data for \[<agg \[grid\]>\] would be \[26344\/25.7kb\], which is larger than the limit of \[25600\/25kb\]/
98+
catch: /data for \[<agg \[grid\]>\] would be \[30160\/29.4kb\], which is larger than the limit of \[25600\/25kb\]/
9999
search:
100100
rest_total_hits_as_int: true
101101
index: locations

0 commit comments

Comments
 (0)