estDistVals = Math.min(MIN_DISTINCT_VALUES, (int)(Bytes.toInt(estDistValsBytes) * 1.5f)); // Allocate 1.5x estimation
}
TenantCache tenantCache = GlobalCache.getTenantCache(c.getEnvironment(), ScanUtil.getTenantId(scan));
int estSize = sizeOfUnorderedGroupByMap(estDistVals, aggregators.getSize());
final MemoryChunk chunk = tenantCache.getMemoryManager().allocate(estSize);
boolean success = false;
try {
// TODO: spool map to disk if map becomes too big
boolean hasMore;
int estValueSize = aggregators.getSize();
MultiKeyValueTuple result = new MultiKeyValueTuple();
Map<ImmutableBytesWritable, Aggregator[]> aggregateMap = new HashMap<ImmutableBytesWritable, Aggregator[]>(estDistVals);
HRegion region = c.getEnvironment().getRegion();
MultiVersionConsistencyControl.setThreadReadPoint(s.getMvccReadPoint());
region.startRegionOperation();
try {
do {
List<KeyValue> results = new ArrayList<KeyValue>();
// Results are potentially returned even when the return value of s.next is false
// since this is an indication of whether or not there are more values after the
// ones returned
hasMore = s.nextRaw(results, null);
if (!results.isEmpty()) {
result.setKeyValues(results);
ImmutableBytesWritable key = TupleUtil.getConcatenatedValue(result, expressions);
Aggregator[] rowAggregators = aggregateMap.get(key);
if (rowAggregators == null) {
// If Aggregators not found for this distinct value, clone our original one (we need one per distinct value)
if (logger.isDebugEnabled()) {
logger.debug("Adding new aggregate bucket for row key " + Bytes.toStringBinary(key.get(),key.getOffset(),key.getLength()));
}
rowAggregators = aggregators.newAggregators(c.getEnvironment().getConfiguration());
aggregateMap.put(key, rowAggregators);
}
// Aggregate values here
aggregators.aggregate(rowAggregators, result);
if (logger.isDebugEnabled()) {
logger.debug("Row passed filters: " + results + ", aggregated values: " + Arrays.asList(rowAggregators));
}
if (aggregateMap.size() > estDistVals) { // increase allocation
estDistVals *= 1.5f;
estSize = sizeOfUnorderedGroupByMap(estDistVals, estValueSize);
chunk.resize(estSize);
}
}
} while (hasMore);
} finally {
region.closeRegionOperation();
}
// Compute final allocation
estSize = sizeOfUnorderedGroupByMap(aggregateMap.size(), estValueSize);
chunk.resize(estSize);
// TODO: spool list to disk if too big and free memory?
final List<KeyValue> aggResults = new ArrayList<KeyValue>(aggregateMap.size());
for (Map.Entry<ImmutableBytesWritable, Aggregator[]> entry : aggregateMap.entrySet()) {
ImmutableBytesWritable key = entry.getKey();
Aggregator[] rowAggregators = entry.getValue();
// Generate byte array of Aggregators and set as value of row
byte[] value = aggregators.toBytes(rowAggregators);
if (logger.isDebugEnabled()) {
logger.debug("Adding new distinct group: " + Bytes.toStringBinary(key.get(),key.getOffset(), key.getLength()) +
" with aggregators " + Arrays.asList(rowAggregators).toString() +
" value = " + Bytes.toStringBinary(value));
}
KeyValue keyValue = KeyValueUtil.newKeyValue(key.get(),key.getOffset(), key.getLength(),SINGLE_COLUMN_FAMILY, SINGLE_COLUMN, AGG_TIMESTAMP, value, 0, value.length);
aggResults.add(keyValue);
}
// Do not sort here, but sort back on the client instead
// The reason is that if the scan ever extends beyond a region (which can happen
// if we're basing our parallelization split points on old metadata), we'll get
// incorrect query results.
RegionScanner scanner = new BaseRegionScanner() {
private int index = 0;
@Override
public HRegionInfo getRegionInfo() {
return s.getRegionInfo();
}
@Override
public void close() throws IOException {
try {
s.close();
} finally {
chunk.close();
}
}
@Override
public boolean next(List<KeyValue> results) throws IOException {
if (index >= aggResults.size()) return false;
results.add(aggResults.get(index));
index++;
return index < aggResults.size();
}
};
success = true;
return scanner;
} finally {
if (!success)
chunk.close();
}
}