-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-23188][SQL] Make vectorized columar reader batch size configurable #20361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -377,6 +377,12 @@ object SQLConf { | |
| .booleanConf | ||
| .createWithDefault(true) | ||
|
|
||
| val PARQUET_VECTORIZED_READER_BATCH_SIZE = buildConf("spark.sql.parquet.batchSize") | ||
| .doc("The number of rows to include in a parquet vectorized reader batch. The number should " + | ||
| "be carefully chosen to minimize overhead and avoid OOMs in reading data.") | ||
| .intConf | ||
| .createWithDefault(4096) | ||
|
|
||
| val ORC_COMPRESSION = buildConf("spark.sql.orc.compression.codec") | ||
| .doc("Sets the compression codec used when writing ORC files. If either `compression` or " + | ||
| "`orc.compress` is specified in the table-specific options/properties, the precedence " + | ||
|
|
@@ -400,6 +406,12 @@ object SQLConf { | |
| .booleanConf | ||
| .createWithDefault(true) | ||
|
|
||
| val ORC_VECTORIZED_READER_BATCH_SIZE = buildConf("spark.sql.orc.batchSize") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| .doc("The number of rows to include in a orc vectorized reader batch. The number should " + | ||
| "be carefully chosen to minimize overhead and avoid OOMs in reading data.") | ||
| .intConf | ||
| .createWithDefault(4096) | ||
|
|
||
| val ORC_COPY_BATCH_TO_SPARK = buildConf("spark.sql.orc.copyBatchToSpark") | ||
| .doc("Whether or not to copy the ORC columnar batch to Spark columnar batch in the " + | ||
| "vectorized ORC reader.") | ||
|
|
@@ -1216,10 +1228,14 @@ class SQLConf extends Serializable with Logging { | |
|
|
||
| def orcVectorizedReaderEnabled: Boolean = getConf(ORC_VECTORIZED_READER_ENABLED) | ||
|
|
||
| def orcVectorizedReaderBatchSize: Int = getConf(ORC_VECTORIZED_READER_BATCH_SIZE) | ||
|
|
||
| def parquetCompressionCodec: String = getConf(PARQUET_COMPRESSION) | ||
|
|
||
| def parquetVectorizedReaderEnabled: Boolean = getConf(PARQUET_VECTORIZED_READER_ENABLED) | ||
|
|
||
| def parquetVectorizedReaderBatchSize: Int = getConf(PARQUET_VECTORIZED_READER_BATCH_SIZE) | ||
|
|
||
| def columnBatchSize: Int = getConf(COLUMN_BATCH_SIZE) | ||
|
|
||
| def numShufflePartitions: Int = getConf(SHUFFLE_PARTITIONS) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,8 +49,9 @@ | |
| * After creating, `initialize` and `initBatch` should be called sequentially. | ||
| */ | ||
| public class OrcColumnarBatchReader extends RecordReader<Void, ColumnarBatch> { | ||
| // TODO: make this configurable. | ||
| private static final int CAPACITY = 4 * 1024; | ||
|
|
||
| // The default size of vectorized batch. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we can remove the comment. It's just the capacity, not a default value.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about rephrase to |
||
| private int capacity; | ||
|
|
||
| // Vectorized ORC Row Batch | ||
| private VectorizedRowBatch batch; | ||
|
|
@@ -81,9 +82,10 @@ public class OrcColumnarBatchReader extends RecordReader<Void, ColumnarBatch> { | |
| // Whether or not to copy the ORC columnar batch to Spark columnar batch. | ||
| private final boolean copyToSpark; | ||
|
|
||
| public OrcColumnarBatchReader(boolean useOffHeap, boolean copyToSpark) { | ||
| public OrcColumnarBatchReader(boolean useOffHeap, boolean copyToSpark, int capacity) { | ||
| MEMORY_MODE = useOffHeap ? MemoryMode.OFF_HEAP : MemoryMode.ON_HEAP; | ||
| this.copyToSpark = copyToSpark; | ||
| this.capacity = capacity; | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -148,7 +150,7 @@ public void initBatch( | |
| StructField[] requiredFields, | ||
| StructType partitionSchema, | ||
| InternalRow partitionValues) { | ||
| batch = orcSchema.createRowBatch(CAPACITY); | ||
| batch = orcSchema.createRowBatch(capacity); | ||
| assert(!batch.selectedInUse); // `selectedInUse` should be initialized with `false`. | ||
|
|
||
| this.requiredFields = requiredFields; | ||
|
|
@@ -162,15 +164,15 @@ public void initBatch( | |
|
|
||
| if (copyToSpark) { | ||
| if (MEMORY_MODE == MemoryMode.OFF_HEAP) { | ||
| columnVectors = OffHeapColumnVector.allocateColumns(CAPACITY, resultSchema); | ||
| columnVectors = OffHeapColumnVector.allocateColumns(capacity, resultSchema); | ||
| } else { | ||
| columnVectors = OnHeapColumnVector.allocateColumns(CAPACITY, resultSchema); | ||
| columnVectors = OnHeapColumnVector.allocateColumns(capacity, resultSchema); | ||
| } | ||
|
|
||
| // Initialize the missing columns once. | ||
| for (int i = 0; i < requiredFields.length; i++) { | ||
| if (requestedColIds[i] == -1) { | ||
| columnVectors[i].putNulls(0, CAPACITY); | ||
| columnVectors[i].putNulls(0, capacity); | ||
| columnVectors[i].setIsConstant(); | ||
| } | ||
| } | ||
|
|
@@ -193,8 +195,8 @@ public void initBatch( | |
| int colId = requestedColIds[i]; | ||
| // Initialize the missing columns once. | ||
| if (colId == -1) { | ||
| OnHeapColumnVector missingCol = new OnHeapColumnVector(CAPACITY, dt); | ||
| missingCol.putNulls(0, CAPACITY); | ||
| OnHeapColumnVector missingCol = new OnHeapColumnVector(capacity, dt); | ||
| missingCol.putNulls(0, capacity); | ||
| missingCol.setIsConstant(); | ||
| orcVectorWrappers[i] = missingCol; | ||
| } else { | ||
|
|
@@ -206,7 +208,7 @@ public void initBatch( | |
| int partitionIdx = requiredFields.length; | ||
| for (int i = 0; i < partitionValues.numFields(); i++) { | ||
| DataType dt = partitionSchema.fields()[i].dataType(); | ||
| OnHeapColumnVector partitionCol = new OnHeapColumnVector(CAPACITY, dt); | ||
| OnHeapColumnVector partitionCol = new OnHeapColumnVector(capacity, dt); | ||
| ColumnVectorUtils.populate(partitionCol, partitionValues, i); | ||
| partitionCol.setIsConstant(); | ||
| orcVectorWrappers[partitionIdx + i] = partitionCol; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,8 +50,9 @@ | |
| * TODO: make this always return ColumnarBatches. | ||
| */ | ||
| public class VectorizedParquetRecordReader extends SpecificParquetRecordReaderBase<Object> { | ||
| // TODO: make this configurable. | ||
| private static final int CAPACITY = 4 * 1024; | ||
|
|
||
| // The default size of vectorized batch. | ||
| private int capacity; | ||
|
|
||
| /** | ||
| * Batch of rows that we assemble and the current index we've returned. Every time this | ||
|
|
@@ -115,13 +116,15 @@ public class VectorizedParquetRecordReader extends SpecificParquetRecordReaderBa | |
| */ | ||
| private final MemoryMode MEMORY_MODE; | ||
|
|
||
| public VectorizedParquetRecordReader(TimeZone convertTz, boolean useOffHeap) { | ||
| public VectorizedParquetRecordReader(TimeZone convertTz, boolean useOffHeap, int capacity) { | ||
| this.convertTz = convertTz; | ||
| MEMORY_MODE = useOffHeap ? MemoryMode.OFF_HEAP : MemoryMode.ON_HEAP; | ||
| this.capacity = capacity; | ||
| } | ||
|
|
||
| // Vectorized parquet reader used for testing and benchmark. | ||
| public VectorizedParquetRecordReader(boolean useOffHeap) { | ||
| this(null, useOffHeap); | ||
| this(null, useOffHeap, 4096); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about changing benchmark and test programs to pass capacity and remove this constructor?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's good to avoid hardcoding the default value again in the code. If there are only a few places need to be changed, let's do it. |
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -199,9 +202,9 @@ private void initBatch( | |
| } | ||
|
|
||
| if (memMode == MemoryMode.OFF_HEAP) { | ||
| columnVectors = OffHeapColumnVector.allocateColumns(CAPACITY, batchSchema); | ||
| columnVectors = OffHeapColumnVector.allocateColumns(capacity, batchSchema); | ||
| } else { | ||
| columnVectors = OnHeapColumnVector.allocateColumns(CAPACITY, batchSchema); | ||
| columnVectors = OnHeapColumnVector.allocateColumns(capacity, batchSchema); | ||
| } | ||
| columnarBatch = new ColumnarBatch(columnVectors); | ||
| if (partitionColumns != null) { | ||
|
|
@@ -215,7 +218,7 @@ private void initBatch( | |
| // Initialize missing columns with nulls. | ||
| for (int i = 0; i < missingColumns.length; i++) { | ||
| if (missingColumns[i]) { | ||
| columnVectors[i].putNulls(0, CAPACITY); | ||
| columnVectors[i].putNulls(0, capacity); | ||
| columnVectors[i].setIsConstant(); | ||
| } | ||
| } | ||
|
|
@@ -257,7 +260,7 @@ public boolean nextBatch() throws IOException { | |
| if (rowsReturned >= totalRowCount) return false; | ||
| checkEndOfRowGroup(); | ||
|
|
||
| int num = (int) Math.min((long) CAPACITY, totalCountLoadedSoFar - rowsReturned); | ||
| int num = (int) Math.min((long) capacity, totalCountLoadedSoFar - rowsReturned); | ||
| for (int i = 0; i < columnReaders.length; ++i) { | ||
| if (columnReaders[i] == null) continue; | ||
| columnReaders[i].readBatch(num, columnVectors[i]); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer
spark.sql.parquet.columnarReaderBatchSizeto be more clear.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still a question. Is that possible to use the estimated memory size instead of the number of rows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say it's very hard. If we need to satisfy a sizeInBytes limitation, we would need to load data record by record, and stop loading if we hit the limitation. But for performance reasons, we wanna load the data with batch, which needs to know the batch size ahead.