[ 
https://issues.apache.org/jira/browse/DRILL-6032?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16497242#comment-16497242
 ] 

ASF GitHub Bot commented on DRILL-6032:
---------------------------------------

ilooner closed pull request #1101: DRILL-6032: Made the batch sizing for 
HashAgg more accurate.
URL: https://github.com/apache/drill/pull/1101
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggBatch.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggBatch.java
index 47f1017b34..374dcdd3a4 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggBatch.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggBatch.java
@@ -25,7 +25,6 @@
 import org.apache.drill.common.exceptions.UserException;
 import org.apache.drill.common.expression.ErrorCollector;
 import org.apache.drill.common.expression.ErrorCollectorImpl;
-import org.apache.drill.common.expression.FunctionHolderExpression;
 import org.apache.drill.common.expression.IfExpression;
 import org.apache.drill.common.expression.LogicalExpression;
 import org.apache.drill.common.logical.data.NamedExpression;
@@ -65,7 +64,7 @@
 
   private HashAggregator aggregator;
   private RecordBatch incoming;
-  private LogicalExpression[] aggrExprs;
+  private ValueVectorWriteExpression[] aggrExprs;
   private TypedFieldId[] groupByOutFieldIds;
   private TypedFieldId[] aggrOutFieldIds;      // field ids for the outgoing 
batch
   private final List<Comparator> comparators;
@@ -231,7 +230,7 @@ private HashAggregator createAggregatorInternal() throws 
SchemaChangeException,
 
     int numGroupByExprs = (popConfig.getGroupByExprs() != null) ? 
popConfig.getGroupByExprs().size() : 0;
     int numAggrExprs = (popConfig.getAggrExprs() != null) ? 
popConfig.getAggrExprs().size() : 0;
-    aggrExprs = new LogicalExpression[numAggrExprs];
+    aggrExprs = new ValueVectorWriteExpression[numAggrExprs];
     groupByOutFieldIds = new TypedFieldId[numGroupByExprs];
     aggrOutFieldIds = new TypedFieldId[numAggrExprs];
 
@@ -255,7 +254,6 @@ private HashAggregator createAggregatorInternal() throws 
SchemaChangeException,
       groupByOutFieldIds[i] = container.add(vv);
     }
 
-    int extraNonNullColumns = 0; // each of SUM, MAX and MIN gets an extra 
bigint column
     for (i = 0; i < numAggrExprs; i++) {
       NamedExpression ne = popConfig.getAggrExprs().get(i);
       final LogicalExpression expr =
@@ -273,15 +271,10 @@ private HashAggregator createAggregatorInternal() throws 
SchemaChangeException,
         continue;
       }
 
-      if ( expr instanceof FunctionHolderExpression ) {
-         String funcName = ((FunctionHolderExpression) expr).getName();
-         if ( funcName.equals("sum") || funcName.equals("max") || 
funcName.equals("min") ) {extraNonNullColumns++;}
-      }
       final MaterializedField outputField = 
MaterializedField.create(ne.getRef().getAsNamePart().getName(), 
expr.getMajorType());
       @SuppressWarnings("resource")
       ValueVector vv = TypeHelper.getNewVector(outputField, 
oContext.getAllocator());
       aggrOutFieldIds[i] = container.add(vv);
-
       aggrExprs[i] = new ValueVectorWriteExpression(aggrOutFieldIds[i], expr, 
true);
     }
 
@@ -301,7 +294,7 @@ private HashAggregator createAggregatorInternal() throws 
SchemaChangeException,
         aggrExprs,
         cgInner.getWorkspaceTypes(),
         groupByOutFieldIds,
-        this.container, extraNonNullColumns * 8 /* sizeof(BigInt) */);
+        container);
 
     return agg;
   }
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggTemplate.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggTemplate.java
index 4c54080169..1a33577120 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggTemplate.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggTemplate.java
@@ -17,20 +17,16 @@
  */
 package org.apache.drill.exec.physical.impl.aggregate;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Named;
-
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
 import org.apache.drill.common.exceptions.RetryAfterSpillException;
 import org.apache.drill.common.exceptions.UserException;
 import org.apache.drill.common.expression.ExpressionPosition;
 import org.apache.drill.common.expression.FieldReference;
+import org.apache.drill.common.expression.FunctionHolderExpression;
 import org.apache.drill.common.expression.LogicalExpression;
-
+import org.apache.drill.common.map.CaseInsensitiveMap;
+import org.apache.drill.common.types.TypeProtos;
 import org.apache.drill.exec.ExecConstants;
 import org.apache.drill.exec.cache.VectorSerializer.Writer;
 import org.apache.drill.exec.compile.sig.RuntimeOverridden;
@@ -38,7 +34,8 @@
 import org.apache.drill.exec.exception.OutOfMemoryException;
 import org.apache.drill.exec.exception.SchemaChangeException;
 import org.apache.drill.exec.expr.TypeHelper;
-
+import org.apache.drill.exec.expr.ValueVectorReadExpression;
+import org.apache.drill.exec.expr.ValueVectorWriteExpression;
 import org.apache.drill.exec.memory.BaseAllocator;
 import org.apache.drill.exec.memory.BufferAllocator;
 import org.apache.drill.exec.ops.FragmentContext;
@@ -51,45 +48,42 @@
 import org.apache.drill.exec.physical.impl.common.HashTable;
 import org.apache.drill.exec.physical.impl.common.HashTableConfig;
 import org.apache.drill.exec.physical.impl.common.HashTableStats;
+import org.apache.drill.exec.physical.impl.common.HashTableTemplate;
 import org.apache.drill.exec.physical.impl.common.IndexPointer;
-
 import org.apache.drill.exec.record.RecordBatchSizer;
-
 import org.apache.drill.exec.physical.impl.spill.SpillSet;
 import org.apache.drill.exec.planner.physical.AggPrelBase;
-
+import org.apache.drill.exec.record.BatchSchema;
 import org.apache.drill.exec.record.MaterializedField;
-
 import org.apache.drill.exec.record.RecordBatch;
-import org.apache.drill.exec.record.BatchSchema;
-
-import org.apache.drill.exec.record.VectorContainer;
-
-import org.apache.drill.exec.record.TypedFieldId;
-
 import org.apache.drill.exec.record.RecordBatch.IterOutcome;
+import org.apache.drill.exec.record.TypedFieldId;
+import org.apache.drill.exec.record.VectorContainer;
 import org.apache.drill.exec.record.VectorWrapper;
 import org.apache.drill.exec.record.WritableBatch;
-
 import org.apache.drill.exec.vector.AllocationHelper;
-
 import org.apache.drill.exec.vector.FixedWidthVector;
 import org.apache.drill.exec.vector.ObjectVector;
 import org.apache.drill.exec.vector.ValueVector;
-
 import org.apache.drill.exec.vector.VariableWidthVector;
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+
+import javax.inject.Named;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 import static org.apache.drill.exec.record.RecordBatch.MAX_BATCH_SIZE;
 
 public abstract class HashAggTemplate implements HashAggregator {
-  protected static final org.slf4j.Logger logger = 
org.slf4j.LoggerFactory.getLogger(HashAggregator.class);
-
-  private static final int VARIABLE_MAX_WIDTH_VALUE_SIZE = 50;
-  private static final int VARIABLE_MIN_WIDTH_VALUE_SIZE = 8;
+  protected static final Marker hashAggDebug1 = 
MarkerFactory.getMarker("HASH_AGG_DEBUG_1");
+  protected static final Marker hashAggDebug2 = 
MarkerFactory.getMarker("HASH_AGG_DEBUG_2");
+  protected static final Marker hashAggDebugSpill = 
MarkerFactory.getMarker("HASH_AGG_DEBUG_SPILL");
 
-  private static final boolean EXTRA_DEBUG_1 = false;
-  private static final boolean EXTRA_DEBUG_2 = false;
-  private static final boolean EXTRA_DEBUG_SPILL = false;
+  protected static final org.slf4j.Logger logger = 
org.slf4j.LoggerFactory.getLogger(HashAggregator.class);
 
   // Fields needed for partitioning (the groups into partitions)
   private int numPartitions = 0; // must be 2 to the power of bitsInMask (set 
in setup())
@@ -112,15 +106,12 @@
   private int earlyPartition = 0; // which partition to return early
   private boolean retrySameIndex = false; // in case put failed during 1st 
phase - need to output early, then retry
   private boolean useMemoryPrediction = false; // whether to use memory 
prediction to decide when to spill
-  private long estMaxBatchSize = 0; // used for adjusting #partitions and 
deciding when to spill
-  private long estRowWidth = 0; // the size of the internal "row" (keys + 
values + extra columns)
-  private long estValuesRowWidth = 0; // the size of the internal values ( 
values + extra )
-  private long estOutputRowWidth = 0; // the size of the output "row" (no 
extra columns)
-  private long estValuesBatchSize = 0; // used for "reserving" memory for the 
Values batch to overcome an OOM
-  private long estOutgoingAllocSize = 0; // used for "reserving" memory for 
the Outgoing Output Values to overcome an OOM
+  private long estAccumulationBatchSize = 0; // used for adjusting #partitions 
and deciding when to spill
+  private long estAccumulationBatchRowWidth = 0; // the size of the output 
"row" (no extra columns)
+  private long estBatchHolderValuesBatchSize = 0; // used for "reserving" 
memory for the Values batch to overcome an OOM
+  private long estOutgoingBatchValuesSize = 0; // used for "reserving" memory 
for the Outgoing Output Values to overcome an OOM
   private long reserveValueBatchMemory; // keep "reserve memory" for Values 
Batch
   private long reserveOutgoingMemory; // keep "reserve memory" for the 
Outgoing (Values only) output
-  private int maxColumnWidth = VARIABLE_MIN_WIDTH_VALUE_SIZE; // to control 
memory allocation for varchars
   private long minBatchesPerPartition; // for tuning - num partitions and 
spill decision
   private long plannedBatches = 0; // account for planned, but not yet 
allocated batches
 
@@ -140,6 +131,9 @@
   private OperatorContext oContext;
   private BufferAllocator allocator;
 
+  private Map<String, Integer> keySizes;
+  // The size estimates for varchar value columns. The keys are the index of 
the varchar value columns.
+  private Map<Integer, Integer> varcharValueSizes;
   private HashTable htables[];
   private ArrayList<BatchHolder> batchHolders[];
   private int outBatchIndex[];
@@ -156,7 +150,6 @@
   private static class SpilledPartition { public int spilledBatches; public 
String spillFile; int cycleNum; int origPartn; int prevOrigPartn; }
 
   private ArrayList<SpilledPartition> spilledPartitionsList;
-  private int operatorId; // for the spill file name
 
   private IndexPointer htIdxHolder; // holder for the Hashtable's internal 
index returned by put()
   private IndexPointer outStartIdxHolder;
@@ -165,6 +158,7 @@
   private TypedFieldId[] groupByOutFieldIds;
 
   private MaterializedField[] materializedValueFields;
+  private ValueVectorWriteExpression[] valueExprs;
   private boolean allFlushed = false;
   private boolean buildComplete = false;
   private boolean handlingSpills = false; // True once starting to process 
spill files
@@ -216,19 +210,19 @@ public BatchHolder() {
           // Create a type-specific ValueVector for this value
           vector = TypeHelper.getNewVector(outputField, allocator);
 
-          // Try to allocate space to store BATCH_SIZE records. Key stored at 
index i in HashTable has its workspace
+          // Try to allocate space to store MAX_BATCH_SIZE records. Key stored 
at index i in HashTable has its workspace
           // variables (such as count, sum etc) stored at index i in HashAgg. 
HashTable and HashAgg both have
           // BatchHolders. Whenever a BatchHolder in HashAgg reaches its 
capacity, a new BatchHolder is added to
-          // HashTable. If HashAgg can't store BATCH_SIZE records in a 
BatchHolder, it leaves empty slots in current
+          // HashTable. If HashAgg can't store MAX_BATCH_SIZE records in a 
BatchHolder, it leaves empty slots in current
           // BatchHolder in HashTable, causing the HashTable to be space 
inefficient. So it is better to allocate space
-          // to fit as close to as BATCH_SIZE records.
+          // to fit as close to as MAX_BATCH_SIZE records.
           if (vector instanceof FixedWidthVector) {
-            ((FixedWidthVector) vector).allocateNew(HashTable.BATCH_SIZE);
+            ((FixedWidthVector) vector).allocateNew(MAX_BATCH_SIZE);
           } else if (vector instanceof VariableWidthVector) {
-            // This case is never used .... a varchar falls under ObjectVector 
which is allocated on the heap !
-            ((VariableWidthVector) vector).allocateNew(maxColumnWidth, 
HashTable.BATCH_SIZE);
+            // TODO once varchars are no longer stored on heap while 
aggregating we will also have to account for their sizes here
+            throw new IllegalStateException();
           } else if (vector instanceof ObjectVector) {
-            ((ObjectVector) vector).allocateNew(HashTable.BATCH_SIZE);
+            ((ObjectVector) vector).allocateNew(MAX_BATCH_SIZE);
           } else {
             vector.allocateNew();
           }
@@ -263,9 +257,7 @@ private void outputValues(IndexPointer outStartIdxHolder, 
IndexPointer outNumRec
       for (int i = batchOutputCount; i <= maxOccupiedIdx; i++) {
         try { outputRecordValues(i, batchOutputCount); }
         catch (SchemaChangeException sc) { throw new 
UnsupportedOperationException(sc);}
-        if (EXTRA_DEBUG_2) {
-          logger.debug("Outputting values to output index: {}", 
batchOutputCount);
-        }
+        logger.debug(hashAggDebug2, "Outputting values to output index: {}", 
batchOutputCount);
         batchOutputCount++;
         outNumRecordsHolder.value++;
       }
@@ -300,16 +292,15 @@ public void outputRecordValues(@Named("htRowIdx") int 
htRowIdx, @Named("outRowId
   }
 
   @Override
-  public void setup(HashAggregate hashAggrConfig, HashTableConfig htConfig, 
FragmentContext context, OperatorContext oContext,
-                    RecordBatch incoming, HashAggBatch outgoing, 
LogicalExpression[] valueExprs, List<TypedFieldId> valueFieldIds,
-                    TypedFieldId[] groupByOutFieldIds, VectorContainer 
outContainer, int extraRowBytes) throws SchemaChangeException, IOException {
+  public void setup(HashAggregate hashAggrConfig, HashTableConfig htConfig,
+                    FragmentContext context, OperatorContext oContext,
+                    RecordBatch incoming, HashAggBatch outgoing,
+                    ValueVectorWriteExpression[] valueExprs, 
List<TypedFieldId> valueFieldIds,
+                    TypedFieldId[] groupByOutFieldIds, VectorContainer 
outContainer) throws SchemaChangeException, IOException {
 
-    if (valueExprs == null || valueFieldIds == null) {
-      throw new IllegalArgumentException("Invalid aggr value exprs or 
workspace variables.");
-    }
-    if (valueFieldIds.size() < valueExprs.length) {
-      throw new IllegalArgumentException("Wrong number of workspace 
variables.");
-    }
+    Preconditions.checkNotNull(valueFieldIds);
+    Preconditions.checkNotNull(valueExprs);
+    Preconditions.checkArgument(valueFieldIds.size() >= valueExprs.length, 
"Wrong number of workspace variables.");
 
     this.context = context;
     this.stats = oContext.getStats();
@@ -318,8 +309,8 @@ public void setup(HashAggregate hashAggrConfig, 
HashTableConfig htConfig, Fragme
     this.incoming = incoming;
     this.outgoing = outgoing;
     this.outContainer = outContainer;
-    this.operatorId = hashAggrConfig.getOperatorId();
     this.useMemoryPrediction = 
context.getOptions().getOption(ExecConstants.HASHAGG_USE_MEMORY_PREDICTION_VALIDATOR);
+    this.valueExprs = valueExprs;
 
     is2ndPhase = hashAggrConfig.getAggPhase() == 
AggPrelBase.OperatorPhase.PHASE_2of2;
     isTwoPhase = hashAggrConfig.getAggPhase() != 
AggPrelBase.OperatorPhase.PHASE_1of1;
@@ -374,10 +365,6 @@ public void setup(HashAggregate hashAggrConfig, 
HashTableConfig htConfig, Fragme
     this.groupByOutFieldIds = groupByOutFieldIds; // retain these for 
delayedSetup, and to allow recreating hash tables (after a spill)
     numGroupByOutFields = groupByOutFieldIds.length;
 
-    // Start calculating the row widths (with the extra columns; the rest 
would be done in updateEstMaxBatchSize() )
-    estRowWidth = extraRowBytes;
-    estValuesRowWidth = extraRowBytes;
-
     doSetup(incoming);
   }
 
@@ -397,24 +384,22 @@ private void delayedSetup() {
     }
     numPartitions = BaseAllocator.nextPowerOfTwo(numPartitions); // in case 
not a power of 2
 
-    if ( schema == null ) { estValuesBatchSize = estOutgoingAllocSize = 
estMaxBatchSize = 0; } // incoming was an empty batch
-    else {
-      // Estimate the max batch size; should use actual data (e.g. lengths of 
varchars)
-      updateEstMaxBatchSize(incoming);
-    }
+    // Estimate the max batch size; should use actual data (e.g. lengths of 
varchars)
+    updateEstMaxBatchSize(incoming);
+
     // create "reserved memory" and adjust the memory limit down
-    reserveValueBatchMemory = reserveOutgoingMemory = estValuesBatchSize ;
-    long newMemoryLimit = allocator.getLimit() - reserveValueBatchMemory - 
reserveOutgoingMemory ;
-    long memAvail = newMemoryLimit - allocator.getAllocatedMemory();
-    if ( memAvail <= 0 ) { throw new OutOfMemoryException("Too little memory 
available"); }
-    allocator.setLimit(newMemoryLimit);
+    if (!restoreReservedMemory()) {
+      throw new OutOfMemoryException("Too little memory available");
+    }
+
+    long memAvail = allocator.getLimit() - allocator.getAllocatedMemory();
 
     if ( !canSpill ) { // single phase, or spill disabled by configuation
       numPartitions = 1; // single phase should use only a single partition 
(to save memory)
     } else { // two phase
       // Adjust down the number of partitions if needed - when the memory 
available can not hold as
       // many batches (configurable option), plus overhead (e.g. hash table, 
links, hash values))
-      while ( numPartitions * ( estMaxBatchSize * minBatchesPerPartition + 2 * 
1024 * 1024) > memAvail ) {
+      while (numPartitions * (estAccumulationBatchSize * 
minBatchesPerPartition) > memAvail) {
         numPartitions /= 2;
         if ( numPartitions < 2) {
           if (is2ndPhase) {
@@ -465,7 +450,7 @@ private void delayedSetup() {
     for (int i = 0; i < numPartitions; i++ ) {
       try {
         this.htables[i] = 
baseHashTable.createAndSetupHashTable(groupByOutFieldIds, numPartitions);
-        this.htables[i].setMaxVarcharSize(maxColumnWidth);
+        this.htables[i].setKeySizes(keySizes);
       } catch (ClassTransformationException e) {
         throw UserException.unsupportedError(e)
             .message("Code generation error - likely an error in the code.")
@@ -516,46 +501,128 @@ private void initializeSetup(RecordBatch newIncoming) 
throws SchemaChangeExcepti
    * @param incoming
    */
   private void updateEstMaxBatchSize(RecordBatch incoming) {
-    if ( estMaxBatchSize > 0 ) { return; }  // no handling of a schema (or 
varchar) change
     // Use the sizer to get the input row width and the length of the longest 
varchar column
-    RecordBatchSizer sizer = new RecordBatchSizer(incoming);
-    logger.trace("Incoming sizer: {}",sizer);
-    // An empty batch only has the schema, can not tell actual length of 
varchars
-    // else use the actual varchars length, each capped at 50 (to match the 
space allocation)
-    long estInputRowWidth = sizer.rowCount() == 0 ? sizer.stdRowWidth() : 
sizer.netRowWidthCap50();
+    final RecordBatchSizer incomingColumnSizes = new 
RecordBatchSizer(incoming);
+    final Map<String, RecordBatchSizer.ColumnSize> columnSizeMap = 
incomingColumnSizes.columns();
+    keySizes = CaseInsensitiveMap.newHashMap();
+    varcharValueSizes = Maps.newHashMap();
+
+    logger.trace("Incoming sizer: {}",incomingColumnSizes);
+
+    for (int columnIndex = 0; columnIndex < numGroupByOutFields; 
columnIndex++) {
+      final VectorWrapper vectorWrapper = 
outContainer.getValueVector(columnIndex);
+      final String columnName = vectorWrapper.getField().getName();
+      final int columnSize = columnSizeMap.get(columnName).getStdOrEstSize();
+      keySizes.put(columnName, columnSize);
+      estAccumulationBatchRowWidth += columnSize;
+    }
+
+    // estBatchHolderValuesRowWidth represents the size of a row in value 
BatchHolder that is used for accumulations.
+    long estBatchHolderValuesRowWidth = 0;
+    // estOutputValuesRowWidth represents the size of the value rows in the 
batch that is actually output.
+    // Does not include extra phantom columns from DRILL-5728.
+    long estOutputValuesRowWidth = 0;
+
+    // TODO DRILL-5728 - This code is necessary because the generated 
BatchHolders add extra BigInt columns
+    // to store a flag indicating if a row is null or not. Ideally we should 
not generate these
+    // extra BigInt columns, but fixing this is a big task. Once DRILL-5728 is 
fixed this code block
+    // should be deleted
+    {
+      final TypeProtos.MajorType majorType = TypeProtos.MajorType.newBuilder()
+        .setMinorType(TypeProtos.MinorType.BIGINT)
+        .build();
+      final int bigIntSize = TypeHelper.getSize(majorType);
+
+      for (ValueVectorWriteExpression valueExpr : valueExprs) {
+        final LogicalExpression childExpression = valueExpr.getChild();
+
+        if (childExpression instanceof FunctionHolderExpression) {
+          final FunctionHolderExpression funcExpression = 
((FunctionHolderExpression) childExpression);
+          final String funcName = funcExpression.getName();
+
+          if (funcName.equals("sum") || funcName.equals("max") || 
funcName.equals("min")) {
+            estBatchHolderValuesRowWidth += bigIntSize;
+            estAccumulationBatchRowWidth += bigIntSize;
+          }
+        }
+      }
+    }
 
-    // Get approx max (varchar) column width to get better memory allocation
-    maxColumnWidth = Math.max(sizer.maxAvgColumnSize(), 
VARIABLE_MIN_WIDTH_VALUE_SIZE);
-    maxColumnWidth = Math.min(maxColumnWidth, VARIABLE_MAX_WIDTH_VALUE_SIZE);
+    // We need to store the estimated size of varchar columns that are being 
aggregated so that we can estimate the out batch size
+    // TODO after varchars are no longer stored on heap we will also have to 
use these estimates for the BatchHolders.
+    for (int valueIndex = 0; valueIndex < valueExprs.length; valueIndex++) {
+      final ValueVectorWriteExpression valueExpr = valueExprs[valueIndex];
+      final LogicalExpression childExpression = valueExpr.getChild();
+
+      if (childExpression instanceof FunctionHolderExpression) {
+        final FunctionHolderExpression funcExpression = 
((FunctionHolderExpression) childExpression);
+
+        for (LogicalExpression logicalExpression: funcExpression.args) {
+          if (logicalExpression instanceof ValueVectorReadExpression) {
+            final ValueVectorReadExpression valueExpression =
+              (ValueVectorReadExpression) logicalExpression;
+
+            if 
(valueExpression.getMajorType().getMinorType().equals(TypeProtos.MinorType.VARCHAR))
 {
+              // If the argument to the aggregation function was a varchar, we 
need to save the size estimate
+              // for the column
+              final VectorWrapper columnWrapper = 
incoming.getValueAccessorById(null, valueExpression.getFieldId().getFieldIds());
+              final String columnName = columnWrapper.getField().getName();
+              final RecordBatchSizer.ColumnSize columnSizer = 
columnSizeMap.get(columnName);
+              varcharValueSizes.put(numGroupByOutFields + valueIndex, 
columnSizer.estSize);
+              break;
+            }
+          }
+        }
+      }
+    }
 
-    //
-    // Calculate the estimated max (internal) batch (i.e. Keys batch + Values 
batch) size
-    // (which is used to decide when to spill)
-    // Also calculate the values batch size (used as a reserve to overcome an 
OOM)
-    //
-    Iterator<VectorWrapper<?>> outgoingIter = outContainer.iterator();
-    int fieldId = 0;
-    while (outgoingIter.hasNext()) {
-      ValueVector vv = outgoingIter.next().getValueVector();
-      MaterializedField mr = vv.getField();
-      int fieldSize = vv instanceof VariableWidthVector ? maxColumnWidth :
-          TypeHelper.getSize(mr.getType());
-      estRowWidth += fieldSize;
-      estOutputRowWidth += fieldSize;
-      if ( fieldId < numGroupByOutFields ) { fieldId++; }
-      else { estValuesRowWidth += fieldSize; }
+    for (int columnIndex = numGroupByOutFields; columnIndex < 
outContainer.getNumberOfColumns(); columnIndex++) {
+      VectorWrapper vectorWrapper = outContainer.getValueVector(columnIndex);
+      RecordBatchSizer.ColumnSize columnSize = new 
RecordBatchSizer.ColumnSize(vectorWrapper.getValueVector());
+
+      if (columnSize.hasStdSize()) {
+        // If the column is fixed width we know the size, otherwise the column 
is a varchar which is
+        // stored on heap in the BatchHolders so we can assume that the amount 
of direct memory it consumes is 0.
+        estAccumulationBatchRowWidth += columnSize.getStdSize();
+        estBatchHolderValuesRowWidth += columnSize.getStdSize();
+        estOutputValuesRowWidth += columnSize.getStdSize();
+      } else {
+        // Include the size of varchar values which are transfered back to 
direct memory when copied from the BatchHolder to
+        // the out container.
+        estOutputValuesRowWidth += varcharValueSizes.get(columnIndex);
+      }
     }
-    // multiply by the max number of rows in a batch to get the final 
estimated max size
-    estMaxBatchSize = Math.max(estRowWidth, estInputRowWidth) * MAX_BATCH_SIZE;
-    // (When there are no aggr functions, use '1' as later code relies on this 
size being non-zero)
-    estValuesBatchSize = Math.max(estValuesRowWidth, 1) * MAX_BATCH_SIZE;
-    estOutgoingAllocSize = estValuesBatchSize; // initially assume same size
 
-    logger.trace("{} phase. Estimated internal row width: {} Values row width: 
{} batch size: {}  memory limit: {}  max column width: {}",
-        
isTwoPhase?(is2ndPhase?"2nd":"1st"):"Single",estRowWidth,estValuesRowWidth,estMaxBatchSize,allocator.getLimit(),maxColumnWidth);
+    // Hash table overhead may double what is actually required.
+    long hashTableOverhead = 
RecordBatchSizer.multiplyByFactors(HashTableTemplate.ENTRY_OVERHEAD_BYTES * 
MAX_BATCH_SIZE);
+
+    // This is the size of an accumulation batch. An accumulation batch 
includes the keys stored in the hashtable and the values stored in the 
BatchHolder.
+    // It also includes the overhead of the links, indices, and hash values 
stored in the hashtable
+    estAccumulationBatchSize = 
RecordBatchSizer.multiplyByFactors(estAccumulationBatchRowWidth * 
MAX_BATCH_SIZE);
+    estAccumulationBatchSize += hashTableOverhead;
+    estAccumulationBatchSize = Math.max(estAccumulationBatchSize, 1);
+    // WARNING! (When there are no aggr functions, use '1' as later code 
relies on this size being non-zero)
+    // Note: estBatchHolderValuesBatchSize cannot be 0 because a zero value 
for estBatchHolderValuesBatchSize will cause reserveValueBatchMemory to have a 
value of 0. And
+    // a reserveValueBatchMemory value of 0 has multiple meanings in different 
contexts. So estBatchHolderValuesBatchSize has an enforced minimum value of 1, 
without this
+    // estBatchHolderValuesBatchSize could have a value of 0 in the case were 
there are no value columns and all the columns are key columns.
+
+    // This includes the values stored in the BatchHolder and the has table 
overhead
+    estBatchHolderValuesBatchSize = 
RecordBatchSizer.multiplyByFactors(estBatchHolderValuesRowWidth * 
MAX_BATCH_SIZE);
+    estBatchHolderValuesBatchSize += hashTableOverhead;
+    estBatchHolderValuesBatchSize = Math.max(estBatchHolderValuesBatchSize, 1);
+    // This includes the values stored in the outContainer. Note the phantom 
columns are not included in this size
+    // because the out container does not have phantom columns, only the 
BatchHolder does. Also note that we are not
+    // including the size of the key columns in this estimate because the key 
columns stored in the hash table are transferred
+    // to the out container, and the size of the key columns are already 
accounted for in estAccumulationBatchSize
+    estOutgoingBatchValuesSize = 
Math.max(RecordBatchSizer.multiplyByFactors(estOutputValuesRowWidth * 
MAX_BATCH_SIZE), 1);;
+
+    if (logger.isTraceEnabled()) {
+      logger.trace("{} phase. Estimated internal row width: {} Values row 
width: {} batch size: {}  memory limit: {}",
+        isTwoPhase ? (is2ndPhase ? "2nd" : "1st") : "Single", 
estAccumulationBatchRowWidth, estBatchHolderValuesRowWidth, 
estAccumulationBatchSize, allocator.getLimit());
+    }
 
-    if ( estMaxBatchSize > allocator.getLimit() ) {
-      logger.warn("HashAggregate: Estimated max batch size {} is larger than 
the memory limit {}",estMaxBatchSize,allocator.getLimit());
+    if ( estAccumulationBatchSize > allocator.getLimit() ) {
+      logger.warn("HashAggregate: Estimated max batch size {} is larger than 
the memory limit {}", estAccumulationBatchSize,allocator.getLimit());
     }
   }
 
@@ -579,16 +646,11 @@ public AggOutcome doWork() {
         delayedSetup();
       }
 
-      //
-      //  loop through existing records in this batch, aggregating the values 
as necessary.
-      //
-      if (EXTRA_DEBUG_1) {
-        logger.debug("Starting outer loop of doWork()...");
-      }
+      // loop through existing records in this batch, aggregating the values 
as necessary.
+      logger.debug(hashAggDebug1, "Starting outer loop of doWork()...");
+
       while (underlyingIndex < currentBatchRecordCount) {
-        if (EXTRA_DEBUG_2) {
-          logger.debug("Doing loop with values underlying {}, current {}", 
underlyingIndex, currentIndex);
-        }
+        logger.debug(hashAggDebug2, "Doing loop with values underlying {}, 
current {}", underlyingIndex, currentIndex);
         checkGroupAndAggrValues(currentIndex);
 
         if ( retrySameIndex ) { retrySameIndex = false; }  // need to retry 
this row (e.g. we had an OOM)
@@ -596,15 +658,13 @@ public AggOutcome doWork() {
 
         // If adding a group discovered a memory pressure during 1st phase, 
then start
         // outputing some partition downstream in order to free memory.
-        if ( earlyOutput ) {
+        if (earlyOutput) {
           outputCurrentBatch();
           return AggOutcome.RETURN_OUTCOME;
         }
       }
 
-      if (EXTRA_DEBUG_1) {
-        logger.debug("Processed {} records", underlyingIndex);
-      }
+      logger.debug(hashAggDebug1, "Processed {} records", underlyingIndex);
 
       // Cleanup the previous batch since we are done processing it.
       for (VectorWrapper<?> v : incoming) {
@@ -615,26 +675,14 @@ public AggOutcome doWork() {
       // from one of the spill files (The spill case is handled differently 
here to avoid
       // collecting stats on the spilled records)
       //
-      long memAllocBeforeNext = allocator.getAllocatedMemory();
       if ( handlingSpills ) {
         outcome = incoming.next(); // get it from the SpilledRecordBatch
       } else {
         // Get the next RecordBatch from the incoming (i.e. upstream operator)
         outcome = outgoing.next(0, incoming);
       }
-      long memAllocAfterNext = allocator.getAllocatedMemory();
-      long incomingBatchSize = memAllocAfterNext - memAllocBeforeNext;
-
-      // If incoming batch is bigger than our estimate - adjust the estimate 
to match
-      if ( estMaxBatchSize < incomingBatchSize) {
-        logger.debug("Found a bigger next {} batch: {} , prior estimate was: 
{}, mem allocated {}",handlingSpills ? "spill" : "incoming",
-            incomingBatchSize, estMaxBatchSize, memAllocAfterNext);
-        estMaxBatchSize = incomingBatchSize;
-      }
 
-      if (EXTRA_DEBUG_1) {
-        logger.debug("Received IterOutcome of {}", outcome);
-      }
+      logger.debug(hashAggDebug1, "Received IterOutcome of {}", outcome);
 
       // Handle various results from getting the next batch
       switch (outcome) {
@@ -643,9 +691,7 @@ public AggOutcome doWork() {
           return AggOutcome.RETURN_OUTCOME;
 
         case OK_NEW_SCHEMA:
-          if (EXTRA_DEBUG_1) {
-            logger.debug("Received new schema.  Batch has {} records.", 
incoming.getRecordCount());
-          }
+          logger.debug(hashAggDebug1, "Received new schema.  Batch has {} 
records.", incoming.getRecordCount());
           this.cleanup();
           // TODO: new schema case needs to be handled appropriately
           return AggOutcome.UPDATE_AGGREGATOR;
@@ -655,9 +701,7 @@ public AggOutcome doWork() {
 
           resetIndex(); // initialize index (a new batch needs to be processed)
 
-          if (EXTRA_DEBUG_1) {
-            logger.debug("Continue to start processing the next batch");
-          }
+          logger.debug(hashAggDebug1, "Continue to start processing the next 
batch");
           break;
 
         case NONE:
@@ -711,21 +755,29 @@ private void useReservedOutgoingMemory() {
    *  Restore the reserve memory (both)
    *
    */
-  private void restoreReservedMemory() {
+  private boolean restoreReservedMemory() {
+    boolean success = true;
+
     if ( 0 == reserveOutgoingMemory ) { // always restore OutputValues first 
(needed for spilling)
       long memAvail = allocator.getLimit() - allocator.getAllocatedMemory();
-      if ( memAvail > estOutgoingAllocSize) {
-        allocator.setLimit(allocator.getLimit() - estOutgoingAllocSize);
-        reserveOutgoingMemory = estOutgoingAllocSize;
+      if ( memAvail > estOutgoingBatchValuesSize) {
+        allocator.setLimit(allocator.getLimit() - estOutgoingBatchValuesSize);
+        reserveOutgoingMemory = estOutgoingBatchValuesSize;
+      } else {
+        success = false;
       }
     }
     if ( 0 == reserveValueBatchMemory ) {
       long memAvail = allocator.getLimit() - allocator.getAllocatedMemory();
-      if ( memAvail > estValuesBatchSize) {
-        allocator.setLimit(allocator.getLimit() - estValuesBatchSize);
-        reserveValueBatchMemory = estValuesBatchSize;
+      if ( memAvail > estBatchHolderValuesBatchSize) {
+        allocator.setLimit(allocator.getLimit() - 
estBatchHolderValuesBatchSize);
+        reserveValueBatchMemory = estBatchHolderValuesBatchSize;
+      } else {
+        success = false;
       }
     }
+
+    return success;
   }
   /**
    *   Allocate space for the returned aggregate columns
@@ -733,28 +785,34 @@ private void restoreReservedMemory() {
    * @param records
    */
   private void allocateOutgoing(int records) {
-    // Skip the keys and only allocate for outputting the workspace values
-    // (keys will be output through splitAndTransfer)
-    Iterator<VectorWrapper<?>> outgoingIter = outContainer.iterator();
-    for (int i = 0; i < numGroupByOutFields; i++) {
-      outgoingIter.next();
-    }
-
     // try to preempt an OOM by using the reserved memory
     useReservedOutgoingMemory();
     long allocatedBefore = allocator.getAllocatedMemory();
 
-    while (outgoingIter.hasNext()) {
+    for (int columnIndex = numGroupByOutFields; columnIndex < 
outContainer.getNumberOfColumns(); columnIndex++) {
+      final VectorWrapper wrapper = outContainer.getValueVector(columnIndex);
       @SuppressWarnings("resource")
-      ValueVector vv = outgoingIter.next().getValueVector();
+      final ValueVector vv = wrapper.getValueVector();
+
+      final RecordBatchSizer.ColumnSize columnSizer = new 
RecordBatchSizer.ColumnSize(wrapper.getValueVector());
+      int columnSize;
+
+      if (columnSizer.hasStdSize()) {
+        // For fixed width vectors we know the size of each record
+        columnSize = columnSizer.getStdSize();
+      } else {
+        // For var chars we need to use the input estimate
+        columnSize = varcharValueSizes.get(columnIndex);
+      }
 
-      AllocationHelper.allocatePrecomputedChildCount(vv, records, 
maxColumnWidth, 0);
+      // TODO currently the childValCount is set to 0 since HashAgg does not 
support aggregating repeated types. If we add support
+      // for repeated types we should use the elementCount estimated from the 
input batch as the value count.
+      AllocationHelper.allocatePrecomputedChildCount(vv, records, columnSize, 
0);
     }
 
     long memAdded = allocator.getAllocatedMemory() - allocatedBefore;
-    if ( memAdded > estOutgoingAllocSize ) {
-      logger.trace("Output values allocated {} but the estimate was only {}. 
Adjusting ...",memAdded,estOutgoingAllocSize);
-      estOutgoingAllocSize = memAdded;
+    if ( memAdded > estOutgoingBatchValuesSize) {
+      logger.trace("Output values allocated {} but the estimate was only {}.", 
memAdded, estOutgoingBatchValuesSize);
     }
     // try to restore the reserve
     restoreReservedMemory();
@@ -816,7 +874,6 @@ public void cleanup() {
     if ( newIncoming != null ) { newIncoming.close();  }
     spillSet.close(); // delete the spill directory(ies)
     htIdxHolder = null;
-    materializedValueFields = null;
     outStartIdxHolder = null;
     outNumRecordsHolder = null;
   }
@@ -920,9 +977,7 @@ private void spillAPartition(int part) {
 
     ArrayList<BatchHolder> currPartition = batchHolders[part];
     rowsInPartition = 0;
-    if ( EXTRA_DEBUG_SPILL ) {
-      logger.debug("HashAggregate: Spilling partition {} current cycle {} part 
size {}", part, cycleNum, currPartition.size());
-    }
+    logger.debug(hashAggDebugSpill, "HashAggregate: Spilling partition {} 
current cycle {} part size {}", part, cycleNum, currPartition.size());
 
     if ( currPartition.size() == 0 ) { return; } // in case empty - nothing to 
spill
 
@@ -956,21 +1011,8 @@ private void spillAPartition(int part) {
       this.htables[part].outputKeys(currOutBatchIndex, this.outContainer, 
outStartIdxHolder.value, outNumRecordsHolder.value, numPendingOutput);
 
       // set the value count for outgoing batch value vectors
-      /* int i = 0; */
       for (VectorWrapper<?> v : outgoing) {
         v.getValueVector().getMutator().setValueCount(numOutputRecords);
-        /*
-        // print out the first row to be spilled ( varchar, varchar, bigint )
-        try {
-          if (i++ < 2) {
-            NullableVarCharVector vv = ((NullableVarCharVector) 
v.getValueVector());
-            logger.info("FIRST ROW = {}", vv.getAccessor().get(0));
-          } else {
-            NullableBigIntVector vv = ((NullableBigIntVector) 
v.getValueVector());
-            logger.info("FIRST ROW = {}", vv.getAccessor().get(0));
-          }
-        } catch (Exception e) { logger.info("While printing the first row - 
Got an exception = {}",e); }
-        */
       }
 
       outContainer.setRecordCount(numPendingOutput);
@@ -997,10 +1039,7 @@ private void addBatchHolder(int part) {
 
     BatchHolder bh = newBatchHolder();
     batchHolders[part].add(bh);
-    if (EXTRA_DEBUG_1) {
-      logger.debug("HashAggregate: Added new batch; num batches = {}.", 
batchHolders[part].size());
-    }
-
+    logger.debug(hashAggDebug1, "HashAggregate: Added new batch; num batches = 
{}.", batchHolders[part].size());
     bh.setup();
   }
 
@@ -1103,10 +1142,8 @@ public AggIterOutcome outputCurrentBatch() {
           if ( cycleNum == 4 ) { logger.warn("QUATERNARY SPILLING "); }
           if ( cycleNum == 5 ) { logger.warn("QUINARY SPILLING "); }
         }
-        if ( EXTRA_DEBUG_SPILL ) {
-          logger.debug("Start reading spilled partition {} (prev {}) from 
cycle {} (with {} batches). More {} spilled partitions left.",
+        logger.debug(hashAggDebugSpill, "Start reading spilled partition {} 
(prev {}) from cycle {} (with {} batches). More {} spilled partitions left.",
               sp.origPartn, sp.prevOrigPartn, sp.cycleNum, sp.spilledBatches, 
spilledPartitionsList.size());
-        }
         return AggIterOutcome.AGG_RESTART;
       }
 
@@ -1128,11 +1165,11 @@ public AggIterOutcome outputCurrentBatch() {
     currPartition.get(currOutBatchIndex).outputValues(outStartIdxHolder, 
outNumRecordsHolder);
     int numOutputRecords = outNumRecordsHolder.value;
 
-    if (EXTRA_DEBUG_1) {
-      logger.debug("After output values: outStartIdx = {}, outNumRecords = 
{}", outStartIdxHolder.value, outNumRecordsHolder.value);
-    }
+    logger.debug(hashAggDebug1, "After output values: outStartIdx = {}, 
outNumRecords = {}", outStartIdxHolder.value, outNumRecordsHolder.value);
+
+    this.htables[partitionToReturn].outputKeys(currOutBatchIndex, 
outContainer, outStartIdxHolder.value, outNumRecordsHolder.value, 
numPendingOutput);
 
-    this.htables[partitionToReturn].outputKeys(currOutBatchIndex, 
this.outContainer, outStartIdxHolder.value, outNumRecordsHolder.value, 
numPendingOutput);
+    outContainer.buildSchema(BatchSchema.SelectionVectorMode.NONE);
 
     // set the value count for outgoing batch value vectors
     for (VectorWrapper<?> v : outgoing) {
@@ -1141,41 +1178,31 @@ public AggIterOutcome outputCurrentBatch() {
 
     this.outcome = IterOutcome.OK;
 
-    if ( EXTRA_DEBUG_SPILL && is2ndPhase ) {
-      logger.debug("So far returned {} + SpilledReturned {}  total {} (spilled 
{})",rowsNotSpilled,rowsSpilledReturned,
-        rowsNotSpilled+rowsSpilledReturned,
-        rowsSpilled);
+    if (is2ndPhase) {
+      logger.debug(hashAggDebugSpill, "So far returned {} + SpilledReturned {} 
 total {} (spilled {})",
+        rowsNotSpilled, rowsSpilledReturned, rowsNotSpilled + 
rowsSpilledReturned, rowsSpilled);
     }
 
     lastBatchOutputCount = numOutputRecords;
     outBatchIndex[partitionToReturn]++;
     // if just flushed the last batch in the partition
     if (outBatchIndex[partitionToReturn] == currPartition.size()) {
+      logger.debug(hashAggDebugSpill, "HashAggregate: {} Flushed partition {} 
with {} batches total {} rows",
+        earlyOutput ? "(Early)" : "", partitionToReturn, 
outBatchIndex[partitionToReturn], rowsInPartition);
 
-      if ( EXTRA_DEBUG_SPILL ) {
-        logger.debug("HashAggregate: {} Flushed partition {} with {} batches 
total {} rows",
-            earlyOutput ? "(Early)" : "",
-            partitionToReturn, outBatchIndex[partitionToReturn], 
rowsInPartition);
-      }
       rowsInPartition = 0; // reset to count for the next partition
 
       // deallocate memory used by this partition, and re-initialize
       reinitPartition(partitionToReturn);
 
       if ( earlyOutput ) {
-
-        if ( EXTRA_DEBUG_SPILL ) {
-          logger.debug("HASH AGG: Finished (early) re-init partition {}, mem 
allocated: {}", earlyPartition, allocator.getAllocatedMemory());
-        }
+        logger.debug(hashAggDebugSpill, "HASH AGG: Finished (early) re-init 
partition {}, mem allocated: {}", earlyPartition, 
allocator.getAllocatedMemory());
         outBatchIndex[earlyPartition] = 0; // reset, for next time
         earlyOutput = false ; // done with early output
       }
       else if ( (partitionToReturn + 1 == numPartitions) && 
spilledPartitionsList.isEmpty() ) { // last partition ?
-
         allFlushed = true; // next next() call will return NONE
-
         logger.trace("HashAggregate: All batches flushed.");
-
         // cleanup my internal state since there is nothing more to return
         this.cleanup();
       }
@@ -1213,11 +1240,25 @@ private String getOOMErrorMsg(String prefix) {
       errmsg = "Too little memory available to operator to facilitate 
spilling.";
     } else { // a bug ?
       errmsg = prefix + " OOM at " + (is2ndPhase ? "Second Phase" : "First 
Phase") + ". Partitions: " + numPartitions +
-      ". Estimated batch size: " + estMaxBatchSize + ". values size: " + 
estValuesBatchSize + ". Output alloc size: " + estOutgoingAllocSize;
+      ". Estimated batch size: " + estAccumulationBatchSize + ". values size: 
" + estBatchHolderValuesBatchSize + ". Output alloc size: " + 
estOutgoingBatchValuesSize;
       if ( plannedBatches > 0 ) { errmsg += ". Planned batches: " + 
plannedBatches; }
       if ( rowsSpilled > 0 ) { errmsg += ". Rows spilled so far: " + 
rowsSpilled; }
     }
     errmsg += " Memory limit: " + allocator.getLimit() + " so far allocated: " 
+ allocator.getAllocatedMemory() + ". ";
+    errmsg += "\nBatch holders in memory:\n";
+
+    // Print the number of batches we are holding in memory for each partition
+    for (int partition = 0; partition < batchHolders.length; partition++) {
+      List<BatchHolder> partitionHolders = batchHolders[partition];
+
+      int num = 0;
+
+      if (partitionHolders != null) {
+        num = partitionHolders.size();
+      }
+
+      errmsg += "Partition " + partition + " num batches: " + num + "\n";
+    }
 
     return errmsg;
   }
@@ -1229,37 +1270,10 @@ private void checkGroupAndAggrValues(int 
incomingRowIdx) {
     assert incomingRowIdx >= 0;
     assert ! earlyOutput;
 
-    /** for debugging
-     Object tmp = (incoming).getValueAccessorById(0, 
BigIntVector.class).getValueVector();
-     BigIntVector vv0 = null;
-     BigIntHolder holder = null;
-
-     if (tmp != null) {
-     vv0 = ((BigIntVector) tmp);
-     holder = new BigIntHolder();
-     holder.value = vv0.getAccessor().get(incomingRowIdx) ;
-     }
-     */
-    /*
-    if ( handlingSpills && ( incomingRowIdx == 0 ) ) {
-      // for debugging -- show the first row from a spilled batch
-      Object tmp0 = 
(incoming).getValueAccessorById(NullableVarCharVector.class, 
0).getValueVector();
-      Object tmp1 = 
(incoming).getValueAccessorById(NullableVarCharVector.class, 
1).getValueVector();
-      Object tmp2 = 
(incoming).getValueAccessorById(NullableBigIntVector.class, 2).getValueVector();
-
-      if (tmp0 != null && tmp1 != null && tmp2 != null) {
-        NullableVarCharVector vv0 = ((NullableVarCharVector) tmp0);
-        NullableVarCharVector vv1 = ((NullableVarCharVector) tmp1);
-        NullableBigIntVector  vv2 = ((NullableBigIntVector) tmp2);
-        logger.debug("The first row = {} , {} , {}", 
vv0.getAccessor().get(incomingRowIdx), vv1.getAccessor().get(incomingRowIdx), 
vv2.getAccessor().get(incomingRowIdx));
-      }
-    }
-    */
     // The hash code is computed once, then its lower bits are used to 
determine the
     // partition to use, and the higher bits determine the location in the 
hash table.
     int hashCode;
     try {
-      // htables[0].updateBatches();
       hashCode = htables[0].getHashCode(incomingRowIdx);
     } catch (SchemaChangeException e) {
       throw new UnsupportedOperationException("Unexpected schema change", e);
@@ -1335,14 +1349,12 @@ private void checkGroupAndAggrValues(int 
incomingRowIdx) {
         logger.trace("MEMORY CHECK AGG: allocated now {}, added {}, total 
(with HT) added {}", allocator.getAllocatedMemory(),
             aggValuesAddedMem, totalAddedMem);
         // resize the batch estimates if needed (e.g., varchars may take more 
memory than estimated)
-        if (totalAddedMem > estMaxBatchSize) {
-          logger.trace("Adjusting Batch size estimate from {} to {}", 
estMaxBatchSize, totalAddedMem);
-          estMaxBatchSize = totalAddedMem;
+        if (totalAddedMem > estAccumulationBatchSize) {
+          logger.trace("Estimated memory needed {}, actual memory used {}", 
estAccumulationBatchSize, totalAddedMem);
           needToCheckIfSpillIsNeeded = true;
         }
-        if (aggValuesAddedMem > estValuesBatchSize) {
-          logger.trace("Adjusting Values Batch size from {} to 
{}",estValuesBatchSize, aggValuesAddedMem);
-          estValuesBatchSize = aggValuesAddedMem;
+        if (aggValuesAddedMem > estBatchHolderValuesBatchSize) {
+          logger.trace("Estimate memory needed {}, actual memory used {}", 
estBatchHolderValuesBatchSize, aggValuesAddedMem);
           needToCheckIfSpillIsNeeded = true;
         }
       } catch (OutOfMemoryException exc) {
@@ -1388,18 +1400,16 @@ private void spillIfNeeded(int currentPartition, 
boolean forceSpill) {
     long maxMemoryNeeded = 0;
     if ( !forceSpill ) { // need to check the memory in order to decide
       // calculate the (max) new memory needed now; plan ahead for at least 
MIN batches
-      maxMemoryNeeded = minBatchesPerPartition * Math.max(1, plannedBatches) * 
(estMaxBatchSize + MAX_BATCH_SIZE * (4 + 4 /* links + hash-values */));
+      maxMemoryNeeded = minBatchesPerPartition * Math.max(1, plannedBatches) * 
estAccumulationBatchSize;
       // Add the (max) size of the current hash table, in case it will double
       int maxSize = 1;
       for (int insp = 0; insp < numPartitions; insp++) {
         maxSize = Math.max(maxSize, batchHolders[insp].size());
       }
-      maxMemoryNeeded += MAX_BATCH_SIZE * 2 * 2 * 4 * maxSize; // 2 - double, 
2 - max when %50 full, 4 - Uint4
 
       // log a detailed debug message explaining why a spill may be needed
       logger.trace("MEMORY CHECK: Allocated mem: {}, agg phase: {}, trying to 
add to partition {} with {} batches. " + "Max memory needed {}, Est batch size 
{}, mem limit {}",
-          allocator.getAllocatedMemory(), isTwoPhase ? (is2ndPhase ? "2ND" : 
"1ST") : "Single", currentPartition, batchHolders[currentPartition].size(), 
maxMemoryNeeded,
-          estMaxBatchSize, allocator.getLimit());
+          allocator.getAllocatedMemory(), isTwoPhase ? (is2ndPhase ? "2ND" : 
"1ST") : "Single", currentPartition, batchHolders[currentPartition].size(), 
maxMemoryNeeded, estAccumulationBatchSize, allocator.getLimit());
     }
     //
     //   Spill if (forced, or) the allocated memory plus the memory needed 
exceed the memory limit.
@@ -1455,10 +1465,7 @@ private void spillIfNeeded(int currentPartition, boolean 
forceSpill) {
         // 1st phase need to return a partition early in order to free some 
memory
         earlyOutput = true;
         earlyPartition = victimPartition;
-
-        if ( EXTRA_DEBUG_SPILL ) {
-          logger.debug("picked partition {} for early output", 
victimPartition);
-        }
+        logger.debug(hashAggDebugSpill, "picked partition {} for early 
output", victimPartition);
       }
     }
   }
@@ -1492,7 +1499,7 @@ private void updateStats(HashTable[] htables) {
     }
     if ( rowsReturnedEarly > 0 ) {
       stats.setLongStat(Metric.SPILL_MB, // update stats - est. total MB 
returned early
-          (int) Math.round( rowsReturnedEarly * estOutputRowWidth / 1024.0D / 
1024.0));
+          (int) Math.round( rowsReturnedEarly * estAccumulationBatchRowWidth / 
1024.0D / 1024.0));
     }
   }
 
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggregator.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggregator.java
index 3384e671d5..bad3ea370a 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggregator.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/aggregate/HashAggregator.java
@@ -20,10 +20,10 @@
 import java.io.IOException;
 import java.util.List;
 
-import org.apache.drill.common.expression.LogicalExpression;
 import org.apache.drill.exec.compile.TemplateClassDefinition;
 import org.apache.drill.exec.exception.ClassTransformationException;
 import org.apache.drill.exec.exception.SchemaChangeException;
+import org.apache.drill.exec.expr.ValueVectorWriteExpression;
 import org.apache.drill.exec.ops.FragmentContext;
 import org.apache.drill.exec.ops.OperatorContext;
 import org.apache.drill.exec.physical.config.HashAggregate;
@@ -34,7 +34,6 @@
 import org.apache.drill.exec.record.VectorContainer;
 
 public interface HashAggregator {
-
   TemplateClassDefinition<HashAggregator> TEMPLATE_DEFINITION =
       new TemplateClassDefinition<HashAggregator>(HashAggregator.class, 
HashAggTemplate.class);
 
@@ -46,8 +45,10 @@
   // OK - batch returned, NONE - end of data, RESTART - call again
   enum AggIterOutcome { AGG_OK, AGG_NONE, AGG_RESTART }
 
-  void setup(HashAggregate hashAggrConfig, HashTableConfig htConfig, 
FragmentContext context, OperatorContext oContext, RecordBatch incoming, 
HashAggBatch outgoing,
-             LogicalExpression[] valueExprs, List<TypedFieldId> valueFieldIds, 
TypedFieldId[] keyFieldIds, VectorContainer outContainer, int extraRowBytes) 
throws SchemaChangeException, IOException, ClassTransformationException;
+  void setup(HashAggregate hashAggrConfig, HashTableConfig htConfig, 
FragmentContext context,
+             OperatorContext oContext, RecordBatch incoming, HashAggBatch 
outgoing,
+             ValueVectorWriteExpression[] valueExprs, List<TypedFieldId> 
valueFieldIds, TypedFieldId[] keyFieldIds,
+             VectorContainer outContainer) throws SchemaChangeException, 
IOException, ClassTransformationException;
 
   IterOutcome getOutcome();
 
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/common/HashTable.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/common/HashTable.java
index d28fe498e4..e72733696a 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/common/HashTable.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/common/HashTable.java
@@ -24,6 +24,8 @@
 import org.apache.drill.exec.record.VectorContainer;
 import org.apache.drill.common.exceptions.RetryAfterSpillException;
 
+import java.util.Map;
+
 public interface HashTable {
   TemplateClassDefinition<HashTable> TEMPLATE_DEFINITION =
       new TemplateClassDefinition<>(HashTable.class, HashTableTemplate.class);
@@ -69,7 +71,7 @@ void setup(HashTableConfig htConfig, BufferAllocator 
allocator, RecordBatch inco
 
   void reset();
 
-  void setMaxVarcharSize(int size);
+  void setKeySizes(Map<String, Integer> keySizes);
 
   boolean outputKeys(int batchIdx, VectorContainer outContainer, int 
outStartIndex, int numRecords, int numExpectedRecords);
 }
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/common/HashTableTemplate.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/common/HashTableTemplate.java
index 272d782e5b..606196deb4 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/common/HashTableTemplate.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/common/HashTableTemplate.java
@@ -19,9 +19,13 @@
 
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.Map;
 
 import javax.inject.Named;
 
+import com.google.common.base.Preconditions;
+import org.apache.drill.common.map.CaseInsensitiveMap;
+import org.apache.drill.common.types.TypeProtos;
 import org.apache.drill.common.types.TypeProtos.MinorType;
 import org.apache.drill.common.types.Types;
 import org.apache.drill.exec.compile.sig.RuntimeOverridden;
@@ -46,6 +50,13 @@
   private static final org.slf4j.Logger logger = 
org.slf4j.LoggerFactory.getLogger(HashTable.class);
   private static final boolean EXTRA_DEBUG = false;
 
+  // startIndices, hashValues, and links overhead
+  public static final int ENTRY_OVERHEAD_BYTES =
+    
TypeHelper.getSize(TypeProtos.MajorType.newBuilder().setMinorType(MinorType.INT).build())
 * 3;
+
+  // TODO remove these defaults. We should require key sizes to be specified.
+  private static final int DEFAULT_VAR_CHAR_SIZE = 8;
+
   private static final int EMPTY_SLOT = -1;
 
   // A hash 'bucket' consists of the start index to indicate start of a hash 
chain
@@ -99,7 +110,7 @@
 
   private int resizingTime = 0;
 
-  private int maxVarcharSize = 8; // for varchar allocation
+  private Map<String, Integer> keySizes; // for varchar allocation
 
   // This class encapsulates the links, keys and values for up to BATCH_SIZE
   // *unique* records. Thus, suppose there are N incoming record batches, each
@@ -117,7 +128,6 @@
     private IntVector hashValues;
 
     private int maxOccupiedIdx = -1;
-//    private int batchOutputCount = 0;
 
     private int batchIndex = 0;
 
@@ -129,7 +139,8 @@ public BatchHolder(int idx) {
       boolean success = false;
       try {
         for (VectorWrapper<?> w : htContainerOrig) {
-          ValueVector vv = TypeHelper.getNewVector(w.getField(), allocator);
+          final ValueVector vv = TypeHelper.getNewVector(w.getField(), 
allocator);
+          final String name = vv.getField().getName();
           htContainer.add(vv); // add to container before actual allocation 
(to allow clearing in case of an OOM)
 
           // Capacity for "hashValues" and "links" vectors is BATCH_SIZE 
records. It is better to allocate space for
@@ -140,9 +151,10 @@ public BatchHolder(int idx) {
           if (vv instanceof FixedWidthVector) {
             ((FixedWidthVector) vv).allocateNew(BATCH_SIZE);
           } else if (vv instanceof VariableWidthVector) {
+            final int keySize = getKeySize(name, DEFAULT_VAR_CHAR_SIZE);
             long beforeMem = allocator.getAllocatedMemory();
-            ((VariableWidthVector) vv).allocateNew(maxVarcharSize * 
BATCH_SIZE, BATCH_SIZE);
-            logger.trace("HT allocated {} for varchar of max width 
{}",allocator.getAllocatedMemory() - beforeMem, maxVarcharSize);
+            ((VariableWidthVector) vv).allocateNew(keySize * BATCH_SIZE, 
BATCH_SIZE);
+            logger.trace("HT allocated {} for varchar size 
{}",allocator.getAllocatedMemory() - beforeMem, keySize);
           } else {
             vv.allocateNew();
           }
@@ -159,6 +171,22 @@ public BatchHolder(int idx) {
       }
     }
 
+    private int getKeySize(final String name, final int defaultSize)
+    {
+      if (keySizes == null) {
+        return defaultSize;
+      }
+
+      final Integer size = keySizes.get(name);
+
+      if (size == null) {
+        final String message = String.format(keySizes + " Could not find size 
for column %s", name);
+        throw new IllegalStateException(message);
+      }
+
+      return size;
+    }
+
     private void init(IntVector links, IntVector hashValues, int size) {
       for (int i = 0; i < size; i++) {
         links.getMutator().set(i, EMPTY_SLOT);
@@ -323,12 +351,6 @@ private void rehash(int numbuckets, IntVector 
newStartIndices, int batchStartIdx
     }
 
     private boolean outputKeys(VectorContainer outContainer, int 
outStartIndex, int numRecords, int numExpectedRecords) {
-
-      /** for debugging
-      BigIntVector vv0 = getValueVector(0);
-      BigIntHolder holder = new BigIntHolder();
-      */
-
       // set the value count for htContainer's value vectors before the 
transfer ..
       setValueCount();
 
@@ -352,26 +374,6 @@ private boolean outputKeys(VectorContainer outContainer, 
int outStartIndex, int
         }
       }
 
-/*
-      logger.debug("Attempting to output keys for batch index: {} from index 
{} to maxOccupiedIndex {}.",
-      this.batchIndex, 0, maxOccupiedIdx);
-      for (int i = batchOutputCount; i <= maxOccupiedIdx; i++) {
-        if (outputRecordKeys(i, batchOutputCount) ) {
-          if (EXTRA_DEBUG) logger.debug("Outputting keys to output index: {}", 
batchOutputCount) ;
-
-          // debugging
-          // holder.value = vv0.getAccessor().get(i);
-          // if (holder.value == 100018 || holder.value == 100021) {
-          //  logger.debug("Outputting key = {} at index - {} to outgoing 
index = {}.", holder.value, i,
-          //      batchOutputCount);
-          // }
-
-          batchOutputCount++;
-        } else {
-          return false;
-        }
-      }
- */
       return true;
     }
 
@@ -805,7 +807,12 @@ private IntVector allocMetadataVector(int size, int 
initialValue) {
   }
 
   @Override
-  public void setMaxVarcharSize(int size) { maxVarcharSize = size; }
+  public void setKeySizes(Map<String, Integer> keySizes) {
+    Preconditions.checkNotNull(keySizes);
+
+    this.keySizes = CaseInsensitiveMap.newHashMap();
+    this.keySizes.putAll(keySizes);
+  }
 
   // These methods will be code-generated in the context of the outer class
   protected abstract void doSetup(@Named("incomingBuild") RecordBatch 
incomingBuild, @Named("incomingProbe") RecordBatch incomingProbe) throws 
SchemaChangeException;
@@ -813,5 +820,4 @@ private IntVector allocMetadataVector(int size, int 
initialValue) {
   protected abstract int getHashBuild(@Named("incomingRowIdx") int 
incomingRowIdx, @Named("seedValue") int seedValue) throws SchemaChangeException;
 
   protected abstract int getHashProbe(@Named("incomingRowIdx") int 
incomingRowIdx, @Named("seedValue") int seedValue) throws SchemaChangeException;
-
 }
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/record/RecordBatchSizer.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/record/RecordBatchSizer.java
index f5c77ce55a..4f48facf89 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/record/RecordBatchSizer.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/record/RecordBatchSizer.java
@@ -17,16 +17,17 @@
  */
 package org.apache.drill.exec.record;
 
-import java.util.Set;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.drill.common.map.CaseInsensitiveMap;
 import org.apache.drill.common.types.TypeProtos.DataMode;
 import org.apache.drill.common.types.TypeProtos.MinorType;
-import org.apache.drill.exec.expr.TypeHelper;
 import org.apache.drill.exec.memory.AllocationManager.BufferLedger;
 import org.apache.drill.exec.memory.BaseAllocator;
+import org.apache.drill.exec.physical.impl.xsort.managed.SortMemoryManager;
 import org.apache.drill.exec.record.selection.SelectionVector2;
+import org.apache.drill.exec.vector.FixedWidthVector;
 import org.apache.drill.exec.vector.UInt4Vector;
 import org.apache.drill.exec.vector.ValueVector;
 import org.apache.drill.exec.vector.complex.AbstractMapVector;
@@ -44,6 +45,11 @@
 
 public class RecordBatchSizer {
 
+  // TODO consolidate common memory estimation helpers
+  public static final double PAYLOAD_FROM_BUFFER = 
SortMemoryManager.PAYLOAD_FROM_BUFFER;
+  public static final double FRAGMENTATION_FACTOR = 1.0 / PAYLOAD_FROM_BUFFER;
+  public static final double BUFFER_FROM_PAYLOAD = 
SortMemoryManager.BUFFER_FROM_PAYLOAD;
+
   /**
    * Column size information.
    */
@@ -57,7 +63,7 @@
      * columns.
      */
 
-    public int stdSize;
+    public int stdSize = -1;
 
     /**
      * Actual average column width as determined from actual memory use. This
@@ -108,7 +114,15 @@
     public final float estElementCountPerArray;
     public final boolean isVariableWidth;
 
+    public ColumnSize(ValueVector v) {
+      this(v, "");
+    }
+
     public ColumnSize(ValueVector v, String prefix) {
+      if (v instanceof FixedWidthVector) {
+        stdSize = ((FixedWidthVector)v).getValueWidth();
+      }
+
       this.prefix = prefix;
       valueCount = v.getAccessor().getValueCount();
       metadata = v.getField();
@@ -133,19 +147,46 @@ public ColumnSize(ValueVector v, String prefix) {
         // No standard size for Union type
         dataSize = v.getPayloadByteCount(valueCount);
         break;
+      case GENERIC_OBJECT:
+        // Object vectors do not consume direct memory so their known size and
+        // estSize are 0.
+        stdSize = 0;
+        break;
       default:
         dataSize = v.getPayloadByteCount(valueCount);
-        try {
-          stdSize = TypeHelper.getSize(metadata.getType()) * elementCount;
-        } catch (Exception e) {
-          // For unsupported types, just set stdSize to 0.
-          stdSize = 0;
-        }
       }
       estSize = safeDivide(dataSize, valueCount);
       netSize = v.getPayloadByteCount(valueCount);
     }
 
+    /**
+     * If we can determine the knownSize, return that. Otherwise return the 
estSize.
+     * @return The knownSize or estSize.
+     */
+    public int getStdOrEstSize()
+    {
+      if (hasStdSize()) {
+        // We know the exact size of the column, return it.
+        return stdSize;
+      }
+
+      return estSize;
+    }
+
+    public boolean hasStdSize()
+    {
+      return stdSize != -1;
+    }
+
+    public int getStdSize()
+    {
+      if (!hasStdSize()) {
+        throw new IllegalStateException("Unknown size for column: " + 
metadata);
+      }
+
+      return stdSize;
+    }
+
     @SuppressWarnings("resource")
     private int buildRepeated(ValueVector v) {
 
@@ -252,8 +293,6 @@ public static ColumnSize getColumn(ValueVector v, String 
prefix) {
     return new ColumnSize(v, prefix);
   }
 
-  public static final int MAX_VECTOR_SIZE = ValueVector.MAX_BUFFER_SIZE; // 16 
MiB
-
   private Map<String, ColumnSize> columnSizes = 
CaseInsensitiveMap.newHashMap();
 
   /**
@@ -281,7 +320,6 @@ public static ColumnSize getColumn(ValueVector v, String 
prefix) {
    * vectors are partially full; prevents overestimating row width.
    */
   private int netRowWidth;
-  private int netRowWidthCap50;
   private boolean hasSv2;
   private int sv2Size;
   private int avgDensity;
@@ -371,25 +409,14 @@ public void applySv2() {
     computeEstimates();
   }
 
-  /**
-   *  Round up (if needed) to the next power of 2 (only up to 64)
-   * @param arg Number to round up (must be < 64)
-   * @return power of 2 result
-   */
-  private int roundUpToPowerOf2(int arg) {
-    if ( arg <= 2 ) { return 2; }
-    if ( arg <= 4 ) { return 4; }
-    if ( arg <= 8 ) { return 8; }
-    if ( arg <= 16 ) { return 16; }
-    if ( arg <= 32 ) { return 32; }
-    return 64;
-  }
-
   private void measureColumn(ValueVector v, String prefix) {
 
     ColumnSize colSize = new ColumnSize(v, prefix);
-    columnSizes.put(v.getField().getName(), colSize);
-    stdRowWidth += colSize.stdSize;
+
+    if (colSize.hasStdSize()) {
+      stdRowWidth += colSize.stdSize;
+    }
+
     netBatchSize += colSize.dataSize;
     maxSize = Math.max(maxSize, colSize.dataSize);
     if (colSize.metadata.isNullable()) {
@@ -414,10 +441,8 @@ private void measureColumn(ValueVector v, String prefix) {
         v.collectLedgers(ledgers);
     }
 
+    columnSizes.put(v.getField().getName(), colSize);
     netRowWidth += colSize.estSize;
-    netRowWidthCap50 += ! colSize.isVariableWidth ? colSize.estSize :
-        8 /* offset vector */ + 
roundUpToPowerOf2(Math.min(colSize.estSize,50));
-        // above change 8 to 4 after DRILL-5446 is fixed
   }
 
   private void expandMap(AbstractMapVector mapVector, String prefix) {
@@ -454,19 +479,17 @@ public static int safeDivide(long num, long denom) {
   public int netRowWidth() { return netRowWidth; }
   public Map<String, ColumnSize> columns() { return columnSizes; }
 
-  /**
-   * Compute the "real" width of the row, taking into account each varchar 
column size
-   * (historically capped at 50, and rounded up to power of 2 to match drill 
buf allocation)
-   * and null marking columns.
-   * @return "real" width of the row
-   */
-  public int netRowWidthCap50() { return netRowWidthCap50 + nullableCount; }
   public long actualSize() { return accountedMemorySize; }
   public boolean hasSv2() { return hasSv2; }
   public int avgDensity() { return avgDensity; }
   public long netSize() { return netBatchSize; }
   public int maxAvgColumnSize() { return maxSize / rowCount; }
 
+  public static long multiplyByFactors(long size)
+  {
+    return (long) (((double) size) * FRAGMENTATION_FACTOR * 
BUFFER_FROM_PAYLOAD);
+  }
+
   @Override
   public String toString() {
     StringBuilder buf = new StringBuilder();
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/work/foreman/FragmentsRunner.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/work/foreman/FragmentsRunner.java
index 2e5f2dd06e..9213f1671e 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/work/foreman/FragmentsRunner.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/work/foreman/FragmentsRunner.java
@@ -61,7 +61,7 @@
   private static final org.slf4j.Logger logger = 
org.slf4j.LoggerFactory.getLogger(FragmentsRunner.class);
   private static final ControlsInjector injector = 
ControlsInjectorFactory.getInjector(FragmentsRunner.class);
 
-  private static final long RPC_WAIT_IN_MSECS_PER_FRAGMENT = 5000;
+  private static final long RPC_WAIT_IN_MSECS_PER_FRAGMENT = 30000;
 
   private final WorkerBee bee;
   private final UserClientConnection initiatingClient;
diff --git a/exec/java-exec/src/main/resources/drill-module.conf 
b/exec/java-exec/src/main/resources/drill-module.conf
index 5659c82ab0..060ee39a84 100644
--- a/exec/java-exec/src/main/resources/drill-module.conf
+++ b/exec/java-exec/src/main/resources/drill-module.conf
@@ -427,8 +427,8 @@ drill.exec.options: {
     exec.enable_union_type: false,
     exec.errors.verbose: false,
     exec.hashagg.mem_limit: 0,
-    exec.hashagg.min_batches_per_partition: 2,
-    exec.hashagg.num_partitions: 32,
+    exec.hashagg.min_batches_per_partition: 1,
+    exec.hashagg.num_partitions: 16,
     exec.hashagg.use_memory_prediction: true,
     exec.impersonation.inbound_policies: "[]",
     exec.java.compiler.exp_in_method_size: 50,
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java
index 3c1088459c..ebb649057a 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestLocalExchange.java
@@ -46,13 +46,10 @@
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
-import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
 
 import java.io.File;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.file.Paths;
 import java.util.List;
@@ -212,7 +209,7 @@ public void testGroupByMultiFields() throws Exception {
       testBuilder.baselineValues(new Object[] { (long)i, (long)0, (long)0, 
(long)numOccurrances});
     }
 
-    testBuilder.go();
+          testBuilder.go();
   }
 
   @Test
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/agg/TestHashAggr.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/agg/TestHashAggr.java
index 3de5519658..cc214d6862 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/agg/TestHashAggr.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/agg/TestHashAggr.java
@@ -59,4 +59,9 @@ public void test8() throws Exception{
     testPhysicalFromFile("agg/hashagg/q8.json");
   }
 
+  // Test aggregating varchars
+  @Test
+  public void testQ9() throws Exception {
+    testPhysicalFromFile("agg/hashagg/q9.json");
+  }
 }
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/agg/TestHashAggrSpill.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/agg/TestHashAggrSpill.java
index f517b1d0c3..689af8871f 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/agg/TestHashAggrSpill.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/agg/TestHashAggrSpill.java
@@ -53,61 +53,63 @@
   @Rule
   public final BaseDirTestWatcher dirTestWatcher = new BaseDirTestWatcher();
 
-    /**
-     *  A template for Hash Aggr spilling tests
-     *
-     * @throws Exception
-     */
-    private void testSpill(long maxMem, long numPartitions, long minBatches, 
int maxParallel, boolean fallback ,boolean predict,
-                           String sql, long expectedRows, int cycle, int 
fromPart, int toPart) throws Exception {
-        LogFixture.LogFixtureBuilder logBuilder = LogFixture.builder()
-          .toConsole()
-          .logger("org.apache.drill", Level.WARN);
-
-        ClusterFixtureBuilder builder = ClusterFixture.builder(dirTestWatcher)
-          .sessionOption(ExecConstants.HASHAGG_MAX_MEMORY_KEY,maxMem)
-          
.sessionOption(ExecConstants.HASHAGG_NUM_PARTITIONS_KEY,numPartitions)
-          
.sessionOption(ExecConstants.HASHAGG_MIN_BATCHES_PER_PARTITION_KEY,minBatches)
-          .configProperty(ExecConstants.SYS_STORE_PROVIDER_LOCAL_ENABLE_WRITE, 
false)
-          .sessionOption(PlannerSettings.FORCE_2PHASE_AGGR_KEY,true)
-          .sessionOption(ExecConstants.HASHAGG_FALLBACK_ENABLED_KEY, fallback)
-          
.sessionOption(ExecConstants.HASHAGG_USE_MEMORY_PREDICTION_KEY,predict)
-          .maxParallelization(maxParallel)
-          .saveProfiles();
-        String sqlStr = sql != null ? sql :  // if null then use this default 
query
-          "SELECT empid_s17, dept_i, branch_i, AVG(salary_i) FROM 
`mock`.`employee_1200K` GROUP BY empid_s17, dept_i, branch_i";
-
-        try (LogFixture logs = logBuilder.build();
-             ClusterFixture cluster = builder.build();
-             ClientFixture client = cluster.clientFixture()) {
-            runAndDump(client, sqlStr, expectedRows, cycle, fromPart,toPart);
-        }
-    }
-    /**
-     * Test "normal" spilling: Only 2 (or 3) partitions (out of 4) would 
require spilling
-     * ("normal spill" means spill-cycle = 1 )
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testSimpleHashAggrSpill() throws Exception {
-        testSpill(68_000_000, 16, 2, 2, false, true, null,
-          1_200_000, 1,2, 3
-          );
-    }
-    /**
-     * Test with "needed memory" prediction turned off
-     * (i.e., do exercise code paths that catch OOMs from the Hash Table and 
recover)
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testNoPredictHashAggrSpill() throws Exception {
-      testSpill(58_000_000, 16, 2, 2, false, false /* no prediction */, null,
-        1_200_000, 1, 1, 1);
+  /**
+   * A template for Hash Aggr spilling tests
+   *
+   * @throws Exception
+   */
+  private void testSpill(long maxMem, long numPartitions, long minBatches, int 
maxParallel,
+                         boolean fallback, boolean predict, String sql, long 
expectedRows,
+                         int fromCycle, int toCycle, int fromPart, int toPart) 
throws Exception {
+    LogFixture.LogFixtureBuilder logBuilder = LogFixture.builder()
+      .toConsole()
+      .logger("org.apache.drill", Level.WARN);
+
+    ClusterFixtureBuilder builder = ClusterFixture.builder(dirTestWatcher)
+      .sessionOption(ExecConstants.HASHAGG_MAX_MEMORY_KEY, maxMem)
+      .sessionOption(ExecConstants.HASHAGG_NUM_PARTITIONS_KEY, numPartitions)
+      .sessionOption(ExecConstants.HASHAGG_MIN_BATCHES_PER_PARTITION_KEY, 
minBatches)
+      .configProperty(ExecConstants.SYS_STORE_PROVIDER_LOCAL_ENABLE_WRITE, 
false)
+      .sessionOption(PlannerSettings.FORCE_2PHASE_AGGR_KEY, true)
+      .sessionOption(ExecConstants.HASHAGG_FALLBACK_ENABLED_KEY, fallback)
+      .sessionOption(ExecConstants.HASHAGG_USE_MEMORY_PREDICTION_KEY, predict)
+      .maxParallelization(maxParallel).saveProfiles();
+    String sqlStr = sql != null ? sql :  // if null then use this default query
+      "SELECT empid_s17, dept_i, branch_i, AVG(salary_i) FROM 
`mock`.`employee_1200K` GROUP BY empid_s17, dept_i, branch_i";
+
+    try (LogFixture logs = logBuilder.build();
+         ClusterFixture cluster = builder.build();
+         ClientFixture client = cluster.clientFixture()) {
+      runAndDump(client, sqlStr, expectedRows, fromCycle, toCycle, fromPart, 
toPart);
     }
+  }
+
+  /**
+   * Test "normal" spilling: Only 2 (or 3) partitions (out of 4) would require 
spilling
+   * ("normal spill" means spill-cycle = 1 )
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testSimpleHashAggrSpill() throws Exception {
+    testSpill(80_000_000, 16, 2, 2, false,
+      true, null, 1_200_000, 1, 1, 1, 3);
+  }
+
+  /**
+   * Test with "needed memory" prediction turned off
+   * (i.e., do exercise code paths that catch OOMs from the Hash Table and 
recover)
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testNoPredictHashAggrSpill() throws Exception {
+    testSpill(58_000_000, 16, 2, 2, false,
+      false /* no prediction */, null, 1_200_000, 1, 1, 1, 3);
+  }
 
-  private void runAndDump(ClientFixture client, String sql, long expectedRows, 
long spillCycle, long fromSpilledPartitions, long toSpilledPartitions) throws 
Exception {
+  private void runAndDump(ClientFixture client, String sql, long expectedRows, 
long fromSpillCycle, long toSpillCycle, long fromSpilledPartitions, long 
toSpilledPartitions) throws
+    Exception {
     QueryBuilder.QuerySummary summary = client.queryBuilder().sql(sql).run();
     if (expectedRows > 0) {
       assertEquals(expectedRows, summary.recordCount());
@@ -120,7 +122,7 @@ private void runAndDump(ClientFixture client, String sql, 
long expectedRows, lon
     // check for the first op only
     ProfileParser.OperatorProfile hag0 = ops.get(0);
     long opCycle = 
hag0.getMetric(HashAggTemplate.Metric.SPILL_CYCLE.ordinal());
-    assertEquals(spillCycle, opCycle);
+    assertTrue(fromSpillCycle <= opCycle && opCycle <= toSpillCycle);
     long op_spilled_partitions = 
hag0.getMetric(HashAggTemplate.Metric.SPILLED_PARTITIONS.ordinal());
     assertTrue(op_spilled_partitions >= fromSpilledPartitions && 
op_spilled_partitions <= toSpilledPartitions);
   }
@@ -133,8 +135,8 @@ private void runAndDump(ClientFixture client, String sql, 
long expectedRows, lon
   @Test
   public void testHashAggrSecondaryTertiarySpill() throws Exception {
 
-    testSpill(58_000_000, 16, 3, 1, false, true, "SELECT empid_s44, dept_i, 
branch_i, AVG(salary_i) FROM `mock`.`employee_1100K` GROUP BY empid_s44, 
dept_i, branch_i",
-      1_100_000, 3, 2, 2);
+    testSpill(58_000_000, 16, 2, 1, false, true, "SELECT empid_s44, dept_i, 
branch_i, AVG(salary_i) FROM `mock`.`employee_1100K` GROUP BY empid_s44, 
dept_i, branch_i",
+      1_100_000, 3, 4, 2, 2);
   }
 
   /**
@@ -147,7 +149,7 @@ public void testHashAggrFailWithFallbackDisabed() throws 
Exception {
 
     try {
       testSpill(34_000_000, 4, 5, 2, false /* no fallback */, true, null,
-        1_200_000, 0 /* no spill due to fallback to pre-1.11 */, 0, 0);
+        1_200_000, 0 /* no spill due to fallback to pre-1.11 */, 0, 0, 0);
       fail(); // in case the above test did not throw
     } catch (Exception ex) {
       assertTrue(ex instanceof UserRemoteException);
@@ -165,6 +167,6 @@ public void testHashAggrFailWithFallbackDisabed() throws 
Exception {
   @Test
   public void testHashAggrSuccessWithFallbackEnabled() throws Exception {
     testSpill(34_000_000, 4, 5, 2, true /* do fallback */, true, null,
-      1_200_000, 0 /* no spill due to fallback to pre-1.11 */, 0, 0);
+      1_200_000, 0 /* no spill due to fallback to pre-1.11 */, 0, 0, 0);
   }
 }
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/join/TestHashJoinAdvanced.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/join/TestHashJoinAdvanced.java
index b9b97c15cb..05a75b11b3 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/join/TestHashJoinAdvanced.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/join/TestHashJoinAdvanced.java
@@ -31,8 +31,6 @@
 import java.io.File;
 import java.io.FileWriter;
 import java.nio.file.Paths;
-import java.util.concurrent.ExecutorService;
-
 
 @Category(OperatorTest.class)
 public class TestHashJoinAdvanced extends JoinTestBase {
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/managed/TestSortImpl.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/managed/TestSortImpl.java
index a98547885f..3ccb2a0995 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/managed/TestSortImpl.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/xsort/managed/TestSortImpl.java
@@ -51,6 +51,7 @@
 import org.apache.drill.test.rowSet.RowSetReader;
 import org.apache.drill.test.rowSet.RowSetWriter;
 import org.apache.drill.test.rowSet.SchemaBuilder;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/test/BaseTestQuery.java 
b/exec/java-exec/src/test/java/org/apache/drill/test/BaseTestQuery.java
index c3ecaf118e..d64132f367 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/test/BaseTestQuery.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/test/BaseTestQuery.java
@@ -36,7 +36,6 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.drill.exec.compile.ClassBuilder;
-import org.apache.drill.exec.compile.CodeCompiler;
 import org.apache.drill.test.DrillTestWrapper.TestServices;
 import org.apache.drill.common.config.DrillConfig;
 import org.apache.drill.common.config.DrillProperties;
@@ -73,8 +72,6 @@
 
 import org.apache.drill.exec.record.VectorWrapper;
 import org.apache.drill.exec.vector.ValueVector;
-import org.apache.drill.test.ClusterFixture;
-import org.junit.ClassRule;
 
 public class BaseTestQuery extends ExecTest {
   private static final org.slf4j.Logger logger = 
org.slf4j.LoggerFactory.getLogger(BaseTestQuery.class);
diff --git a/exec/java-exec/src/test/resources/agg/hashagg/q9.json 
b/exec/java-exec/src/test/resources/agg/hashagg/q9.json
new file mode 100644
index 0000000000..35faed8bf8
--- /dev/null
+++ b/exec/java-exec/src/test/resources/agg/hashagg/q9.json
@@ -0,0 +1,57 @@
+{
+  head : {
+    version : 1,
+    generator : {
+      type : "optiq",
+      info : "na"
+    },
+    type : "APACHE_DRILL_PHYSICAL"
+  },
+  graph : [ {
+    "pop" : "parquet-scan",
+    "@id" : 1,
+    "entries" : [ {
+      "path" : "tpch/nation.parquet"
+    } ],
+    "storage" : {
+      "type" : "file",
+      "connection" : "classpath:///"
+    },
+    "format" : {
+      "type" : "parquet"
+    }
+  }, {
+    pop : "project",
+    @id : 2,
+    exprs : [ {
+      ref : "$f0",
+      expr : "N_REGIONKEY"
+    }, {
+      ref : "$f1",
+      expr : "N_NAME"
+    }, {
+      ref : "$f2",
+      expr : "N_NATIONKEY"
+    } ],
+    child : 1
+  }, {
+    pop : "hash-aggregate",
+    @id : 3,
+    child : 2,
+    keys : [ {
+      ref : "$f0",
+      expr : "$f0"
+    } ],
+    exprs : [ {
+      ref : "Y",
+      expr : "sum($f2) "
+    }, {
+      ref : "Z",
+      expr : "max($f1) "
+    } ]
+  }, {
+    pop : "screen",
+    @id : 4,
+    child : 3
+  } ]
+}
diff --git a/exec/vector/src/main/codegen/templates/FixedValueVectors.java 
b/exec/vector/src/main/codegen/templates/FixedValueVectors.java
index 79beb52e05..3284efe8c1 100644
--- a/exec/vector/src/main/codegen/templates/FixedValueVectors.java
+++ b/exec/vector/src/main/codegen/templates/FixedValueVectors.java
@@ -298,6 +298,11 @@ public int getPayloadByteCount(int valueCount) {
     return valueCount * ${type.width};
   }
 
+  @Override
+  public int getValueWidth() {
+    return ${type.width};
+  }
+
   private class TransferImpl implements TransferPair {
     private ${minor.class}Vector to;
 
diff --git a/exec/vector/src/main/codegen/templates/NullableValueVectors.java 
b/exec/vector/src/main/codegen/templates/NullableValueVectors.java
index bc13d62a2f..2c59ec7ba2 100644
--- a/exec/vector/src/main/codegen/templates/NullableValueVectors.java
+++ b/exec/vector/src/main/codegen/templates/NullableValueVectors.java
@@ -221,6 +221,13 @@ public int getPayloadByteCount(int valueCount) {
     return bits.getPayloadByteCount(valueCount) + 
values.getPayloadByteCount(valueCount);
   }
 
+  <#if type.major != "VarLen">
+  @Override
+  public int getValueWidth(){
+    return bits.getValueWidth() + ${type.width};
+  }
+  </#if>
+
   <#if type.major == "VarLen">
   @Override
   public void allocateNew(int totalBytes, int valueCount) {
diff --git 
a/exec/vector/src/main/java/org/apache/drill/exec/vector/BitVector.java 
b/exec/vector/src/main/java/org/apache/drill/exec/vector/BitVector.java
index ca2be3a769..247355690d 100644
--- a/exec/vector/src/main/java/org/apache/drill/exec/vector/BitVector.java
+++ b/exec/vector/src/main/java/org/apache/drill/exec/vector/BitVector.java
@@ -211,6 +211,11 @@ public void zeroVector() {
     data.setZero(0, data.capacity());
   }
 
+  @Override
+  public int getValueWidth() {
+    return VALUE_WIDTH;
+  }
+
   public void copyFrom(int inIndex, int outIndex, BitVector from) {
     this.mutator.set(outIndex, from.accessor.get(inIndex));
   }
diff --git 
a/exec/vector/src/main/java/org/apache/drill/exec/vector/FixedWidthVector.java 
b/exec/vector/src/main/java/org/apache/drill/exec/vector/FixedWidthVector.java
index 09bcdd8134..e151e30c81 100644
--- 
a/exec/vector/src/main/java/org/apache/drill/exec/vector/FixedWidthVector.java
+++ 
b/exec/vector/src/main/java/org/apache/drill/exec/vector/FixedWidthVector.java
@@ -30,4 +30,10 @@
    * Zero out the underlying buffer backing this vector.
    */
   void zeroVector();
+
+  /**
+   * The width of a record in bytes.
+   * @return The width of a record in bytes.
+   */
+  int getValueWidth();
 }
diff --git 
a/exec/vector/src/main/java/org/apache/drill/exec/vector/UntypedNullVector.java 
b/exec/vector/src/main/java/org/apache/drill/exec/vector/UntypedNullVector.java
index 5565fa41a6..c46e3b854e 100644
--- 
a/exec/vector/src/main/java/org/apache/drill/exec/vector/UntypedNullVector.java
+++ 
b/exec/vector/src/main/java/org/apache/drill/exec/vector/UntypedNullVector.java
@@ -91,6 +91,11 @@ public DrillBuf reallocRaw(int newAllocationSize) {
     throw new UnsupportedOperationException();
   }
 
+  @Override
+  public int getValueWidth() {
+    return VALUE_WIDTH;
+  }
+
   @Override
   public void load(SerializedField metadata, DrillBuf buffer) {
     
Preconditions.checkArgument(this.field.getName().equals(metadata.getNamePart().getName()),


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


> Use RecordBatchSizer to estimate size of columns in HashAgg
> -----------------------------------------------------------
>
>                 Key: DRILL-6032
>                 URL: https://issues.apache.org/jira/browse/DRILL-6032
>             Project: Apache Drill
>          Issue Type: Improvement
>            Reporter: Timothy Farkas
>            Assignee: Timothy Farkas
>            Priority: Major
>             Fix For: 1.15.0
>
>
> We need to use the RecordBatchSize to estimate the size of columns in the 
> Partition batches created by HashAgg.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to