This is an automated email from the ASF dual-hosted git repository.

gianm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new fc33de4b499 fix: LimitedBufferHashGrouper heapIndexUpdater used 
incorrect table. (#20861)
fc33de4b499 is described below

commit fc33de4b499b100ac26b9b2478669c4c7472322f
Author: Gian Merlino <[email protected]>
AuthorDate: Fri Nov 6 14:15:48 2026 -0800

    fix: LimitedBufferHashGrouper heapIndexUpdater used incorrect table. 
(#20861)
    
    When LimitedBufferHashGrouper swaps hash tables, it needs to also
    update the hash table buffer reference in the heapIndexUpdater.
    Failing to do so means that the pointers from the hash table to the
    heap are not properly updated, which could cause rows to be skipped
    during aggregation.
---
 .../epinephelinae/LimitedBufferHashGrouper.java    |  1 +
 .../LimitedBufferHashGrouperTest.java              | 54 ++++++++++++++++++++++
 2 files changed, 55 insertions(+)

diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/LimitedBufferHashGrouper.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/LimitedBufferHashGrouper.java
index 167b21ee77c..737b0cafed0 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/LimitedBufferHashGrouper.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/LimitedBufferHashGrouper.java
@@ -585,6 +585,7 @@ public class LimitedBufferHashGrouper<KeyType> extends 
AbstractBufferHashGrouper
       size = numCopied;
       updateMaxMergeBufferUsedBytes();
       tableBuffer = newTableBuffer;
+      heapIndexUpdater.setHashTableBuffer(newTableBuffer);
       growthCount++;
     }
 
diff --git 
a/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/LimitedBufferHashGrouperTest.java
 
b/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/LimitedBufferHashGrouperTest.java
index aa3884cbcce..34ef1753cf1 100644
--- 
a/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/LimitedBufferHashGrouperTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/LimitedBufferHashGrouperTest.java
@@ -43,8 +43,10 @@ import org.junit.jupiter.api.Test;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Random;
 
 public class LimitedBufferHashGrouperTest extends InitializedNullHandlingTest
 {
@@ -450,6 +452,58 @@ public class LimitedBufferHashGrouperTest extends 
InitializedNullHandlingTest
     grouper.close();
   }
 
+  @Test
+  public void testRepeatedKeysAcrossBufferSwaps()
+  {
+    for (final byte fill : new byte[]{0, (byte) 0xFF, 0x7F}) {
+      final GroupByTestColumnSelectorFactory columnSelectorFactory = 
GrouperTestUtil.newColumnSelectorFactory();
+      final ByteBuffer buffer = ByteBuffer.allocate(20000);
+      for (int i = 0; i < buffer.capacity(); i++) {
+        buffer.put(i, fill);
+      }
+      final LimitedBufferHashGrouper<IntKey> grouper = new 
LimitedBufferHashGrouper<>(
+          Suppliers.ofInstance(buffer),
+          GrouperTestUtil.intKeySerde(),
+          AggregatorAdapters.factorizeBuffered(
+              columnSelectorFactory,
+              ImmutableList.of(
+                  new LongSumAggregatorFactory("valueSum", "value"),
+                  new CountAggregatorFactory("count")
+              )
+          ),
+          Integer.MAX_VALUE,
+          0.5f,
+          2,
+          LIMIT,
+          false
+      );
+      grouper.init();
+
+      // Shuffle so that keys recur after buffer swaps, which is when the 
stale heap index matters.
+      final int numKeys = 1000;
+      final int repeats = 10;
+      final List<Integer> rows = new ArrayList<>();
+      for (int r = 0; r < repeats; r++) {
+        for (int k = 0; k < numKeys; k++) {
+          rows.add(k);
+        }
+      }
+      Collections.shuffle(rows, new Random(0));
+
+      columnSelectorFactory.setRow(new MapBasedRow(0, ImmutableMap.of("value", 
1L)));
+      for (final int k : rows) {
+        Assertions.assertTrue(grouper.aggregate(new IntKey(k)).isOk());
+      }
+      Assertions.assertTrue(grouper.getGrowthCount() > 1, "growthCount " + 
grouper.getGrowthCount());
+
+      final List<Pair<Integer, List<Object>>> expected = new ArrayList<>();
+      for (int i = 0; i < LIMIT; i++) {
+        expected.add(Pair.of(i, ImmutableList.of((long) repeats, (long) 
repeats)));
+      }
+      Assertions.assertEquals(expected, entriesToList(grouper.iterator(true)), 
"fill " + fill);
+    }
+  }
+
   private static LimitedBufferHashGrouper<IntKey> makeGrouper(
       GroupByTestColumnSelectorFactory columnSelectorFactory,
       int bufferSize


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to