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

gian 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 875e069  GroupBy: Cap dictionary-building selector memory usage. 
(#12309)
875e069 is described below

commit 875e0696e01c4348fa31c77ec6fa333a324a53d8
Author: Gian Merlino <[email protected]>
AuthorDate: Tue Mar 8 13:13:11 2022 -0800

    GroupBy: Cap dictionary-building selector memory usage. (#12309)
    
    * GroupBy: Cap dictionary-building selector memory usage.
    
    New context parameter "maxSelectorDictionarySize" controls when the
    per-segment processing code should return early and trigger a trip
    to the merge buffer.
    
    Includes:
    
    - Vectorized and nonvectorized implementations.
    - Adjustments to GroupByQueryRunnerTest to exercise this code in
      the v2SmallDictionary suite. (Both the selector dictionary and
      the merging dictionary will be small in that suite.)
    - Tests for the new config parameter.
    
    * Fix issues from tests.
    
    * Add "pre-existing" to dictionary.
    
    * Simplify GroupByColumnSelectorStrategy interface by removing one of the 
writeToKeyBuffer methods.
    
    * Adjustments from review comments.
---
 docs/configuration/index.md                        |  4 +-
 docs/querying/groupbyquery.md                      | 15 +++--
 .../druid/query/groupby/GroupByQueryConfig.java    | 24 ++++++-
 .../groupby/epinephelinae/DictionaryBuilding.java  | 63 +++++++++++++++++++
 .../epinephelinae/GroupByQueryEngineV2.java        | 44 +++++++++++--
 .../epinephelinae/RowBasedGrouperHelper.java       | 36 ++++-------
 .../ArrayDoubleGroupByColumnSelectorStrategy.java  |  6 +-
 .../ArrayLongGroupByColumnSelectorStrategy.java    |  8 +--
 .../ArrayNumericGroupByColumnSelectorStrategy.java | 68 ++++++++++++++------
 .../ArrayStringGroupByColumnSelectorStrategy.java  | 73 ++++++++++++++++------
 ...uildingStringGroupByColumnSelectorStrategy.java | 43 ++++++++-----
 .../DoubleGroupByColumnSelectorStrategy.java       | 31 +++++----
 .../column/FloatGroupByColumnSelectorStrategy.java | 31 +++++----
 .../column/GroupByColumnSelectorStrategy.java      | 52 +++++++++------
 .../column/LongGroupByColumnSelectorStrategy.java  | 31 +++++----
 ...llableNumericGroupByColumnSelectorStrategy.java | 51 +++++++++------
 .../StringGroupByColumnSelectorStrategy.java       | 35 +++++++----
 ...ngleValueStringGroupByVectorColumnSelector.java | 26 +++++---
 .../vector/DoubleGroupByVectorColumnSelector.java  | 10 ++-
 .../vector/FloatGroupByVectorColumnSelector.java   | 10 ++-
 .../vector/GroupByVectorColumnSelector.java        | 20 +++++-
 .../vector/LongGroupByVectorColumnSelector.java    | 10 ++-
 .../vector/NilGroupByVectorColumnSelector.java     |  9 ++-
 .../NullableDoubleGroupByVectorColumnSelector.java | 10 ++-
 .../NullableFloatGroupByVectorColumnSelector.java  | 10 ++-
 .../NullableLongGroupByVectorColumnSelector.java   | 10 ++-
 ...ngleValueStringGroupByVectorColumnSelector.java | 10 ++-
 .../epinephelinae/vector/VectorGroupByEngine.java  | 15 ++++-
 .../query/groupby/GroupByQueryConfigTest.java      | 35 ++++++-----
 .../query/groupby/GroupByQueryRunnerTest.java      |  6 ++
 ...rayDoubleGroupByColumnSelectorStrategyTest.java | 12 ++--
 ...ArrayLongGroupByColumnSelectorStrategyTest.java | 12 ++--
 ...rayStringGroupByColumnSelectorStrategyTest.java |  6 +-
 website/.spelling                                  |  2 +
 34 files changed, 601 insertions(+), 227 deletions(-)

diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index 727876d..d22faae 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -2047,13 +2047,15 @@ Supported runtime properties:
 
 |Property|Description|Default|
 |--------|-----------|-------|
-|`druid.query.groupBy.maxMergingDictionarySize`|Maximum amount of heap space 
(approximately) to use for the string dictionary during merging. When the 
dictionary exceeds this size, a spill to disk will be triggered.|100000000|
+|`druid.query.groupBy.maxSelectorDictionarySize`|Maximum amount of heap space 
(approximately) to use for per-segment string dictionaries. See [groupBy memory 
tuning and resource 
limits](../querying/groupbyquery.md#memory-tuning-and-resource-limits) for 
details.|100000000|
+|`druid.query.groupBy.maxMergingDictionarySize`|Maximum amount of heap space 
(approximately) to use for per-query string dictionaries. When the dictionary 
exceeds this size, a spill to disk will be triggered. See [groupBy memory 
tuning and resource 
limits](../querying/groupbyquery.md#memory-tuning-and-resource-limits) for 
details.|100000000|
 |`druid.query.groupBy.maxOnDiskStorage`|Maximum amount of disk space to use, 
per-query, for spilling result sets to disk when either the merging buffer or 
the dictionary fills up. Queries that exceed this limit will fail. Set to zero 
to disable disk spilling.|0 (disabled)|
 
 Supported query contexts:
 
 |Key|Description|
 |---|-----------|
+|`maxSelectorDictionarySize`|Can be used to lower the value of 
`druid.query.groupBy.maxMergingDictionarySize` for this query.|
 |`maxMergingDictionarySize`|Can be used to lower the value of 
`druid.query.groupBy.maxMergingDictionarySize` for this query.|
 |`maxOnDiskStorage`|Can be used to lower the value of 
`druid.query.groupBy.maxOnDiskStorage` for this query.|
 
diff --git a/docs/querying/groupbyquery.md b/docs/querying/groupbyquery.md
index 5502387..4defd00 100644
--- a/docs/querying/groupbyquery.md
+++ b/docs/querying/groupbyquery.md
@@ -275,13 +275,18 @@ as the index, so the aggregated values in the array can 
be accessed directly wit
 
 ### Memory tuning and resource limits
 
-When using groupBy v2, three parameters control resource usage and limits:
+When using groupBy v2, four parameters control resource usage and limits:
 
 - `druid.processing.buffer.sizeBytes`: size of the off-heap hash table used 
for aggregation, per query, in bytes. At
 most `druid.processing.numMergeBuffers` of these will be created at once, 
which also serves as an upper limit on the
 number of concurrently running groupBy queries.
-- `druid.query.groupBy.maxMergingDictionarySize`: size of the on-heap 
dictionary used when grouping on strings, per query,
-in bytes. Note that this is based on a rough estimate of the dictionary size, 
not the actual size.
+- `druid.query.groupBy.maxSelectorDictionarySize`: size of the on-heap 
segment-level dictionary used when grouping on
+string or array-valued expressions that do not have pre-existing dictionaries. 
There is at most one dictionary per
+processing thread; therefore there are up to `druid.processing.numThreads` of 
these. Note that the size is based on a
+rough estimate of the dictionary footprint.
+- `druid.query.groupBy.maxMergingDictionarySize`: size of the on-heap 
query-level dictionary used when grouping on
+any string expression. There is at most one dictionary per 
concurrently-running query; therefore there are up to
+`druid.server.http.numThreads` of these. Note that the size is based on a 
rough estimate of the dictionary footprint.
 - `druid.query.groupBy.maxOnDiskStorage`: amount of space on disk used for 
aggregation, per query, in bytes. By default,
 this is 0, which means aggregation will not use disk.
 
@@ -381,13 +386,15 @@ Supported runtime properties:
 
 |Property|Description|Default|
 |--------|-----------|-------|
-|`druid.query.groupBy.maxMergingDictionarySize`|Maximum amount of heap space 
(approximately) to use for the string dictionary during merging. When the 
dictionary exceeds this size, a spill to disk will be triggered.|100000000|
+|`druid.query.groupBy.maxSelectorDictionarySize`|Maximum amount of heap space 
(approximately) to use for per-segment string dictionaries. See [Memory tuning 
and resource limits](#memory-tuning-and-resource-limits) for details.|100000000|
+|`druid.query.groupBy.maxMergingDictionarySize`|Maximum amount of heap space 
(approximately) to use for per-query string dictionaries. When the dictionary 
exceeds this size, a spill to disk will be triggered. See [Memory tuning and 
resource limits](#memory-tuning-and-resource-limits) for details.|100000000|
 |`druid.query.groupBy.maxOnDiskStorage`|Maximum amount of disk space to use, 
per-query, for spilling result sets to disk when either the merging buffer or 
the dictionary fills up. Queries that exceed this limit will fail. Set to zero 
to disable disk spilling.|0 (disabled)|
 
 Supported query contexts:
 
 |Key|Description|
 |---|-----------|
+|`maxSelectorDictionarySize`|Can be used to lower the value of 
`druid.query.groupBy.maxMergingDictionarySize` for this query.|
 |`maxMergingDictionarySize`|Can be used to lower the value of 
`druid.query.groupBy.maxMergingDictionarySize` for this query.|
 |`maxOnDiskStorage`|Can be used to lower the value of 
`druid.query.groupBy.maxOnDiskStorage` for this query.|
 
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/GroupByQueryConfig.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/GroupByQueryConfig.java
index 5e32e88..986f4d2 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/GroupByQueryConfig.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/GroupByQueryConfig.java
@@ -24,6 +24,7 @@ import org.apache.druid.query.QueryContexts;
 import org.apache.druid.query.groupby.strategy.GroupByStrategySelector;
 
 /**
+ *
  */
 public class GroupByQueryConfig
 {
@@ -42,6 +43,7 @@ public class GroupByQueryConfig
   private static final String CTX_KEY_BUFFER_GROUPER_MAX_LOAD_FACTOR = 
"bufferGrouperMaxLoadFactor";
   private static final String CTX_KEY_BUFFER_GROUPER_MAX_SIZE = 
"bufferGrouperMaxSize";
   private static final String CTX_KEY_MAX_ON_DISK_STORAGE = "maxOnDiskStorage";
+  private static final String CTX_KEY_MAX_SELECTOR_DICTIONARY_SIZE = 
"maxSelectorDictionarySize";
   private static final String CTX_KEY_MAX_MERGING_DICTIONARY_SIZE = 
"maxMergingDictionarySize";
   private static final String CTX_KEY_FORCE_HASH_AGGREGATION = 
"forceHashAggregation";
   private static final String CTX_KEY_INTERMEDIATE_COMBINE_DEGREE = 
"intermediateCombineDegree";
@@ -70,6 +72,11 @@ public class GroupByQueryConfig
   private int bufferGrouperInitialBuckets = 0;
 
   @JsonProperty
+  // Size of on-heap string dictionary for merging, per-processing-thread; 
when exceeded, partial results will be
+  // emitted to the merge buffer early.
+  private long maxSelectorDictionarySize = 100_000_000L;
+
+  @JsonProperty
   // Size of on-heap string dictionary for merging, per-query; when exceeded, 
partial results will be spilled to disk
   private long maxMergingDictionarySize = 100_000_000L;
 
@@ -151,6 +158,11 @@ public class GroupByQueryConfig
     return bufferGrouperInitialBuckets;
   }
 
+  public long getMaxSelectorDictionarySize()
+  {
+    return maxSelectorDictionarySize;
+  }
+
   public long getMaxMergingDictionarySize()
   {
     return maxMergingDictionarySize;
@@ -230,6 +242,13 @@ public class GroupByQueryConfig
         ((Number) query.getContextValue(CTX_KEY_MAX_ON_DISK_STORAGE, 
getMaxOnDiskStorage())).longValue(),
         getMaxOnDiskStorage()
     );
+    newConfig.maxSelectorDictionarySize = Math.min(
+        ((Number) query.getContextValue(
+            CTX_KEY_MAX_SELECTOR_DICTIONARY_SIZE,
+            getMaxSelectorDictionarySize()
+        )).longValue(),
+        getMaxSelectorDictionarySize()
+    );
     newConfig.maxMergingDictionarySize = Math.min(
         ((Number) query.getContextValue(
             CTX_KEY_MAX_MERGING_DICTIONARY_SIZE,
@@ -243,7 +262,10 @@ public class GroupByQueryConfig
         isApplyLimitPushDownToSegment()
     );
     newConfig.forceHashAggregation = 
query.getContextBoolean(CTX_KEY_FORCE_HASH_AGGREGATION, 
isForceHashAggregation());
-    newConfig.forcePushDownNestedQuery = 
query.getContextBoolean(CTX_KEY_FORCE_PUSH_DOWN_NESTED_QUERY, 
isForcePushDownNestedQuery());
+    newConfig.forcePushDownNestedQuery = query.getContextBoolean(
+        CTX_KEY_FORCE_PUSH_DOWN_NESTED_QUERY,
+        isForcePushDownNestedQuery()
+    );
     newConfig.intermediateCombineDegree = query.getContextValue(
         CTX_KEY_INTERMEDIATE_COMBINE_DEGREE,
         getIntermediateCombineDegree()
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/DictionaryBuilding.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/DictionaryBuilding.java
new file mode 100644
index 0000000..edbe352
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/DictionaryBuilding.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.query.groupby.epinephelinae;
+
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import org.apache.druid.segment.DimensionDictionary;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities for parts of the groupBy engine that need to build dictionaries.
+ */
+public class DictionaryBuilding
+{
+  // Entry in dictionary, node pointer in reverseDictionary, hash + k/v/next 
pointer in reverseDictionary nodes
+  private static final int ROUGH_OVERHEAD_PER_DICTIONARY_ENTRY = Long.BYTES * 
5 + Integer.BYTES;
+
+  /**
+   * Creates a forward dictionary (dictionary ID -> value).
+   */
+  public static <T> List<T> createDictionary()
+  {
+    return new ArrayList<>();
+  }
+
+  /**
+   * Creates a reverse dictionary (value -> dictionary ID). If a value is not 
present in the reverse dictionary,
+   * {@link Object2IntMap#getInt} will return {@link 
DimensionDictionary#ABSENT_VALUE_ID}.
+   */
+  public static <T> Object2IntMap<T> createReverseDictionary()
+  {
+    final Object2IntOpenHashMap<T> m = new Object2IntOpenHashMap<>();
+    m.defaultReturnValue(DimensionDictionary.ABSENT_VALUE_ID);
+    return m;
+  }
+
+  /**
+   * Estimated footprint of a new entry.
+   */
+  public static int estimateEntryFootprint(final int valueFootprint)
+  {
+    return valueFootprint + ROUGH_OVERHEAD_PER_DICTIONARY_ENTRY;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/GroupByQueryEngineV2.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/GroupByQueryEngineV2.java
index f38f6ce..602b2eb 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/GroupByQueryEngineV2.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/GroupByQueryEngineV2.java
@@ -623,7 +623,10 @@ public class GroupByQueryEngineV2
     private final ByteBuffer keyBuffer;
 
     private int stackPointer = Integer.MIN_VALUE;
-    protected boolean currentRowWasPartiallyAggregated = false;
+    private boolean currentRowWasPartiallyAggregated = false;
+
+    // Sum of internal state footprint across all "dims".
+    private long selectorInternalFootprint = 0;
 
     public HashAggregateIterator(
         GroupByQuery query,
@@ -717,12 +720,19 @@ public class GroupByQueryEngineV2
     @Override
     protected void aggregateSingleValueDims(Grouper<ByteBuffer> grouper)
     {
+      if (!currentRowWasPartiallyAggregated) {
+        for (GroupByColumnSelectorPlus dim : dims) {
+          dim.getColumnSelectorStrategy().reset();
+        }
+        selectorInternalFootprint = 0;
+      }
+
       while (!cursor.isDone()) {
         for (GroupByColumnSelectorPlus dim : dims) {
           final GroupByColumnSelectorStrategy strategy = 
dim.getColumnSelectorStrategy();
-          strategy.writeToKeyBuffer(
+          selectorInternalFootprint += strategy.writeToKeyBuffer(
               dim.getKeyBufferPosition(),
-              strategy.getOnlyValue(dim.getSelector()),
+              dim.getSelector(),
               keyBuffer
           );
         }
@@ -731,13 +741,27 @@ public class GroupByQueryEngineV2
         if (!grouper.aggregate(keyBuffer).isOk()) {
           return;
         }
+
         cursor.advance();
+
+        // Check selectorInternalFootprint after advancing the cursor. (We 
reset after the first row that causes
+        // us to go past the limit.)
+        if (selectorInternalFootprint > 
querySpecificConfig.getMaxSelectorDictionarySize()) {
+          return;
+        }
       }
     }
 
     @Override
     protected void aggregateMultiValueDims(Grouper<ByteBuffer> grouper)
     {
+      if (!currentRowWasPartiallyAggregated) {
+        for (GroupByColumnSelectorPlus dim : dims) {
+          dim.getColumnSelectorStrategy().reset();
+        }
+        selectorInternalFootprint = 0;
+      }
+
       while (!cursor.isDone()) {
         if (!currentRowWasPartiallyAggregated) {
           // Set up stack, valuess, and first grouping in keyBuffer for this 
row
@@ -745,7 +769,7 @@ public class GroupByQueryEngineV2
 
           for (int i = 0; i < dims.length; i++) {
             GroupByColumnSelectorStrategy strategy = 
dims[i].getColumnSelectorStrategy();
-            strategy.initColumnValues(
+            selectorInternalFootprint += strategy.initColumnValues(
                 dims[i].getSelector(),
                 i,
                 valuess
@@ -808,6 +832,12 @@ public class GroupByQueryEngineV2
         // Advance to next row
         cursor.advance();
         currentRowWasPartiallyAggregated = false;
+
+        // Check selectorInternalFootprint after advancing the cursor. (We 
reset after the first row that causes
+        // us to go past the limit.)
+        if (selectorInternalFootprint > 
querySpecificConfig.getMaxSelectorDictionarySize()) {
+          return;
+        }
       }
     }
 
@@ -882,6 +912,9 @@ public class GroupByQueryEngineV2
 
     private void aggregateSingleValueDims(IntGrouper grouper)
     {
+      // No need to track strategy internal state footprint, because 
array-based grouping does not use strategies.
+      // It accesses dimension selectors directly and only works on truly 
dictionary-coded columns.
+
       while (!cursor.isDone()) {
         final int key;
         if (dim != null) {
@@ -900,6 +933,9 @@ public class GroupByQueryEngineV2
 
     private void aggregateMultiValueDims(IntGrouper grouper)
     {
+      // No need to track strategy internal state footprint, because 
array-based grouping does not use strategies.
+      // It accesses dimension selectors directly and only works on truly 
dictionary-coded columns.
+
       if (dim == null) {
         throw new ISE("dim must exist");
       }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java
index 1f825f5..9522715 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java
@@ -29,7 +29,6 @@ import com.google.common.primitives.Longs;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import it.unimi.dsi.fastutil.ints.IntArrays;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.apache.druid.collections.ReferenceCountingResourceHolder;
 import org.apache.druid.common.config.NullHandling;
 import org.apache.druid.common.guava.SettableSupplier;
@@ -63,6 +62,7 @@ import org.apache.druid.segment.BaseLongColumnValueSelector;
 import org.apache.druid.segment.ColumnInspector;
 import org.apache.druid.segment.ColumnSelectorFactory;
 import org.apache.druid.segment.ColumnValueSelector;
+import org.apache.druid.segment.DimensionDictionary;
 import org.apache.druid.segment.DimensionHandlerUtils;
 import org.apache.druid.segment.DimensionSelector;
 import org.apache.druid.segment.RowAdapter;
@@ -101,9 +101,6 @@ import java.util.stream.IntStream;
  */
 public class RowBasedGrouperHelper
 {
-  // Entry in dictionary, node pointer in reverseDictionary, hash + k/v/next 
pointer in reverseDictionary nodes
-  private static final int ROUGH_OVERHEAD_PER_DICTIONARY_ENTRY = Long.BYTES * 
5 + Integer.BYTES;
-
   private static final int SINGLE_THREAD_CONCURRENCY_HINT = -1;
   private static final int UNKNOWN_THREAD_PRIORITY = -1;
   private static final long UNKNOWN_TIMEOUT = -1L;
@@ -1144,14 +1141,11 @@ public class RowBasedGrouperHelper
 
   static long estimateStringKeySize(@Nullable String key)
   {
-    long length = key == null ? 0 : key.length();
-    return length * Character.BYTES + ROUGH_OVERHEAD_PER_DICTIONARY_ENTRY;
+    return DictionaryBuilding.estimateEntryFootprint((key == null ? 0 : 
key.length()) * Character.BYTES);
   }
 
   private static class RowBasedKeySerde implements 
Grouper.KeySerde<RowBasedGrouperHelper.RowBasedKey>
   {
-    private static final int UNKNOWN_DICTIONARY_ID = -1;
-
     private final boolean includeTimestamp;
     private final boolean sortByDimsFirst;
     private final List<DimensionSpec> dimensions;
@@ -1203,20 +1197,14 @@ public class RowBasedGrouperHelper
       this.valueTypes = valueTypes;
       this.limitSpec = limitSpec;
       this.enableRuntimeDictionaryGeneration = dictionary == null;
-      this.dictionary = enableRuntimeDictionaryGeneration ? new ArrayList<>() 
: dictionary;
-      this.reverseDictionary = enableRuntimeDictionaryGeneration ?
-                               new Object2IntOpenHashMap<>() :
-                               new Object2IntOpenHashMap<>(dictionary.size());
-
-      this.arrayDictionary = new ArrayList<>();
-      this.reverseArrayDictionary = new Object2IntOpenHashMap<>();
+      this.dictionary = enableRuntimeDictionaryGeneration ? 
DictionaryBuilding.createDictionary() : dictionary;
+      this.reverseDictionary = DictionaryBuilding.createReverseDictionary();
 
-      this.listDictionary = new ArrayList<>();
-      this.reverseListDictionary = new Object2IntOpenHashMap<>();
+      this.arrayDictionary = DictionaryBuilding.createDictionary();
+      this.reverseArrayDictionary = 
DictionaryBuilding.createReverseDictionary();
 
-      this.reverseDictionary.defaultReturnValue(UNKNOWN_DICTIONARY_ID);
-      this.reverseArrayDictionary.defaultReturnValue(UNKNOWN_DICTIONARY_ID);
-      this.reverseListDictionary.defaultReturnValue(UNKNOWN_DICTIONARY_ID);
+      this.listDictionary = DictionaryBuilding.createDictionary();
+      this.reverseListDictionary = 
DictionaryBuilding.createReverseDictionary();
 
       this.maxDictionarySize = maxDictionarySize;
       this.serdeHelpers = makeSerdeHelpers(limitSpec != null, 
enableRuntimeDictionaryGeneration);
@@ -1534,7 +1522,7 @@ public class RowBasedGrouperHelper
       {
         final ComparableList comparableList = (ComparableList) 
key.getKey()[idx];
         int id = reverseDictionary.getInt(comparableList);
-        if (id == UNKNOWN_DICTIONARY_ID) {
+        if (id == DimensionDictionary.ABSENT_VALUE_ID) {
           id = listDictionary.size();
           reverseListDictionary.put(comparableList, id);
           listDictionary.add(comparableList);
@@ -1610,7 +1598,7 @@ public class RowBasedGrouperHelper
       private int addToArrayDictionary(final ComparableStringArray s)
       {
         int idx = reverseArrayDictionary.getInt(s);
-        if (idx == UNKNOWN_DICTIONARY_ID) {
+        if (idx == DimensionDictionary.ABSENT_VALUE_ID) {
           idx = arrayDictionary.size();
           reverseArrayDictionary.put(s, idx);
           arrayDictionary.add(s);
@@ -1700,7 +1688,7 @@ public class RowBasedGrouperHelper
       private int addToDictionary(final String s)
       {
         int idx = reverseDictionary.getInt(s);
-        if (idx == UNKNOWN_DICTIONARY_ID) {
+        if (idx == DimensionDictionary.ABSENT_VALUE_ID) {
           final long additionalEstimatedSize = estimateStringKeySize(s);
           if (currentEstimatedSize + additionalEstimatedSize > 
maxDictionarySize) {
             return -1;
@@ -1732,7 +1720,7 @@ public class RowBasedGrouperHelper
         final String stringKey = (String) key.getKey()[idx];
 
         final int dictIndex = reverseDictionary.getInt(stringKey);
-        if (dictIndex == UNKNOWN_DICTIONARY_ID) {
+        if (dictIndex == DimensionDictionary.ABSENT_VALUE_ID) {
           throw new ISE("Cannot find key[%s] from dictionary", stringKey);
         }
         keyBuffer.putInt(dictIndex);
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayDoubleGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayDoubleGroupByColumnSelectorStrategy.java
index 46cfe0d..6c56406 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayDoubleGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayDoubleGroupByColumnSelectorStrategy.java
@@ -33,7 +33,7 @@ public class ArrayDoubleGroupByColumnSelectorStrategy extends 
ArrayNumericGroupB
 {
   public ArrayDoubleGroupByColumnSelectorStrategy()
   {
-
+    super(Double.BYTES);
   }
 
   @VisibleForTesting
@@ -42,11 +42,11 @@ public class ArrayDoubleGroupByColumnSelectorStrategy 
extends ArrayNumericGroupB
       Object2IntOpenHashMap<List<Double>> reverseDictionary
   )
   {
-    super(dictionary, reverseDictionary);
+    super(dictionary, reverseDictionary, Double.BYTES);
   }
 
   @Override
-  public Object getOnlyValue(ColumnValueSelector selector)
+  protected int computeDictionaryId(ColumnValueSelector selector)
   {
     Object object = selector.getObject();
     if (object == null) {
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayLongGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayLongGroupByColumnSelectorStrategy.java
index ff137c6..23b4ceb 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayLongGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayLongGroupByColumnSelectorStrategy.java
@@ -31,10 +31,9 @@ import java.util.stream.Collectors;
 
 public class ArrayLongGroupByColumnSelectorStrategy extends 
ArrayNumericGroupByColumnSelectorStrategy<Long>
 {
-
   public ArrayLongGroupByColumnSelectorStrategy()
   {
-
+    super(Long.BYTES);
   }
 
   @VisibleForTesting
@@ -43,12 +42,11 @@ public class ArrayLongGroupByColumnSelectorStrategy extends 
ArrayNumericGroupByC
       Object2IntOpenHashMap<List<Long>> reverseDictionary
   )
   {
-    super(dictionary, reverseDictionary);
+    super(dictionary, reverseDictionary, Long.BYTES);
   }
 
-
   @Override
-  public Object getOnlyValue(ColumnValueSelector selector)
+  protected int computeDictionaryId(ColumnValueSelector selector)
   {
     Object object = selector.getObject();
     if (object == null) {
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayNumericGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayNumericGroupByColumnSelectorStrategy.java
index 55dc29f..dfaac78 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayNumericGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayNumericGroupByColumnSelectorStrategy.java
@@ -20,8 +20,10 @@
 package org.apache.druid.query.groupby.epinephelinae.column;
 
 import com.google.common.annotations.VisibleForTesting;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.apache.druid.query.groupby.ResultRow;
+import org.apache.druid.query.groupby.epinephelinae.DictionaryBuilding;
 import org.apache.druid.query.groupby.epinephelinae.Grouper;
 import org.apache.druid.query.ordering.StringComparator;
 import org.apache.druid.query.ordering.StringComparators;
@@ -30,31 +32,36 @@ import org.apache.druid.segment.data.ComparableList;
 
 import javax.annotation.Nullable;
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
 import java.util.List;
 
-public abstract class ArrayNumericGroupByColumnSelectorStrategy<T extends 
Comparable> implements GroupByColumnSelectorStrategy
+public abstract class ArrayNumericGroupByColumnSelectorStrategy<T extends 
Comparable>
+    implements GroupByColumnSelectorStrategy
 {
   protected static final int GROUP_BY_MISSING_VALUE = -1;
 
   protected final List<List<T>> dictionary;
-  protected final Object2IntOpenHashMap<List<T>> reverseDictionary;
+  protected final Object2IntMap<List<T>> reverseDictionary;
+  protected long estimatedFootprint = 0L;
 
-  public ArrayNumericGroupByColumnSelectorStrategy()
+  private final int valueFootprint;
+
+  public ArrayNumericGroupByColumnSelectorStrategy(final int valueFootprint)
   {
-    dictionary = new ArrayList<>();
-    reverseDictionary = new Object2IntOpenHashMap<>();
-    reverseDictionary.defaultReturnValue(-1);
+    this.dictionary = DictionaryBuilding.createDictionary();
+    this.reverseDictionary = DictionaryBuilding.createReverseDictionary();
+    this.valueFootprint = valueFootprint;
   }
 
   @VisibleForTesting
   ArrayNumericGroupByColumnSelectorStrategy(
       List<List<T>> dictionary,
-      Object2IntOpenHashMap<List<T>> reverseDictionary
+      Object2IntOpenHashMap<List<T>> reverseDictionary,
+      int valueFootprint
   )
   {
     this.dictionary = dictionary;
     this.reverseDictionary = reverseDictionary;
+    this.valueFootprint = valueFootprint;
   }
 
   @Override
@@ -83,16 +90,17 @@ public abstract class 
ArrayNumericGroupByColumnSelectorStrategy<T extends Compar
   }
 
   @Override
-  public void initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
+  public int initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
   {
-    final int groupingKey = (int) getOnlyValue(selector);
-    valuess[columnIndex] = groupingKey;
+    final long priorFootprint = estimatedFootprint;
+    valuess[columnIndex] = computeDictionaryId(selector);
+    return (int) (estimatedFootprint - priorFootprint);
   }
 
   @Override
   public void initGroupingKeyColumnValue(
       int keyBufferPosition,
-      int columnIndex,
+      int dimensionIndex,
       Object rowObj,
       ByteBuffer keyBuffer,
       int[] stack
@@ -101,9 +109,9 @@ public abstract class 
ArrayNumericGroupByColumnSelectorStrategy<T extends Compar
     final int groupingKey = (int) rowObj;
     writeToKeyBuffer(keyBufferPosition, groupingKey, keyBuffer);
     if (groupingKey == GROUP_BY_MISSING_VALUE) {
-      stack[columnIndex] = 0;
+      stack[dimensionIndex] = 0;
     } else {
-      stack[columnIndex] = 1;
+      stack[dimensionIndex] = 1;
     }
 
   }
@@ -119,23 +127,29 @@ public abstract class 
ArrayNumericGroupByColumnSelectorStrategy<T extends Compar
     return false;
   }
 
-  @Override
-  public abstract Object getOnlyValue(ColumnValueSelector selector);
-
+  protected abstract int computeDictionaryId(ColumnValueSelector selector);
 
   @Override
-  public void writeToKeyBuffer(int keyBufferPosition, Object obj, ByteBuffer 
keyBuffer)
+  public int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector 
selector, ByteBuffer keyBuffer)
   {
-    keyBuffer.putInt(keyBufferPosition, (int) obj);
+    final long priorFootprint = estimatedFootprint;
+
+    // computeDictionaryId updates estimatedFootprint
+    keyBuffer.putInt(keyBufferPosition, computeDictionaryId(selector));
+
+    return (int) (estimatedFootprint - priorFootprint);
   }
 
-  int addToIndexedDictionary(List<T> t)
+  protected int addToIndexedDictionary(List<T> t)
   {
     final int dictId = reverseDictionary.getInt(t);
     if (dictId < 0) {
       final int size = dictionary.size();
       dictionary.add(t);
       reverseDictionary.put(t, size);
+
+      // Footprint estimate: one pointer, one value per list entry.
+      estimatedFootprint += DictionaryBuilding.estimateEntryFootprint(t.size() 
* (Long.BYTES + valueFootprint));
       return size;
     }
     return dictId;
@@ -178,4 +192,18 @@ public abstract class 
ArrayNumericGroupByColumnSelectorStrategy<T extends Compar
       }
     };
   }
+
+  @Override
+  public void reset()
+  {
+    dictionary.clear();
+    reverseDictionary.clear();
+    estimatedFootprint = 0;
+  }
+
+  @VisibleForTesting
+  void writeToKeyBuffer(int keyBufferPosition, int groupingKey, ByteBuffer 
keyBuffer)
+  {
+    keyBuffer.putInt(keyBufferPosition, groupingKey);
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayStringGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayStringGroupByColumnSelectorStrategy.java
index 3d42d5a..604df27 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayStringGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayStringGroupByColumnSelectorStrategy.java
@@ -24,6 +24,7 @@ import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import org.apache.druid.java.util.common.ISE;
 import org.apache.druid.query.groupby.ResultRow;
+import org.apache.druid.query.groupby.epinephelinae.DictionaryBuilding;
 import org.apache.druid.query.groupby.epinephelinae.Grouper;
 import org.apache.druid.query.ordering.StringComparator;
 import org.apache.druid.query.ordering.StringComparators;
@@ -35,8 +36,7 @@ import javax.annotation.Nullable;
 import java.nio.ByteBuffer;
 import java.util.List;
 
-public class ArrayStringGroupByColumnSelectorStrategy
-    implements GroupByColumnSelectorStrategy
+public class ArrayStringGroupByColumnSelectorStrategy implements 
GroupByColumnSelectorStrategy
 {
   private static final int GROUP_BY_MISSING_VALUE = -1;
 
@@ -50,6 +50,8 @@ public class ArrayStringGroupByColumnSelectorStrategy
   // [1,2,3] <-> 1
   private final BiMap<ComparableIntArray, Integer> intListToInt;
 
+  private long estimatedFootprint = 0L;
+
   @Override
   public int getGroupingKeySize()
   {
@@ -98,20 +100,22 @@ public class ArrayStringGroupByColumnSelectorStrategy
   }
 
   @Override
-  public void initColumnValues(
+  public int initColumnValues(
       ColumnValueSelector selector,
       int columnIndex,
       Object[] valuess
   )
   {
-    final int groupingKey = (int) getOnlyValue(selector);
+    final long priorFootprint = estimatedFootprint;
+    final int groupingKey = computeDictionaryId(selector);
     valuess[columnIndex] = groupingKey;
+    return (int) (estimatedFootprint - priorFootprint);
   }
 
   @Override
   public void initGroupingKeyColumnValue(
       int keyBufferPosition,
-      int columnIndex,
+      int dimensionIndex,
       Object rowObj,
       ByteBuffer keyBuffer,
       int[] stack
@@ -120,9 +124,9 @@ public class ArrayStringGroupByColumnSelectorStrategy
     final int groupingKey = (int) rowObj;
     writeToKeyBuffer(keyBufferPosition, groupingKey, keyBuffer);
     if (groupingKey == GROUP_BY_MISSING_VALUE) {
-      stack[columnIndex] = 0;
+      stack[dimensionIndex] = 0;
     } else {
-      stack[columnIndex] = 1;
+      stack[dimensionIndex] = 1;
     }
   }
 
@@ -137,8 +141,11 @@ public class ArrayStringGroupByColumnSelectorStrategy
     return false;
   }
 
-  @Override
-  public Object getOnlyValue(ColumnValueSelector selector)
+  /**
+   * Compute dictionary ID for the given selector. Updates {@link 
#estimatedFootprint} as necessary.
+   */
+  @VisibleForTesting
+  int computeDictionaryId(ColumnValueSelector selector)
   {
     final int[] intRepresentation;
     Object object = selector.getObject();
@@ -172,9 +179,15 @@ public class ArrayStringGroupByColumnSelectorStrategy
     final ComparableIntArray comparableIntArray = 
ComparableIntArray.of(intRepresentation);
     final int dictId = intListToInt.getOrDefault(comparableIntArray, 
GROUP_BY_MISSING_VALUE);
     if (dictId == GROUP_BY_MISSING_VALUE) {
-      final int dictionarySize = intListToInt.keySet().size();
-      intListToInt.put(comparableIntArray, dictionarySize);
-      return dictionarySize;
+      final int nextId = intListToInt.keySet().size();
+      intListToInt.put(comparableIntArray, nextId);
+
+      // We're not using the dictionary and reverseDictionary from 
DictionaryBuilding, but the BiMap is close enough
+      // that we expect this footprint calculation to still be useful. (It 
doesn't have to be exact.)
+      estimatedFootprint +=
+          
DictionaryBuilding.estimateEntryFootprint(comparableIntArray.getDelegate().length
 * Integer.BYTES);
+
+      return nextId;
     } else {
       return dictId;
     }
@@ -184,18 +197,29 @@ public class ArrayStringGroupByColumnSelectorStrategy
   {
     final Integer dictId = dictionaryToInt.get(value);
     if (dictId == null) {
-      final int size = dictionaryToInt.size();
-      dictionaryToInt.put(value, dictionaryToInt.size());
-      return size;
+      final int nextId = dictionaryToInt.size();
+      dictionaryToInt.put(value, nextId);
+
+      // We're not using the dictionary and reverseDictionary from 
DictionaryBuilding, but the BiMap is close enough
+      // that we expect this footprint calculation to still be useful. (It 
doesn't have to be exact.)
+      estimatedFootprint +=
+          DictionaryBuilding.estimateEntryFootprint((value == null ? 0 : 
value.length()) * Character.BYTES);
+
+      return nextId;
     } else {
       return dictId;
     }
   }
 
   @Override
-  public void writeToKeyBuffer(int keyBufferPosition, Object obj, ByteBuffer 
keyBuffer)
+  public int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector 
selector, ByteBuffer keyBuffer)
   {
-    keyBuffer.putInt(keyBufferPosition, (int) obj);
+    final long priorFootprint = estimatedFootprint;
+
+    // computeDictionaryId updates estimatedFootprint
+    keyBuffer.putInt(keyBufferPosition, computeDictionaryId(selector));
+
+    return (int) (estimatedFootprint - priorFootprint);
   }
 
   @Override
@@ -230,5 +254,18 @@ public class ArrayStringGroupByColumnSelectorStrategy
       }
     };
   }
-}
 
+  @Override
+  public void reset()
+  {
+    dictionaryToInt.clear();
+    intListToInt.clear();
+    estimatedFootprint = 0;
+  }
+
+  @VisibleForTesting
+  void writeToKeyBuffer(int keyBufferPosition, int groupingKey, ByteBuffer 
keyBuffer)
+  {
+    keyBuffer.putInt(keyBufferPosition, groupingKey);
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/DictionaryBuildingStringGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/DictionaryBuildingStringGroupByColumnSelectorStrategy.java
index 025819c..dfc5149 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/DictionaryBuildingStringGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/DictionaryBuildingStringGroupByColumnSelectorStrategy.java
@@ -20,20 +20,21 @@
 package org.apache.druid.query.groupby.epinephelinae.column;
 
 import com.google.common.base.Preconditions;
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import org.apache.druid.common.config.NullHandling;
 import org.apache.druid.query.groupby.ResultRow;
+import org.apache.druid.query.groupby.epinephelinae.DictionaryBuilding;
 import org.apache.druid.query.groupby.epinephelinae.Grouper;
 import org.apache.druid.query.ordering.StringComparator;
 import org.apache.druid.query.ordering.StringComparators;
 import org.apache.druid.segment.ColumnValueSelector;
+import org.apache.druid.segment.DimensionDictionary;
 import org.apache.druid.segment.DimensionSelector;
 import org.apache.druid.segment.data.ArrayBasedIndexedInts;
 import org.apache.druid.segment.data.IndexedInts;
 
 import javax.annotation.Nullable;
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -44,13 +45,8 @@ public class 
DictionaryBuildingStringGroupByColumnSelectorStrategy extends Strin
 {
   private static final int GROUP_BY_MISSING_VALUE = -1;
 
-  private int nextId = 0;
-  private final List<String> dictionary = new ArrayList<>();
-  private final Object2IntOpenHashMap<String> reverseDictionary = new 
Object2IntOpenHashMap<>();
-
-  {
-    reverseDictionary.defaultReturnValue(-1);
-  }
+  private final List<String> dictionary = 
DictionaryBuilding.createDictionary();
+  private final Object2IntMap<String> reverseDictionary = 
DictionaryBuilding.createReverseDictionary();
 
   public DictionaryBuildingStringGroupByColumnSelectorStrategy()
   {
@@ -77,10 +73,11 @@ public class 
DictionaryBuildingStringGroupByColumnSelectorStrategy extends Strin
   }
 
   @Override
-  public void initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
+  public int initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
   {
     final DimensionSelector dimSelector = (DimensionSelector) selector;
     final IndexedInts row = dimSelector.getRow();
+    int stateFootprintIncrease = 0;
     ArrayBasedIndexedInts newRow = (ArrayBasedIndexedInts) 
valuess[columnIndex];
     if (newRow == null) {
       newRow = new ArrayBasedIndexedInts();
@@ -92,19 +89,22 @@ public class 
DictionaryBuildingStringGroupByColumnSelectorStrategy extends Strin
       final String value = dimSelector.lookupName(row.get(i));
       final int dictId = reverseDictionary.getInt(value);
       if (dictId < 0) {
+        final int nextId = dictionary.size();
         dictionary.add(value);
         reverseDictionary.put(value, nextId);
         newRow.setValue(i, nextId);
-        nextId++;
+        stateFootprintIncrease +=
+            DictionaryBuilding.estimateEntryFootprint((value == null ? 0 : 
value.length()) * Character.BYTES);
       } else {
         newRow.setValue(i, dictId);
       }
     }
     newRow.setSize(rowSize);
+    return stateFootprintIncrease;
   }
 
   @Override
-  public Object getOnlyValue(ColumnValueSelector selector)
+  public int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector 
selector, ByteBuffer keyBuffer)
   {
     final DimensionSelector dimSelector = (DimensionSelector) selector;
     final IndexedInts row = dimSelector.getRow();
@@ -112,17 +112,21 @@ public class 
DictionaryBuildingStringGroupByColumnSelectorStrategy extends Strin
     Preconditions.checkState(row.size() < 2, "Not supported for multi-value 
dimensions");
 
     if (row.size() == 0) {
-      return GROUP_BY_MISSING_VALUE;
+      writeToKeyBuffer(keyBufferPosition, GROUP_BY_MISSING_VALUE, keyBuffer);
+      return 0;
     }
 
     final String value = dimSelector.lookupName(row.get(0));
     final int dictId = reverseDictionary.getInt(value);
-    if (dictId < 0) {
+    if (dictId == DimensionDictionary.ABSENT_VALUE_ID) {
+      final int nextId = dictionary.size();
       dictionary.add(value);
       reverseDictionary.put(value, nextId);
-      return nextId++;
+      writeToKeyBuffer(keyBufferPosition, nextId, keyBuffer);
+      return DictionaryBuilding.estimateEntryFootprint((value == null ? 0 : 
value.length()) * Character.BYTES);
     } else {
-      return dictId;
+      writeToKeyBuffer(keyBufferPosition, dictId, keyBuffer);
+      return 0;
     }
   }
 
@@ -138,4 +142,11 @@ public class 
DictionaryBuildingStringGroupByColumnSelectorStrategy extends Strin
       return realComparator.compare(lhsStr, rhsStr);
     };
   }
+
+  @Override
+  public void reset()
+  {
+    dictionary.clear();
+    reverseDictionary.clear();
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/DoubleGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/DoubleGroupByColumnSelectorStrategy.java
index 86522b5..6fcbf9c 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/DoubleGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/DoubleGroupByColumnSelectorStrategy.java
@@ -50,34 +50,30 @@ public class DoubleGroupByColumnSelectorStrategy implements 
GroupByColumnSelecto
   }
 
   @Override
-  public void initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] values)
+  public int initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] values)
   {
     values[columnIndex] = selector.getDouble();
+    return 0;
   }
 
   @Override
-  public Object getOnlyValue(ColumnValueSelector selector)
+  public int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector 
selector, ByteBuffer keyBuffer)
   {
-    return selector.getDouble();
-  }
-
-  @Override
-  public void writeToKeyBuffer(int keyBufferPosition, @Nullable Object obj, 
ByteBuffer keyBuffer)
-  {
-    keyBuffer.putDouble(keyBufferPosition, 
DimensionHandlerUtils.nullToZero((Double) obj));
+    keyBuffer.putDouble(keyBufferPosition, selector.getDouble());
+    return 0;
   }
 
   @Override
   public void initGroupingKeyColumnValue(
       int keyBufferPosition,
-      int columnIndex,
+      int dimensionIndex,
       Object rowObj,
       ByteBuffer keyBuffer,
       int[] stack
   )
   {
-    writeToKeyBuffer(keyBufferPosition, rowObj, keyBuffer);
-    stack[columnIndex] = 1;
+    writeToKeyBuffer(keyBufferPosition, 
DimensionHandlerUtils.nullToZero((Double) rowObj), keyBuffer);
+    stack[dimensionIndex] = 1;
   }
 
   @Override
@@ -102,4 +98,15 @@ public class DoubleGroupByColumnSelectorStrategy implements 
GroupByColumnSelecto
         stringComparator
     );
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
+
+  private void writeToKeyBuffer(int keyBufferPosition, double value, 
ByteBuffer keyBuffer)
+  {
+    keyBuffer.putDouble(keyBufferPosition, value);
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/FloatGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/FloatGroupByColumnSelectorStrategy.java
index 49fc1fb..a01c3c3 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/FloatGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/FloatGroupByColumnSelectorStrategy.java
@@ -51,21 +51,17 @@ public class FloatGroupByColumnSelectorStrategy implements 
GroupByColumnSelector
   }
 
   @Override
-  public void initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
+  public int initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
   {
     valuess[columnIndex] = selector.getFloat();
+    return 0;
   }
 
   @Override
-  public Object getOnlyValue(ColumnValueSelector selector)
+  public int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector 
selector, ByteBuffer keyBuffer)
   {
-    return selector.getFloat();
-  }
-
-  @Override
-  public void writeToKeyBuffer(int keyBufferPosition, @Nullable Object obj, 
ByteBuffer keyBuffer)
-  {
-    keyBuffer.putFloat(keyBufferPosition, 
DimensionHandlerUtils.nullToZero((Float) obj));
+    keyBuffer.putFloat(keyBufferPosition, selector.getFloat());
+    return 0;
   }
 
   @Override
@@ -84,14 +80,14 @@ public class FloatGroupByColumnSelectorStrategy implements 
GroupByColumnSelector
   @Override
   public void initGroupingKeyColumnValue(
       int keyBufferPosition,
-      int columnIndex,
+      int dimensionIndex,
       Object rowObj,
       ByteBuffer keyBuffer,
       int[] stack
   )
   {
-    writeToKeyBuffer(keyBufferPosition, rowObj, keyBuffer);
-    stack[columnIndex] = 1;
+    writeToKeyBuffer(keyBufferPosition, 
DimensionHandlerUtils.nullToZero((Float) rowObj), keyBuffer);
+    stack[dimensionIndex] = 1;
   }
 
   @Override
@@ -106,4 +102,15 @@ public class FloatGroupByColumnSelectorStrategy implements 
GroupByColumnSelector
     // this method handles row values after the first in a multivalued row, so 
just return false
     return false;
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
+
+  private void writeToKeyBuffer(int keyBufferPosition, float value, ByteBuffer 
keyBuffer)
+  {
+    keyBuffer.putFloat(keyBufferPosition, value);
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/GroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/GroupByColumnSelectorStrategy.java
index 5825009..1a2bfc5 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/GroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/GroupByColumnSelectorStrategy.java
@@ -33,6 +33,14 @@ import java.nio.ByteBuffer;
  * GroupByQueryEngineV2.
  *
  * Each GroupByColumnSelectorStrategy is associated with a single dimension.
+ *
+ * Strategies may have internal state, such as the dictionary maintained by
+ * {@link DictionaryBuildingStringGroupByColumnSelectorStrategy}. Callers 
should assume that the internal
+ * state footprint starts out empty (zero bytes) and is also reset to zero on 
each call to {@link #reset()}. Each call
+ * to {@link #initColumnValues} or {@link #writeToKeyBuffer(int, 
ColumnValueSelector, ByteBuffer)} returns the
+ * incremental increase in internal state footprint that happened as a result 
of that particular call.
+ *
+ * @see 
org.apache.druid.query.groupby.epinephelinae.vector.GroupByVectorColumnSelector 
the vectorized version
  */
 public interface GroupByColumnSelectorStrategy extends ColumnSelectorStrategy
 {
@@ -77,8 +85,11 @@ public interface GroupByColumnSelectorStrategy extends 
ColumnSelectorStrategy
    * @param selector    Value selector for a column.
    * @param columnIndex Index of the column within the row values array
    * @param valuess     Row values array, one index per column
+   *
+   * @return estimated increase in internal state footprint, in bytes, as a 
result of this operation. May be zero if
+   * memory did not increase as a result of this operation. Will not be 
negative.
    */
-  void initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess);
+  int initColumnValues(ColumnValueSelector selector, int columnIndex, Object[] 
valuess);
 
   /**
    * Read the first value within a row values object (e. g. {@link 
org.apache.druid.segment.data.IndexedInts}, as the value in
@@ -88,14 +99,14 @@ public interface GroupByColumnSelectorStrategy extends 
ColumnSelectorStrategy
    * If the size of the row is > 0, write 1 to stack[] at columnIndex, 
otherwise write 0.
    *
    * @param keyBufferPosition Starting offset for this column's value within 
the grouping key.
-   * @param columnIndex       Index of the column within the row values array
+   * @param dimensionIndex    Index of this dimension within the {@code stack} 
array
    * @param rowObj            Row value object for this column
    * @param keyBuffer         grouping key
    * @param stack             array containing the current within-row value 
index for each column
    */
   void initGroupingKeyColumnValue(
       int keyBufferPosition,
-      int columnIndex,
+      int dimensionIndex,
       Object rowObj,
       ByteBuffer keyBuffer,
       int[] stack
@@ -123,30 +134,35 @@ public interface GroupByColumnSelectorStrategy extends 
ColumnSelectorStrategy
   );
 
   /**
-   * Retrieve a single object using the {@link ColumnValueSelector}.  The 
reading column must have a single value.
-   *
-   * @param selector Value selector for a column
-   *
-   * @return an object retrieved from the column
-   */
-  Object getOnlyValue(ColumnValueSelector selector);
-
-  /**
-   * Write a given object to the keyBuffer at keyBufferPosition.
+   * Write a single object from the given selector to the keyBuffer at 
keyBufferPosition. The reading column must
+   * have a single value. The position of the keyBuffer may be modified.
    *
    * @param keyBufferPosition starting offset for this column's value within 
the grouping key
-   * @param obj               row value object retrieved from {@link 
#getOnlyValue(ColumnValueSelector)}
+   * @param selector          selector to retrieve row value object from
    * @param keyBuffer         grouping key
+   *
+   * @return estimated increase in internal state footprint, in bytes, as a 
result of this operation. May be zero if
+   * memory did not increase as a result of this operation. Will not be 
negative.
    */
-  void writeToKeyBuffer(int keyBufferPosition, Object obj, ByteBuffer 
keyBuffer);
+  int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector selector, 
ByteBuffer keyBuffer);
 
   /**
    * Return BufferComparator for values written using this strategy when limit 
is pushed down to segment scan.
+   *
    * @param keyBufferPosition starting offset for this column's value within 
the grouping key
-   * @param stringComparator stringComparator from LimitSpec for this column. 
If this is null, implementations
-   *                         will use the {@link 
org.apache.druid.query.ordering.StringComparators#LEXICOGRAPHIC}
-   *                         comparator.
+   * @param stringComparator  stringComparator from LimitSpec for this column. 
If this is null, implementations
+   *                          will use the {@link 
org.apache.druid.query.ordering.StringComparators#LEXICOGRAPHIC}
+   *                          comparator.
+   *
    * @return BufferComparator for comparing values written
    */
   Grouper.BufferComparator bufferComparator(int keyBufferPosition, @Nullable 
StringComparator stringComparator);
+
+  /**
+   * Reset any internal state held by this selector.
+   *
+   * After this method is called, any row objects or key objects generated by 
any methods of this class must be
+   * considered unreadable. Calling {@link #processValueFromGroupingKey} on 
that memory has undefined behavior.
+   */
+  void reset();
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/LongGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/LongGroupByColumnSelectorStrategy.java
index b70c5c2..95d57e0 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/LongGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/LongGroupByColumnSelectorStrategy.java
@@ -51,21 +51,17 @@ public class LongGroupByColumnSelectorStrategy implements 
GroupByColumnSelectorS
   }
 
   @Override
-  public void initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
+  public int initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
   {
     valuess[columnIndex] = selector.getLong();
+    return 0;
   }
 
   @Override
-  public Object getOnlyValue(ColumnValueSelector selector)
+  public int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector 
selector, ByteBuffer keyBuffer)
   {
-    return selector.getLong();
-  }
-
-  @Override
-  public void writeToKeyBuffer(int keyBufferPosition, @Nullable Object obj, 
ByteBuffer keyBuffer)
-  {
-    keyBuffer.putLong(keyBufferPosition, 
DimensionHandlerUtils.nullToZero((Long) obj));
+    keyBuffer.putLong(keyBufferPosition, selector.getLong());
+    return 0;
   }
 
   @Override
@@ -84,14 +80,14 @@ public class LongGroupByColumnSelectorStrategy implements 
GroupByColumnSelectorS
   @Override
   public void initGroupingKeyColumnValue(
       int keyBufferPosition,
-      int columnIndex,
+      int dimensionIndex,
       Object rowObj,
       ByteBuffer keyBuffer,
       int[] stack
   )
   {
-    writeToKeyBuffer(keyBufferPosition, rowObj, keyBuffer);
-    stack[columnIndex] = 1;
+    writeToKeyBuffer(keyBufferPosition, 
DimensionHandlerUtils.nullToZero((Long) rowObj), keyBuffer);
+    stack[dimensionIndex] = 1;
   }
 
   @Override
@@ -106,4 +102,15 @@ public class LongGroupByColumnSelectorStrategy implements 
GroupByColumnSelectorS
     // this method handles row values after the first in a multivalued row, so 
just return false
     return false;
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
+
+  public void writeToKeyBuffer(int keyBufferPosition, long value, ByteBuffer 
keyBuffer)
+  {
+    keyBuffer.putLong(keyBufferPosition, value);
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/NullableNumericGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/NullableNumericGroupByColumnSelectorStrategy.java
index 663a0dd..34af762 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/NullableNumericGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/NullableNumericGroupByColumnSelectorStrategy.java
@@ -38,10 +38,13 @@ import java.nio.ByteBuffer;
 public class NullableNumericGroupByColumnSelectorStrategy implements 
GroupByColumnSelectorStrategy
 {
   private final GroupByColumnSelectorStrategy delegate;
+  private final byte[] nullKeyBytes;
 
   public 
NullableNumericGroupByColumnSelectorStrategy(GroupByColumnSelectorStrategy 
delegate)
   {
     this.delegate = delegate;
+    this.nullKeyBytes = new byte[delegate.getGroupingKeySize() + 1];
+    this.nullKeyBytes[0] = NullHandling.IS_NULL_BYTE;
   }
 
   @Override
@@ -66,34 +69,27 @@ public class NullableNumericGroupByColumnSelectorStrategy 
implements GroupByColu
   }
 
   @Override
-  public void initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] values)
+  public int initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] values)
   {
     if (selector.isNull()) {
       values[columnIndex] = null;
+      return 0;
     } else {
-      delegate.initColumnValues(selector, columnIndex, values);
+      return delegate.initColumnValues(selector, columnIndex, values);
     }
   }
 
   @Override
-  @Nullable
-  public Object getOnlyValue(ColumnValueSelector selector)
+  public int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector 
selector, ByteBuffer keyBuffer)
   {
     if (selector.isNull()) {
-      return null;
-    }
-    return delegate.getOnlyValue(selector);
-  }
-
-  @Override
-  public void writeToKeyBuffer(int keyBufferPosition, @Nullable Object obj, 
ByteBuffer keyBuffer)
-  {
-    if (obj == null) {
-      keyBuffer.put(keyBufferPosition, NullHandling.IS_NULL_BYTE);
+      keyBuffer.position(keyBufferPosition);
+      keyBuffer.put(nullKeyBytes);
+      return 0;
     } else {
       keyBuffer.put(keyBufferPosition, NullHandling.IS_NOT_NULL_BYTE);
+      return delegate.writeToKeyBuffer(keyBufferPosition + Byte.BYTES, 
selector, keyBuffer);
     }
-    delegate.writeToKeyBuffer(keyBufferPosition + Byte.BYTES, obj, keyBuffer);
   }
 
   @Override
@@ -111,14 +107,27 @@ public class NullableNumericGroupByColumnSelectorStrategy 
implements GroupByColu
   @Override
   public void initGroupingKeyColumnValue(
       int keyBufferPosition,
-      int columnIndex,
+      int dimensionIndex,
       Object rowObj,
       ByteBuffer keyBuffer,
       int[] stack
   )
   {
-    writeToKeyBuffer(keyBufferPosition, rowObj, keyBuffer);
-    stack[columnIndex] = 1;
+    if (rowObj == null) {
+      keyBuffer.position(keyBufferPosition);
+      keyBuffer.put(nullKeyBytes);
+    } else {
+      keyBuffer.put(keyBufferPosition, NullHandling.IS_NOT_NULL_BYTE);
+
+      // No need to update stack ourselves; we expect the delegate to do this.
+      delegate.initGroupingKeyColumnValue(
+          keyBufferPosition + Byte.BYTES,
+          dimensionIndex,
+          rowObj,
+          keyBuffer,
+          stack
+      );
+    }
   }
 
   @Override
@@ -133,4 +142,10 @@ public class NullableNumericGroupByColumnSelectorStrategy 
implements GroupByColu
     // this method handles row values after the first in a multivalued row, so 
just return false
     return false;
   }
+
+  @Override
+  public void reset()
+  {
+    delegate.reset();
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/StringGroupByColumnSelectorStrategy.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/StringGroupByColumnSelectorStrategy.java
index a128d10..8c25c77 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/StringGroupByColumnSelectorStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/column/StringGroupByColumnSelectorStrategy.java
@@ -76,32 +76,39 @@ public class StringGroupByColumnSelectorStrategy implements 
GroupByColumnSelecto
   }
 
   @Override
-  public void initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
+  public int initColumnValues(ColumnValueSelector selector, int columnIndex, 
Object[] valuess)
   {
     DimensionSelector dimSelector = (DimensionSelector) selector;
     IndexedInts row = dimSelector.getRow();
     valuess[columnIndex] = row;
+    return 0;
   }
 
-  @Override
-  public Object getOnlyValue(ColumnValueSelector selector)
+  /**
+   * Writes a dictionary ID to the grouping key.
+   *
+   * Protected so subclasses can access it, like {@link 
DictionaryBuildingStringGroupByColumnSelectorStrategy}.
+   */
+  protected void writeToKeyBuffer(int keyBufferPosition, int dictId, 
ByteBuffer keyBuffer)
   {
-    final DimensionSelector dimSelector = (DimensionSelector) selector;
-    final IndexedInts row = dimSelector.getRow();
-    Preconditions.checkState(row.size() < 2, "Not supported for multi-value 
dimensions");
-    return row.size() == 1 ? row.get(0) : GROUP_BY_MISSING_VALUE;
+    keyBuffer.putInt(keyBufferPosition, dictId);
   }
 
   @Override
-  public void writeToKeyBuffer(int keyBufferPosition, Object obj, ByteBuffer 
keyBuffer)
+  public int writeToKeyBuffer(int keyBufferPosition, ColumnValueSelector 
selector, ByteBuffer keyBuffer)
   {
-    keyBuffer.putInt(keyBufferPosition, (int) obj);
+    final DimensionSelector dimSelector = (DimensionSelector) selector;
+    final IndexedInts row = dimSelector.getRow();
+    Preconditions.checkState(row.size() < 2, "Not supported for multi-value 
dimensions");
+    final int dictId = row.size() == 1 ? row.get(0) : GROUP_BY_MISSING_VALUE;
+    keyBuffer.putInt(keyBufferPosition, dictId);
+    return 0;
   }
 
   @Override
   public void initGroupingKeyColumnValue(
       int keyBufferPosition,
-      int columnIndex,
+      int dimensionIndex,
       Object rowObj,
       ByteBuffer keyBuffer,
       int[] stack
@@ -111,7 +118,7 @@ public class StringGroupByColumnSelectorStrategy implements 
GroupByColumnSelecto
     int rowSize = row.size();
 
     initializeGroupingKeyV2Dimension(row, rowSize, keyBuffer, 
keyBufferPosition);
-    stack[columnIndex] = rowSize == 0 ? 0 : 1;
+    stack[dimensionIndex] = rowSize == 0 ? 0 : 1;
   }
 
   @Override
@@ -172,4 +179,10 @@ public class StringGroupByColumnSelectorStrategy 
implements GroupByColumnSelecto
       };
     }
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/DictionaryBuildingSingleValueStringGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/DictionaryBuildingSingleValueStringGroupByVectorColumnSelector.java
index d83166f..d5db5c9 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/DictionaryBuildingSingleValueStringGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/DictionaryBuildingSingleValueStringGroupByVectorColumnSelector.java
@@ -24,6 +24,7 @@ import org.apache.datasketches.memory.Memory;
 import org.apache.datasketches.memory.WritableMemory;
 import org.apache.druid.common.config.NullHandling;
 import org.apache.druid.query.groupby.ResultRow;
+import org.apache.druid.query.groupby.epinephelinae.DictionaryBuilding;
 import org.apache.druid.segment.vector.VectorObjectSelector;
 
 import java.util.ArrayList;
@@ -42,20 +43,15 @@ public class 
DictionaryBuildingSingleValueStringGroupByVectorColumnSelector impl
 
   private final VectorObjectSelector selector;
 
-  private int nextId = 0;
   private final List<String> dictionary = new ArrayList<>();
   private final Object2IntOpenHashMap<String> reverseDictionary = new 
Object2IntOpenHashMap<>();
 
-  {
-    reverseDictionary.defaultReturnValue(-1);
-  }
-
   public 
DictionaryBuildingSingleValueStringGroupByVectorColumnSelector(VectorObjectSelector
 selector)
   {
     this.selector = selector;
+    this.reverseDictionary.defaultReturnValue(-1);
   }
 
-
   @Override
   public int getGroupingKeySize()
   {
@@ -63,7 +59,7 @@ public class 
DictionaryBuildingSingleValueStringGroupByVectorColumnSelector impl
   }
 
   @Override
-  public void writeKeys(
+  public int writeKeys(
       final WritableMemory keySpace,
       final int keySize,
       final int keyOffset,
@@ -72,19 +68,26 @@ public class 
DictionaryBuildingSingleValueStringGroupByVectorColumnSelector impl
   )
   {
     final Object[] vector = selector.getObjectVector();
+    int stateFootprintIncrease = 0;
 
     for (int i = startRow, j = keyOffset; i < endRow; i++, j += keySize) {
       final String value = (String) vector[i];
       final int dictId = reverseDictionary.getInt(value);
       if (dictId < 0) {
+        final int nextId = dictionary.size();
         dictionary.add(value);
         reverseDictionary.put(value, nextId);
         keySpace.putInt(j, nextId);
-        nextId++;
+
+        // Use same ROUGH_OVERHEAD_PER_DICTIONARY_ENTRY as the nonvectorized 
version; dictionary structure is the same.
+        stateFootprintIncrease +=
+            DictionaryBuilding.estimateEntryFootprint((value == null ? 0 : 
value.length()) * Character.BYTES);
       } else {
         keySpace.putInt(j, dictId);
       }
     }
+
+    return stateFootprintIncrease;
   }
 
   @Override
@@ -104,4 +107,11 @@ public class 
DictionaryBuildingSingleValueStringGroupByVectorColumnSelector impl
       resultRow.set(resultRowPosition, NullHandling.defaultStringValue());
     }
   }
+
+  @Override
+  public void reset()
+  {
+    dictionary.clear();
+    reverseDictionary.clear();
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/DoubleGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/DoubleGroupByVectorColumnSelector.java
index 437d2f8..7830483 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/DoubleGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/DoubleGroupByVectorColumnSelector.java
@@ -40,7 +40,7 @@ public class DoubleGroupByVectorColumnSelector implements 
GroupByVectorColumnSel
   }
 
   @Override
-  public void writeKeys(
+  public int writeKeys(
       final WritableMemory keySpace,
       final int keySize,
       final int keyOffset,
@@ -57,6 +57,8 @@ public class DoubleGroupByVectorColumnSelector implements 
GroupByVectorColumnSel
         keySpace.putDouble(j, vector[i]);
       }
     }
+
+    return 0;
   }
 
   @Override
@@ -69,4 +71,10 @@ public class DoubleGroupByVectorColumnSelector implements 
GroupByVectorColumnSel
   {
     resultRow.set(resultRowPosition, keyMemory.getDouble(keyOffset));
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/FloatGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/FloatGroupByVectorColumnSelector.java
index c404932..c0dc92b 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/FloatGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/FloatGroupByVectorColumnSelector.java
@@ -40,7 +40,7 @@ public class FloatGroupByVectorColumnSelector implements 
GroupByVectorColumnSele
   }
 
   @Override
-  public void writeKeys(
+  public int writeKeys(
       final WritableMemory keySpace,
       final int keySize,
       final int keyOffset,
@@ -57,6 +57,8 @@ public class FloatGroupByVectorColumnSelector implements 
GroupByVectorColumnSele
         keySpace.putFloat(j, vector[i]);
       }
     }
+
+    return 0;
   }
 
   @Override
@@ -69,4 +71,10 @@ public class FloatGroupByVectorColumnSelector implements 
GroupByVectorColumnSele
   {
     resultRow.set(resultRowPosition, keyMemory.getFloat(keyOffset));
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/GroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/GroupByVectorColumnSelector.java
index 707980f..70beec0 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/GroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/GroupByVectorColumnSelector.java
@@ -26,7 +26,14 @@ import org.apache.druid.query.groupby.ResultRow;
 /**
  * Column processor for groupBy dimensions.
  *
+ * Processors may have internal state, such as the dictionary maintained by
+ * {@link DictionaryBuildingSingleValueStringGroupByVectorColumnSelector}. 
Callers should assume that the internal
+ * state footprint starts out empty (zero bytes) and is also reset to zero on 
each call to {@link #reset()}. Each call
+ * to {@link #writeKeys} returns the incremental increase in internal state 
footprint that happened as a result
+ * of that particular call.
+ *
  * @see GroupByVectorColumnProcessorFactory
+ * @see 
org.apache.druid.query.groupby.epinephelinae.column.GroupByColumnSelectorStrategy
 the nonvectorized version
  */
 public interface GroupByVectorColumnSelector
 {
@@ -44,10 +51,13 @@ public interface GroupByVectorColumnSelector
    * @param keyOffset starting position for the first key part within keySpace
    * @param startRow  starting row (inclusive) within the current vector
    * @param endRow    ending row (exclusive) within the current vector
+   *
+   * @return estimated increase in internal state footprint, in bytes, as a 
result of this operation. May be zero if
+   * memory did not increase as a result of this operation. Will not be 
negative.
    */
   // False positive unused inspection warning for "keySize": 
https://youtrack.jetbrains.com/issue/IDEA-231034
   @SuppressWarnings("unused")
-  void writeKeys(WritableMemory keySpace, int keySize, int keyOffset, int 
startRow, int endRow);
+  int writeKeys(WritableMemory keySpace, int keySize, int keyOffset, int 
startRow, int endRow);
 
   /**
    * Write key parts for this column into a particular result row.
@@ -63,4 +73,12 @@ public interface GroupByVectorColumnSelector
       ResultRow resultRow,
       int resultRowPosition
   );
+
+  /**
+   * Reset any internal state held by this selector.
+   *
+   * After this method is called, any memory previously written by {@link 
#writeKeys} must be considered unreadable.
+   * Calling {@link #writeKeyToResultRow} on that memory has undefined 
behavior.
+   */
+  void reset();
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/LongGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/LongGroupByVectorColumnSelector.java
index f806ef8..c875696 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/LongGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/LongGroupByVectorColumnSelector.java
@@ -40,7 +40,7 @@ public class LongGroupByVectorColumnSelector implements 
GroupByVectorColumnSelec
   }
 
   @Override
-  public void writeKeys(
+  public int writeKeys(
       final WritableMemory keySpace,
       final int keySize,
       final int keyOffset,
@@ -57,6 +57,8 @@ public class LongGroupByVectorColumnSelector implements 
GroupByVectorColumnSelec
         keySpace.putLong(j, vector[i]);
       }
     }
+
+    return 0;
   }
 
   @Override
@@ -69,4 +71,10 @@ public class LongGroupByVectorColumnSelector implements 
GroupByVectorColumnSelec
   {
     resultRow.set(resultRowPosition, keyMemory.getLong(keyOffset));
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NilGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NilGroupByVectorColumnSelector.java
index e70eaa7..26c3369 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NilGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NilGroupByVectorColumnSelector.java
@@ -42,9 +42,10 @@ public class NilGroupByVectorColumnSelector implements 
GroupByVectorColumnSelect
   }
 
   @Override
-  public void writeKeys(WritableMemory keySpace, int keySize, int keyOffset, 
int startRow, int endRow)
+  public int writeKeys(WritableMemory keySpace, int keySize, int keyOffset, 
int startRow, int endRow)
   {
     // Nothing to do.
+    return 0;
   }
 
   @Override
@@ -52,4 +53,10 @@ public class NilGroupByVectorColumnSelector implements 
GroupByVectorColumnSelect
   {
     resultRow.set(resultRowPosition, null);
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableDoubleGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableDoubleGroupByVectorColumnSelector.java
index 26a5844..db9d470 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableDoubleGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableDoubleGroupByVectorColumnSelector.java
@@ -41,7 +41,7 @@ public class NullableDoubleGroupByVectorColumnSelector 
implements GroupByVectorC
   }
 
   @Override
-  public void writeKeys(
+  public int writeKeys(
       final WritableMemory keySpace,
       final int keySize,
       final int keyOffset,
@@ -63,6 +63,8 @@ public class NullableDoubleGroupByVectorColumnSelector 
implements GroupByVectorC
         keySpace.putDouble(j + 1, vector[i]);
       }
     }
+
+    return 0;
   }
 
   @Override
@@ -79,4 +81,10 @@ public class NullableDoubleGroupByVectorColumnSelector 
implements GroupByVectorC
       resultRow.set(resultRowPosition, keyMemory.getDouble(keyOffset + 1));
     }
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableFloatGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableFloatGroupByVectorColumnSelector.java
index a2a0c60..238e4cf 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableFloatGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableFloatGroupByVectorColumnSelector.java
@@ -41,7 +41,7 @@ public class NullableFloatGroupByVectorColumnSelector 
implements GroupByVectorCo
   }
 
   @Override
-  public void writeKeys(
+  public int writeKeys(
       final WritableMemory keySpace,
       final int keySize,
       final int keyOffset,
@@ -63,6 +63,8 @@ public class NullableFloatGroupByVectorColumnSelector 
implements GroupByVectorCo
         keySpace.putFloat(j + 1, vector[i]);
       }
     }
+
+    return 0;
   }
 
   @Override
@@ -79,4 +81,10 @@ public class NullableFloatGroupByVectorColumnSelector 
implements GroupByVectorCo
       resultRow.set(resultRowPosition, keyMemory.getFloat(keyOffset + 1));
     }
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableLongGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableLongGroupByVectorColumnSelector.java
index ab020e2..8ea0b7b 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableLongGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/NullableLongGroupByVectorColumnSelector.java
@@ -41,7 +41,7 @@ public class NullableLongGroupByVectorColumnSelector 
implements GroupByVectorCol
   }
 
   @Override
-  public void writeKeys(
+  public int writeKeys(
       final WritableMemory keySpace,
       final int keySize,
       final int keyOffset,
@@ -63,6 +63,8 @@ public class NullableLongGroupByVectorColumnSelector 
implements GroupByVectorCol
         keySpace.putLong(j + 1, vector[i]);
       }
     }
+
+    return 0;
   }
 
   @Override
@@ -79,4 +81,10 @@ public class NullableLongGroupByVectorColumnSelector 
implements GroupByVectorCol
       resultRow.set(resultRowPosition, keyMemory.getLong(keyOffset + 1));
     }
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/SingleValueStringGroupByVectorColumnSelector.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/SingleValueStringGroupByVectorColumnSelector.java
index 2dfdb0e..f82f4db 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/SingleValueStringGroupByVectorColumnSelector.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/SingleValueStringGroupByVectorColumnSelector.java
@@ -40,7 +40,7 @@ public class SingleValueStringGroupByVectorColumnSelector 
implements GroupByVect
   }
 
   @Override
-  public void writeKeys(
+  public int writeKeys(
       final WritableMemory keySpace,
       final int keySize,
       final int keyOffset,
@@ -57,6 +57,8 @@ public class SingleValueStringGroupByVectorColumnSelector 
implements GroupByVect
         keySpace.putInt(j, vector[i]);
       }
     }
+
+    return 0;
   }
 
   @Override
@@ -70,4 +72,10 @@ public class SingleValueStringGroupByVectorColumnSelector 
implements GroupByVect
     final int id = keyMemory.getInt(keyOffset);
     resultRow.set(resultRowPosition, selector.lookupName(id));
   }
+
+  @Override
+  public void reset()
+  {
+    // Nothing to do.
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/VectorGroupByEngine.java
 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/VectorGroupByEngine.java
index 1ead1d8..c867425 100644
--- 
a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/VectorGroupByEngine.java
+++ 
b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/vector/VectorGroupByEngine.java
@@ -244,8 +244,13 @@ public class VectorGroupByEngine
     @Nullable
     private Interval bucketInterval;
 
+    // -1 if the current vector was fully aggregated after a call to 
"initNewDelegate". Otherwise, the number of
+    // rows of the current vector that were aggregated.
     private int partiallyAggregatedRows = -1;
 
+    // Sum of internal state footprint across all "selectors".
+    private long selectorInternalFootprint = 0;
+
     @Nullable
     private CloseableGrouperIterator<Memory, ResultRow> delegate = null;
 
@@ -304,6 +309,8 @@ public class VectorGroupByEngine
             if (delegate != null) {
               delegate.close();
               vectorGrouper.reset();
+              selectors.forEach(GroupByVectorColumnSelector::reset);
+              selectorInternalFootprint = 0;
             }
 
             delegate = initNewDelegate();
@@ -390,7 +397,11 @@ public class VectorGroupByEngine
           // Write keys to the keySpace.
           int keyOffset = 0;
           for (final GroupByVectorColumnSelector selector : selectors) {
-            selector.writeKeys(keySpace, keySize, keyOffset, startOffset, 
granulizer.getEndOffset());
+            // Update selectorInternalFootprint now, but check it later. (We 
reset on the first vector that causes us
+            // to go past the limit.)
+            selectorInternalFootprint +=
+                selector.writeKeys(keySpace, keySize, keyOffset, startOffset, 
granulizer.getEndOffset());
+
             keyOffset += selector.getGroupingKeySize();
           }
 
@@ -420,6 +431,8 @@ public class VectorGroupByEngine
           // Advance bucketInterval.
           bucketInterval = bucketIterator.hasNext() ? bucketIterator.next() : 
null;
           break;
+        } else if (selectorInternalFootprint > 
querySpecificConfig.getMaxSelectorDictionarySize()) {
+          break;
         }
       }
 
diff --git 
a/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryConfigTest.java
 
b/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryConfigTest.java
index fb0090f..0ce2998 100644
--- 
a/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryConfigTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryConfigTest.java
@@ -39,8 +39,9 @@ public class GroupByQueryConfigTest
       .put("maxIntermediateRows", "2")
       .put("maxResults", "3")
       .put("maxOnDiskStorage", "4")
-      .put("maxMergingDictionarySize", "5")
-      .put("bufferGrouperMaxLoadFactor", "6")
+      .put("maxSelectorDictionarySize", "5")
+      .put("maxMergingDictionarySize", "6")
+      .put("bufferGrouperMaxLoadFactor", "7")
       .build();
 
   @Test
@@ -54,8 +55,9 @@ public class GroupByQueryConfigTest
     Assert.assertEquals(2, config.getMaxIntermediateRows());
     Assert.assertEquals(3, config.getMaxResults());
     Assert.assertEquals(4, config.getMaxOnDiskStorage());
-    Assert.assertEquals(5, config.getMaxMergingDictionarySize());
-    Assert.assertEquals(6.0, config.getBufferGrouperMaxLoadFactor(), 0.0);
+    Assert.assertEquals(5, config.getMaxSelectorDictionarySize());
+    Assert.assertEquals(6, config.getMaxMergingDictionarySize());
+    Assert.assertEquals(7.0, config.getBufferGrouperMaxLoadFactor(), 0.0);
     Assert.assertFalse(config.isApplyLimitPushDownToSegment());
   }
 
@@ -77,8 +79,9 @@ public class GroupByQueryConfigTest
     Assert.assertEquals(2, config2.getMaxIntermediateRows());
     Assert.assertEquals(3, config2.getMaxResults());
     Assert.assertEquals(4, config2.getMaxOnDiskStorage());
-    Assert.assertEquals(5, config2.getMaxMergingDictionarySize());
-    Assert.assertEquals(6.0, config2.getBufferGrouperMaxLoadFactor(), 0.0);
+    Assert.assertEquals(5, config2.getMaxSelectorDictionarySize());
+    Assert.assertEquals(6, config2.getMaxMergingDictionarySize());
+    Assert.assertEquals(7.0, config2.getBufferGrouperMaxLoadFactor(), 0.0);
     Assert.assertFalse(config2.isApplyLimitPushDownToSegment());
   }
 
@@ -92,13 +95,14 @@ public class GroupByQueryConfigTest
                     .setInterval(Intervals.of("2000/P1D"))
                     .setGranularity(Granularities.ALL)
                     .setContext(
-                        ImmutableMap.of(
-                            "groupByStrategy", "v1",
-                            "maxOnDiskStorage", 0,
-                            "maxResults", 2,
-                            "maxMergingDictionarySize", 3,
-                            "applyLimitPushDownToSegment", true
-                        )
+                        ImmutableMap.<String, Object>builder()
+                                    .put("groupByStrategy", "v1")
+                                    .put("maxOnDiskStorage", 0)
+                                    .put("maxResults", 2)
+                                    .put("maxSelectorDictionarySize", 3)
+                                    .put("maxMergingDictionarySize", 4)
+                                    .put("applyLimitPushDownToSegment", true)
+                                    .build()
                     )
                     .build()
     );
@@ -109,8 +113,9 @@ public class GroupByQueryConfigTest
     Assert.assertEquals(2, config2.getMaxIntermediateRows());
     Assert.assertEquals(2, config2.getMaxResults());
     Assert.assertEquals(0, config2.getMaxOnDiskStorage());
-    Assert.assertEquals(3, config2.getMaxMergingDictionarySize());
-    Assert.assertEquals(6.0, config2.getBufferGrouperMaxLoadFactor(), 0.0);
+    Assert.assertEquals(3, config2.getMaxSelectorDictionarySize());
+    Assert.assertEquals(4, config2.getMaxMergingDictionarySize());
+    Assert.assertEquals(7.0, config2.getBufferGrouperMaxLoadFactor(), 0.0);
     Assert.assertTrue(config2.isApplyLimitPushDownToSegment());
   }
 }
diff --git 
a/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryRunnerTest.java
 
b/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryRunnerTest.java
index 5f1deee..ee1340d 100644
--- 
a/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryRunnerTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryRunnerTest.java
@@ -298,6 +298,12 @@ public class GroupByQueryRunnerTest extends 
InitializedNullHandlingTest
       }
 
       @Override
+      public long getMaxSelectorDictionarySize()
+      {
+        return 20;
+      }
+
+      @Override
       public long getMaxMergingDictionarySize()
       {
         return 400;
diff --git 
a/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayDoubleGroupByColumnSelectorStrategyTest.java
 
b/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayDoubleGroupByColumnSelectorStrategyTest.java
index 7b66225f..4acdef0 100644
--- 
a/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayDoubleGroupByColumnSelectorStrategyTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayDoubleGroupByColumnSelectorStrategyTest.java
@@ -115,7 +115,7 @@ public class ArrayDoubleGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     
Mockito.when(columnValueSelector.getObject()).thenReturn(ImmutableList.of(1.0, 
2.0));
-    Assert.assertEquals(0, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(0, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
@@ -123,7 +123,7 @@ public class ArrayDoubleGroupByColumnSelectorStrategyTest
 
     buffer1.putInt(0);
     strategy.processValueFromGroupingKey(groupByColumnSelectorPlus, buffer1, 
row, 0);
-    Assert.assertEquals(new ComparableList(ImmutableList.of(1.0, 2.0)), 
row.get(0));
+    Assert.assertEquals(new ComparableList<>(ImmutableList.of(1.0, 2.0)), 
row.get(0));
   }
 
 
@@ -132,7 +132,7 @@ public class ArrayDoubleGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     
Mockito.when(columnValueSelector.getObject()).thenReturn(ImmutableList.of(4.0, 
2.0));
-    Assert.assertEquals(3, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(3, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
@@ -140,7 +140,7 @@ public class ArrayDoubleGroupByColumnSelectorStrategyTest
 
     buffer1.putInt(3);
     strategy.processValueFromGroupingKey(groupByColumnSelectorPlus, buffer1, 
row, 0);
-    Assert.assertEquals(new ComparableList(ImmutableList.of(4.0, 2.0)), 
row.get(0));
+    Assert.assertEquals(new ComparableList<>(ImmutableList.of(4.0, 2.0)), 
row.get(0));
   }
 
   @Test
@@ -148,14 +148,14 @@ public class ArrayDoubleGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     Mockito.when(columnValueSelector.getObject()).thenReturn(new 
Object[]{4.0D, 2.0D});
-    Assert.assertEquals(3, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(3, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
     ResultRow row = ResultRow.create(1);
     buffer1.putInt(3);
     strategy.processValueFromGroupingKey(groupByColumnSelectorPlus, buffer1, 
row, 0);
-    Assert.assertEquals(new ComparableList(ImmutableList.of(4.0, 2.0)), 
row.get(0));
+    Assert.assertEquals(new ComparableList<>(ImmutableList.of(4.0, 2.0)), 
row.get(0));
   }
 
   @After
diff --git 
a/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayLongGroupByColumnSelectorStrategyTest.java
 
b/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayLongGroupByColumnSelectorStrategyTest.java
index 51325b5..74be538 100644
--- 
a/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayLongGroupByColumnSelectorStrategyTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayLongGroupByColumnSelectorStrategyTest.java
@@ -118,7 +118,7 @@ public class ArrayLongGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     
Mockito.when(columnValueSelector.getObject()).thenReturn(ImmutableList.of(1L, 
2L));
-    Assert.assertEquals(0, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(0, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
@@ -126,7 +126,7 @@ public class ArrayLongGroupByColumnSelectorStrategyTest
 
     buffer1.putInt(0);
     strategy.processValueFromGroupingKey(groupByColumnSelectorPlus, buffer1, 
row, 0);
-    Assert.assertEquals(new ComparableList(ImmutableList.of(1L, 2L)), 
row.get(0));
+    Assert.assertEquals(new ComparableList<>(ImmutableList.of(1L, 2L)), 
row.get(0));
   }
 
 
@@ -135,7 +135,7 @@ public class ArrayLongGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     
Mockito.when(columnValueSelector.getObject()).thenReturn(ImmutableList.of(4L, 
2L));
-    Assert.assertEquals(3, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(3, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
@@ -143,7 +143,7 @@ public class ArrayLongGroupByColumnSelectorStrategyTest
 
     buffer1.putInt(3);
     strategy.processValueFromGroupingKey(groupByColumnSelectorPlus, buffer1, 
row, 0);
-    Assert.assertEquals(new ComparableList(ImmutableList.of(4L, 2L)), 
row.get(0));
+    Assert.assertEquals(new ComparableList<>(ImmutableList.of(4L, 2L)), 
row.get(0));
   }
 
   @Test
@@ -151,14 +151,14 @@ public class ArrayLongGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     Mockito.when(columnValueSelector.getObject()).thenReturn(new Object[]{4L, 
2L});
-    Assert.assertEquals(3, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(3, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
     ResultRow row = ResultRow.create(1);
     buffer1.putInt(3);
     strategy.processValueFromGroupingKey(groupByColumnSelectorPlus, buffer1, 
row, 0);
-    Assert.assertEquals(new ComparableList(ImmutableList.of(4L, 2L)), 
row.get(0));
+    Assert.assertEquals(new ComparableList<>(ImmutableList.of(4L, 2L)), 
row.get(0));
   }
 
   @After
diff --git 
a/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayStringGroupByColumnSelectorStrategyTest.java
 
b/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayStringGroupByColumnSelectorStrategyTest.java
index a1d70b5..3be1354 100644
--- 
a/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayStringGroupByColumnSelectorStrategyTest.java
+++ 
b/processing/src/test/java/org/apache/druid/query/groupby/epinephelinae/column/ArrayStringGroupByColumnSelectorStrategyTest.java
@@ -127,7 +127,7 @@ public class ArrayStringGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     
Mockito.when(columnValueSelector.getObject()).thenReturn(ImmutableList.of("a", 
"b"));
-    Assert.assertEquals(0, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(0, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
@@ -144,7 +144,7 @@ public class ArrayStringGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     
Mockito.when(columnValueSelector.getObject()).thenReturn(ImmutableList.of("f", 
"a"));
-    Assert.assertEquals(3, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(3, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
@@ -160,7 +160,7 @@ public class ArrayStringGroupByColumnSelectorStrategyTest
   {
     ColumnValueSelector columnValueSelector = 
Mockito.mock(ColumnValueSelector.class);
     Mockito.when(columnValueSelector.getObject()).thenReturn(new Object[]{"f", 
"a"});
-    Assert.assertEquals(3, strategy.getOnlyValue(columnValueSelector));
+    Assert.assertEquals(3, strategy.computeDictionaryId(columnValueSelector));
 
     GroupByColumnSelectorPlus groupByColumnSelectorPlus = 
Mockito.mock(GroupByColumnSelectorPlus.class);
     
Mockito.when(groupByColumnSelectorPlus.getResultRowPosition()).thenReturn(0);
diff --git a/website/.spelling b/website/.spelling
index e3bc69e..25b64b4 100644
--- a/website/.spelling
+++ b/website/.spelling
@@ -1554,6 +1554,7 @@ D1
 D2
 D3
 druid.query.groupBy.defaultStrategy
+druid.query.groupBy.maxSelectorDictionarySize
 druid.query.groupBy.maxMergingDictionarySize
 druid.query.groupBy.maxOnDiskStorage
 druid.query.groupBy.maxResults.
@@ -1563,6 +1564,7 @@ maxResults
 orderby
 orderbys
 outputName
+pre-existing
 pushdown
 row1
 subtotalsSpec

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

Reply via email to