Skip to content

Commit 8a1aa7c

Browse files
authored
Fix minimum required bucket count for exponential histogram merging (#133711)
1 parent 3b853aa commit 8a1aa7c

File tree

5 files changed

+29
-6
lines changed

5 files changed

+29
-6
lines changed

libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMerger.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,21 @@ public class ExponentialHistogramMerger implements Accountable, Releasable {
5656
/**
5757
* Creates a new instance with the specified bucket limit.
5858
*
59-
* @param bucketLimit the maximum number of buckets the result histogram is allowed to have
59+
* @param bucketLimit the maximum number of buckets the result histogram is allowed to have, must be at least 4
6060
* @param circuitBreaker the circuit breaker to use to limit memory allocations
6161
*/
6262
public static ExponentialHistogramMerger create(int bucketLimit, ExponentialHistogramCircuitBreaker circuitBreaker) {
6363
circuitBreaker.adjustBreaker(BASE_SIZE);
64-
return new ExponentialHistogramMerger(bucketLimit, circuitBreaker);
64+
boolean success = false;
65+
try {
66+
ExponentialHistogramMerger result = new ExponentialHistogramMerger(bucketLimit, circuitBreaker);
67+
success = true;
68+
return result;
69+
} finally {
70+
if (success == false) {
71+
circuitBreaker.adjustBreaker(-BASE_SIZE);
72+
}
73+
}
6574
}
6675

6776
private ExponentialHistogramMerger(int bucketLimit, ExponentialHistogramCircuitBreaker circuitBreaker) {
@@ -70,6 +79,10 @@ private ExponentialHistogramMerger(int bucketLimit, ExponentialHistogramCircuitB
7079

7180
// Only intended for testing, using this in production means an unnecessary reduction of precision
7281
private ExponentialHistogramMerger(int bucketLimit, int maxScale, ExponentialHistogramCircuitBreaker circuitBreaker) {
82+
// We need at least four buckets to represent any possible distribution
83+
if (bucketLimit < 4) {
84+
throw new IllegalArgumentException("The bucket limit must be at least 4");
85+
}
7386
this.bucketLimit = bucketLimit;
7487
this.maxScale = maxScale;
7588
this.circuitBreaker = circuitBreaker;

libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGeneratorTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class ExponentialHistogramGeneratorTests extends ExponentialHistogramTest
3333

3434
public void testVeryLargeValue() {
3535
double value = Double.MAX_VALUE / 10;
36-
ExponentialHistogram histo = createAutoReleasedHistogram(1, value);
36+
ExponentialHistogram histo = createAutoReleasedHistogram(4, value);
3737
long index = histo.positiveBuckets().iterator().peekIndex();
3838
int scale = histo.scale();
3939

libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMergerTests.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MIN_INDEX;
3737
import static org.elasticsearch.exponentialhistogram.ExponentialScaleUtils.adjustScale;
3838
import static org.hamcrest.Matchers.closeTo;
39+
import static org.hamcrest.Matchers.containsString;
3940
import static org.hamcrest.Matchers.equalTo;
4041
import static org.hamcrest.Matchers.greaterThan;
4142

@@ -112,7 +113,7 @@ public void testAggregatesCorrectness() {
112113
try (
113114
// Merge some empty histograms too to test that code path
114115
ReleasableExponentialHistogram merged = ExponentialHistogram.merge(
115-
2,
116+
4,
116117
breaker(),
117118
ExponentialHistogram.empty(),
118119
createAutoReleasedHistogram(10, firstValues),
@@ -153,6 +154,15 @@ public void testUpscalingDoesNotExceedIndexLimits() {
153154
}
154155
}
155156

157+
public void testMinimumBucketCountBounded() {
158+
try {
159+
ExponentialHistogram.merge(3, breaker(), ExponentialHistogram.empty(), ExponentialHistogram.empty());
160+
fail("Expected exception");
161+
} catch (IllegalArgumentException e) {
162+
assertThat(e.getMessage(), containsString("limit must be at least 4"));
163+
}
164+
}
165+
156166
/**
157167
* Verify that the resulting histogram is independent of the order of elements and therefore merges performed.
158168
*/

libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramUtilsTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class ExponentialHistogramUtilsTests extends ExponentialHistogramTestCase
3131
public void testRandomDataSumEstimation() {
3232
for (int i = 0; i < 100; i++) {
3333
int valueCount = randomIntBetween(100, 10_000);
34-
int bucketCount = randomIntBetween(2, 500);
34+
int bucketCount = randomIntBetween(4, 500);
3535

3636
double correctSum = 0;
3737
double sign = randomBoolean() ? 1 : -1;

libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/QuantileAccuracyTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public void testEmptyHistogram() {
159159
}
160160

161161
public void testSingleValueHistogram() {
162-
ExponentialHistogram histo = createAutoReleasedHistogram(1, 42.0);
162+
ExponentialHistogram histo = createAutoReleasedHistogram(4, 42.0);
163163
for (double q : QUANTILES_TO_TEST) {
164164
assertThat(ExponentialHistogramQuantile.getQuantile(histo, q), closeTo(42, 0.0000001));
165165
}

0 commit comments

Comments
 (0)