一. 前言
在Presto中通过数据源的Connector读取数据的时候,返回来的是Page,一个Page中包含每列数据Block,Block中会包含多行数据。本文主要是通过Presto源码走读了解在Presto的Hive Connector中是如何通过Batch Read实现到Hive数据源取Page的时候进行多行数据读取功能的实现过程。
二. Parquet文件的Batch Read
Parquet文件的读取时候每次通过Parquet接口读取的数据量在Presto中是通过ParquetReader::batchSize参数控制的。BatchSize的计算遵循如下几个规则:
-
Parquet文件读取时,初始batchSize为1。
-
读取Parquet数据量一般情况下,每次通过Parquet接口读取的batchSize是上一次的2倍。
-
如果一个rowkey内,剩下的数据小于batchSize,则只读取剩下的数据量大小。
-
如果读数据的position已经越过一个GroupRow,则重新清零,重复2-3,BatchSize还是上一次的两倍,不会从1开始初始化。
-
从parquet中真正读数据的时候,也是按照page对齐的方式读取的,每次读取pageSize大小的数据,读到BatchSize大小为止。
Parquet文件读取时候BatchSize的控制主要通过如下几个代码段控制的:
// 计算BatchSize 大小
public int nextBatch()
{
// nextRowInGroup: 在此RowGroup中,下一行读取的位置
// currentGroupRowCount :此RowGroup的大小
// currentPosition : 所有RowGroup中累计的位置
// 如果下一行已经超过当前GroupRow的大小,则跳到下一个RowGroup
if (nextRowInGroup >= currentGroupRowCount && !advanceToNextRowGroup()) {
return -1;
}
// batchSize: 本次读取的批大小
// nextBatchSize : 下一次读取的批大小
// maxBatchSize : 固定为1024
// 计算本次读取的批大小为上一次读取的大小的2倍
batchSize = toIntExact(min(nextBatchSize, maxBatchSize));
nextBatchSize = min(batchSize * BATCH_SIZE_GROWTH_FACTOR, MAX_VECTOR_LENGTH);
// 如果当前RowGroup剩下的数据小于batchSize,以剩下的数据大小为准
batchSize = toIntExact(min(batchSize, currentGroupRowCount - nextRowInGroup));
nextRowInGroup += batchSize;
currentPosition += batchSize;
Arrays.stream(columnReaders)
.forEach(reader -> reader.prepareNextRead(batchSize));
return batchSize;
}
// 越过RowGroup BatchSize不会重新初始化,还是上次的2倍
private boolean advanceToNextRowGroup()
{
currentRowGroupMemoryContext.close();
currentRowGroupMemoryContext = systemMemoryContext.newAggregatedMemoryContext();
// currentBlock: 当前的block的位置
// 所有的block数据都已经读取完成
if (currentBlock == blocks.size()) {
return false;
}
currentBlockMetadata = blocks.get(currentBlock);
currentBlock = currentBlock + 1;
// 从下一个block的0开始的地方读取
nextRowInGroup = 0L;
currentGroupRowCount = currentBlockMetadata.getRowCount();
initializeColumnReaders();
return true;
}
// 读数据时候Page对齐进行读取
private void processValues(int valuesToRead, Consumer<Void> valueConsumer)
{
if (definitionLevel == EMPTY_LEVEL_VALUE && repetitionLevel == EMPTY_LEVEL_VALUE) {
definitionLevel = definitionReader.readLevel();
repetitionLevel = repetitionReader.readLevel();
}
int valueCount = 0;
for (int i = 0; i < valuesToRead; i++) {
do {
valueConsumer.accept(null);
valueCount++;
if (valueCount == remainingValueCountInPage) {
// 一个page一个page读取数据,直到读满batchSize
updateValueCounts(valueCount);
if (!readNextPage()) {
return;
}
valueCount = 0;
}
repetitionLevel = repetitionReader.readLevel();
definitionLevel = definitionReader.readLevel();
}
while (repetitionLevel != 0);
}
updateValueCounts(valueCount);
}
三. ORC文件的Batch Read
ORC文件的Batch Size计算规则与Parquet文件有些不一样,主要表现为:
1. ORC文件的初始initSize为 blockSize(一般为16M)与1024的较小值
2. 当访问到下一个RowGroup的时候,BatchSize会按照上述第一步规则重新初始化
3. ORC不需要Page对齐,因为ORC读取是如流式那样每个字节进行读取读取,然后不断地next直接到BatchSize数量为止,下边是ORC数据的主要读取流程。
OrcRecordReader::nextPage
prepareNextBatch // 计算batchSize大小
columnsReader[columnIndex].readBlock
BooleanColumnReader.readBlock // 根据列的类型迪调用对应的ColumnReader
readNonNullBlock
dataStream.getSetBits
getSetBits
byteStream.next() // 一个个字节进行直接读取