Skip to content

Commit f7a70ff

Browse files
authored
CNDB-13617: EstimatedHistogram uses C* bucket boundaries regardless of the DSE-compatibility flag (#1725)
### What is the issue When using `use_dse_compatible_histogram_boundaries` flag we by mistake use DSE DecayingEstimatedHistogramReservoir bucket boundaries for `EstimatedHistogram`. This may lead to unexpected and unhandled histogram overflow for extremely large partitions. ### What does this PR fix and why was it fixed This fix makes `EstimatedHistogram` use, by default, the same bucket boundaries as in upstream Cassandra and DSE `EstimatedHistogram`
1 parent c0a2d76 commit f7a70ff

File tree

6 files changed

+127
-30
lines changed

6 files changed

+127
-30
lines changed

src/java/org/apache/cassandra/io/sstable/metadata/MetadataCollector.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ static EstimatedHistogram defaultCellPerPartitionCountHistogram()
5656

5757
static EstimatedHistogram defaultPartitionSizeHistogram()
5858
{
59-
// EH of 150 can track a max value of 1697806495183, i.e., > 1.5PB
59+
// EH of 150 can track a max value of 1414838745986, i.e., ~ 1.5PB
60+
// see {@link MetadataCollectorTest#testFindMaxSampleWithoutOverflow} for details
6061
return new EstimatedHistogram(150);
6162
}
6263

src/java/org/apache/cassandra/metrics/DecayingEstimatedHistogramReservoir.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838

3939
import static java.lang.Math.max;
4040
import static java.lang.Math.min;
41-
import static org.apache.cassandra.utils.EstimatedHistogram.USE_DSE_COMPATIBLE_HISTOGRAM_BOUNDARIES;
4241

4342
/**
4443
* A decaying histogram reservoir where values collected during each minute will be twice as significant as the values
@@ -81,6 +80,7 @@
8180
*/
8281
public class DecayingEstimatedHistogramReservoir implements Reservoir
8382
{
83+
public static final boolean USE_DSE_COMPATIBLE_HISTOGRAM_BOUNDARIES = CassandraRelevantProperties.USE_DSE_COMPATIBLE_HISTOGRAM_BOUNDARIES.getBoolean();
8484
/**
8585
* The default number of decayingBuckets. Use this bucket count to reduce memory allocation for bucket offsets.
8686
*/
@@ -224,7 +224,10 @@ public DecayingEstimatedHistogramReservoir(Clock clock)
224224
}
225225
else
226226
{
227-
bucketOffsets = EstimatedHistogram.newOffsets(bucketCount, considerZeroes);
227+
if (USE_DSE_COMPATIBLE_HISTOGRAM_BOUNDARIES)
228+
bucketOffsets = newDseOffsets(bucketCount, considerZeroes);
229+
else
230+
bucketOffsets = EstimatedHistogram.newOffsets(bucketCount, considerZeroes);
228231
}
229232

230233
nStripes = stripes;

src/java/org/apache/cassandra/utils/EstimatedHistogram.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
public class EstimatedHistogram
3737
{
3838
public static final EstimatedHistogramSerializer serializer = new EstimatedHistogramSerializer();
39-
public static final boolean USE_DSE_COMPATIBLE_HISTOGRAM_BOUNDARIES = CassandraRelevantProperties.USE_DSE_COMPATIBLE_HISTOGRAM_BOUNDARIES.getBoolean();
4039

4140
/**
4241
* The series of values to which the counts in `buckets` correspond:
@@ -91,10 +90,7 @@ public EstimatedHistogram(long[] offsets, long[] bucketData)
9190

9291
public static long[] newOffsets(int size, boolean considerZeroes)
9392
{
94-
if (USE_DSE_COMPATIBLE_HISTOGRAM_BOUNDARIES)
95-
return DecayingEstimatedHistogramReservoir.newDseOffsets(size, considerZeroes);
96-
else
97-
return newCassandraOffsets(size, considerZeroes);
93+
return newCassandraOffsets(size, considerZeroes);
9894
}
9995

10096
public static long[] newCassandraOffsets(int size, boolean considerZeroes)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.cassandra.io.sstable.metadata;
20+
21+
import org.apache.cassandra.config.CassandraRelevantProperties;
22+
import org.apache.cassandra.utils.EstimatedHistogram;
23+
import org.junit.Test;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
import static org.apache.cassandra.io.sstable.metadata.MetadataCollector.defaultPartitionSizeHistogram;
28+
import static org.junit.Assert.*;
29+
30+
public class MetadataCollectorTest
31+
{
32+
private static final Logger logger = LoggerFactory.getLogger(MetadataCollectorTest.class);
33+
34+
@Test
35+
public void testNoOverflow()
36+
{
37+
EstimatedHistogram histogram = defaultPartitionSizeHistogram();
38+
histogram.add(1414838745986L);
39+
assertFalse(histogram.isOverflowed());
40+
}
41+
42+
@Test
43+
public void testFindMaxSampleWithoutOverflow()
44+
{
45+
logger.info("dse compatible boundaries: {}", CassandraRelevantProperties.USE_DSE_COMPATIBLE_HISTOGRAM_BOUNDARIES.getBoolean());
46+
47+
long low = 0;
48+
long high = Long.MAX_VALUE;
49+
long result = -1;
50+
51+
while (low <= high) {
52+
long mid = low + (high - low) / 2; // Avoid potential overflow in (low + high) / 2
53+
54+
// Create a fresh histogram for each test to avoid accumulated state
55+
EstimatedHistogram testHistogram = defaultPartitionSizeHistogram();
56+
testHistogram.add(mid);
57+
58+
if (testHistogram.isOverflowed()) {
59+
high = mid - 1;
60+
} else {
61+
result = mid; // Keep track of the last working value
62+
low = mid + 1;
63+
}
64+
}
65+
66+
logger.info("Max value without overflow: {}", result);
67+
68+
// Verify the result
69+
EstimatedHistogram finalHistogram = defaultPartitionSizeHistogram();
70+
finalHistogram.add(result);
71+
assertFalse(finalHistogram.isOverflowed());
72+
73+
// Verify that result + 1 causes overflow
74+
EstimatedHistogram overflowHistogram = defaultPartitionSizeHistogram();
75+
overflowHistogram.add(result + 1);
76+
assertTrue(overflowHistogram.isOverflowed());
77+
}
78+
}

test/unit/org/apache/cassandra/metrics/DecayingEstimatedHistogramReservoirTestBase.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
import static java.util.concurrent.TimeUnit.MILLISECONDS;
4646
import static java.util.concurrent.TimeUnit.SECONDS;
47+
import static org.apache.cassandra.metrics.DecayingEstimatedHistogramReservoir.DEFAULT_STRIPE_COUNT;
4748
import static org.apache.cassandra.metrics.DecayingEstimatedHistogramReservoir.MAX_BUCKET_COUNT;
4849
import static org.junit.Assert.assertEquals;
4950
import static org.junit.Assert.assertFalse;
@@ -77,7 +78,13 @@ private static Gen<long[]> generateOffsets()
7778
{
7879
return integers().from(DecayingEstimatedHistogramReservoir.DEFAULT_BUCKET_COUNT)
7980
.upToAndIncluding(DecayingEstimatedHistogramReservoir.MAX_BUCKET_COUNT - 10)
80-
.zip(booleans().all(), EstimatedHistogram::newOffsets);
81+
.zip(booleans().all(), DecayingEstimatedHistogramReservoirTestBase::decayingHistogramOffsets);
82+
}
83+
84+
private static long[] decayingHistogramOffsets(int bucketsCount, boolean considerZeroes)
85+
{
86+
return ((DecayingEstimatedHistogramReservoir.EstimatedHistogramReservoirSnapshot)
87+
new DecayingEstimatedHistogramReservoir(considerZeroes, bucketsCount, DEFAULT_STRIPE_COUNT).getSnapshot()).getOffsets();
8188
}
8289

8390
@Test
@@ -86,6 +93,7 @@ public void testFindIndex()
8693
qt().withExamples(numExamples)
8794
.forAll(booleans().all()
8895
.flatMap(b -> offsets.flatMap(offs -> this.offsetsAndValue(offs, b, 0))))
96+
.describedAs(pair -> String.format("offsets=%s, value=%d", Arrays.toString(pair.left), pair.right))
8997
.check(this::checkFindIndex);
9098
}
9199

@@ -309,7 +317,7 @@ public void testMean()
309317

310318
DecayingEstimatedHistogramReservoir histogram = new DecayingEstimatedHistogramReservoir(true,
311319
DecayingEstimatedHistogramReservoir.DEFAULT_BUCKET_COUNT,
312-
DecayingEstimatedHistogramReservoir.DEFAULT_STRIPE_COUNT,
320+
DEFAULT_STRIPE_COUNT,
313321
clock);
314322
for (int i = 0; i < 40; i++)
315323
histogram.update(0);
@@ -425,7 +433,7 @@ public void testPercentile()
425433

426434
DecayingEstimatedHistogramReservoir histogram = new DecayingEstimatedHistogramReservoir(true,
427435
DecayingEstimatedHistogramReservoir.DEFAULT_BUCKET_COUNT,
428-
DecayingEstimatedHistogramReservoir.DEFAULT_STRIPE_COUNT,
436+
DEFAULT_STRIPE_COUNT,
429437
clock);
430438
histogram.update(0);
431439
histogram.update(0);

test/unit/org/apache/cassandra/utils/DSEEstimatedHistogramTest.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,36 @@ public static void setup()
3939
@Test
4040
public void testDSEBoundaries()
4141
{
42-
// these boundaries were computed in DSE
43-
long[] dseBoundaries = new long[]{ 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40,
44-
48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792,
45-
2048, 2560, 3072, 3584, 4096, 5120, 6144, 7168, 8192, 10240, 12288, 14336, 16384, 20480, 24576, 28672, 32768,
46-
40960, 49152, 57344, 65536, 81920, 98304, 114688, 131072, 163840, 196608, 229376, 262144, 327680, 393216,
47-
458752, 524288, 655360, 786432, 917504, 1048576, 1310720, 1572864, 1835008, 2097152, 2621440, 3145728, 3670016,
48-
4194304, 5242880, 6291456, 7340032, 8388608, 10485760, 12582912, 14680064, 16777216, 20971520, 25165824,
49-
29360128, 33554432, 41943040, 50331648, 58720256, 67108864, 83886080, 100663296, 117440512, 134217728,
50-
167772160, 201326592, 234881024, 268435456, 335544320, 402653184, 469762048, 536870912, 671088640, 805306368,
51-
939524096, 1073741824, 1342177280, 1610612736, 1879048192, 2147483648L, 2684354560L, 3221225472L, 3758096384L,
52-
4294967296L, 5368709120L, 6442450944L, 7516192768L, 8589934592L, 10737418240L, 12884901888L, 15032385536L, 17179869184L,
53-
21474836480L, 25769803776L, 30064771072L, 34359738368L, 42949672960L, 51539607552L, 60129542144L, 68719476736L,
54-
85899345920L, 103079215104L, 120259084288L, 137438953472L, 171798691840L, 206158430208L, 240518168576L, 274877906944L,
55-
343597383680L, 412316860416L, 481036337152L, 549755813888L, 687194767360L, 824633720832L, 962072674304L, 1099511627776L,
56-
1374389534720L, 1649267441664L, 1924145348608L, 2199023255552L, 2748779069440L, 3298534883328L, 3848290697216L,
57-
4398046511104L, 5497558138880L, 6597069766656L, 7696581394432L, 8796093022208L, 10995116277760L, 13194139533312L,
58-
15393162788864L, 17592186044416L, 21990232555520L, 26388279066624L, 30786325577728L, 35184372088832L, 43980465111040L,
59-
52776558133248L, 61572651155456L, 70368744177664L, 87960930222080L, 105553116266496L, 123145302310912L,
60-
140737488355328L, 175921860444160L };
42+
// these boundaries were computed in DSE, in HistogramSnapshotTest::boundaries
43+
long[] dseBoundaries = new long[]{ 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 17, 20, 24, 29, 35, 42, 50, 60, 72, 86, 103, 124,
44+
149, 179, 215, 258, 310, 372, 446, 535, 642, 770, 924, 1109, 1331, 1597, 1916, 2299, 2759, 3311, 3973, 4768,
45+
5722, 6866, 8239, 9887, 11864, 14237, 17084, 20501, 24601, 29521, 35425, 42510, 51012, 61214, 73457, 88148,
46+
105778, 126934, 152321, 182785, 219342, 263210, 315852, 379022, 454826, 545791, 654949, 785939, 943127,
47+
1131752, 1358102, 1629722, 1955666, 2346799, 2816159, 3379391, 4055269, 4866323, 5839588, 7007506, 8409007,
48+
10090808, 12108970, 14530764, 17436917, 20924300, 25109160, 30130992, 36157190, 43388628, 52066354, 62479625,
49+
74975550, 89970660, 107964792, 129557750, 155469300, 186563160, 223875792, 268650950, 322381140, 386857368,
50+
464228842, 557074610, 668489532, 802187438, 962624926, 1155149911, 1386179893, 1663415872, 1996099046,
51+
2395318855L, 2874382626L, 3449259151L, 4139110981L, 4966933177L, 5960319812L, 7152383774L, 8582860529L,
52+
10299432635L, 12359319162L, 14831182994L, 17797419593L, 21356903512L, 25628284214L, 30753941057L,
53+
36904729268L, 44285675122L, 53142810146L, 63771372175L, 76525646610L, 91830775932L, 110196931118L,
54+
132236317342L, 158683580810L, 190420296972L, 228504356366L, 274205227639L, 329046273167L, 394855527800L,
55+
473826633360L, 568591960032L, 682310352038L, 818772422446L, 982526906935L, 1179032288322L, 1414838745986L,
56+
1697806495183L, 2037367794220L, 2444841353064L, 2933809623677L, 3520571548412L, 4224685858094L,
57+
5069623029713L, 6083547635656L, 7300257162787L, 8760308595344L, 10512370314413L, 12614844377296L,
58+
15137813252755L, 18165375903306L, 21798451083967L, 26158141300760L, 31389769560912L, 37667723473094L,
59+
45201268167713L, 54241521801256L, 65089826161507L, 78107791393808L, 93729349672570L, 112475219607084L,
60+
134970263528501L, 161964316234201L, 194357179481041L, 233228615377249L, 279874338452699L, 335849206143239L,
61+
403019047371887L, 483622856846264L, 580347428215517L, 696416913858620L, 835700296630344L, 1002840355956413L,
62+
1203408427147696L, 1444090112577235L, 1732908135092682L, 2079489762111218L, 2495387714533462L,
63+
2994465257440155L, 3593358308928186L, 4312029970713823L, 5174435964856587L, 6209323157827904L,
64+
7451187789393485L, 8941425347272182L, 10729710416726618L, 12875652500071942L, 15450783000086330L,
65+
18540939600103596L, 22249127520124316L, 26698953024149180L, 32038743628979016L, 38446492354774816L,
66+
46135790825729776L, 55362948990875728L, 66435538789050872L, 79722646546861040L, 95667175856233248L,
67+
114800611027479888L, 137760733232975856L, 165312879879571008L, 198375455855485216L, 238050547026582240L,
68+
285660656431898688L, 342792787718278400L, 411351345261934080L, 493621614314320896L, 592345937177185024L,
69+
710815124612621952L, 852978149535146368L, 1023573779442175616L, 1228288535330610688L, 1473946242396732672L,
70+
1768735490876079104L, 2122482589051294720L, 2546979106861553664L, 3056374928233864192L, 3667649913880636928L,
71+
4401179896656763904L, 5281415875988116480L, 6337699051185739776L, 7605238861422887936L, 9126286633707464704L };
6172

6273
// the code below is O(n^2) so that we don't need to assume that boundaries are independent
6374
// of the histogram size; this is not a problem since the number of boundaries is small

0 commit comments

Comments
 (0)