-
Notifications
You must be signed in to change notification settings - Fork 229
Add tunables bounding compaction pipeline memory #1993
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
base: main
Are you sure you want to change the base?
Changes from all 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 |
|---|---|---|
|
|
@@ -224,8 +224,11 @@ type RewriteDataFilesOptions struct { | |
| type CompactionGroupOption func(*compactionGroupConfig) | ||
|
|
||
| type compactionGroupConfig struct { | ||
| targetFileSize int64 | ||
| scanConcurrency int | ||
| targetFileSize int64 | ||
| scanConcurrency int | ||
| arrowBatchSize int | ||
| recordBatchBufferSize int | ||
| parquetRowGroupLimit int | ||
| } | ||
|
|
||
| // WithCompactionTargetFileSize sets the size target for output files | ||
|
|
@@ -252,6 +255,47 @@ func WithCompactionScanConcurrency(n int) CompactionGroupOption { | |
| } | ||
| } | ||
|
|
||
| // WithCompactionArrowBatchSize caps the number of rows decoded per | ||
| // Arrow record batch while reading the group's tasks, forwarded to the | ||
| // scan as [WithArrowBatchSize]. Together with | ||
| // [WithCompactionRecordBatchBufferSize] it bounds the memory held by | ||
| // the record pipeline specifically: buffered batches times rows per | ||
| // batch. Delete-side memory is not covered — positional deletes and | ||
| // deletion-vector bitmaps for the group's tasks are materialized up | ||
| // front and sized by delete volume, not by these knobs. A non-positive | ||
| // value keeps the table's read.parquet.batch-size property. | ||
| func WithCompactionArrowBatchSize(n int) CompactionGroupOption { | ||
| return func(c *compactionGroupConfig) { | ||
|
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. nit — Inconsistent parameter types and names across the new option family WithArrowBatchSize and WithCompactionReadBatchSize take int64 while WithParquetRowGroupLimit, WithRecordBatchBufferSize, WithCompactionRecordBatchBufferSize and WithCompactionParquetRowGroupLimit take int, with no evident reason for the split (the batch size is ultimately consumed via props.GetInt, which returns an int). Separately, the same underlying knob is named WithArrowBatchSize at the scan layer but WithCompactionReadBatchSize at the compaction layer, whereas the other two compaction options mirror their write-layer names exactly (WithCompaction + the write option name). Suggest aligning on int and on WithCompactionArrowBatchSize for symmetry. |
||
| if n > 0 { | ||
| c.arrowBatchSize = n | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // WithCompactionRecordBatchBufferSize sets the capacity, in record | ||
| // batches, of the write pipeline's per-writer input buffer, forwarded | ||
| // to [WriteRecords] as [WithRecordBatchBufferSize]. The default is 64 | ||
| // batches. A non-positive value is ignored. | ||
| func WithCompactionRecordBatchBufferSize(n int) CompactionGroupOption { | ||
| return func(c *compactionGroupConfig) { | ||
| if n > 0 { | ||
| c.recordBatchBufferSize = n | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // WithCompactionParquetRowGroupLimit caps the rows per Parquet row | ||
| // group in the compacted output files, forwarded to [WriteRecords] as | ||
| // [WithParquetRowGroupLimit]. A non-positive value keeps the table's | ||
| // write.parquet.row-group-limit property. | ||
| func WithCompactionParquetRowGroupLimit(n int) CompactionGroupOption { | ||
| return func(c *compactionGroupConfig) { | ||
| if n > 0 { | ||
| c.parquetRowGroupLimit = n | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // RewriteDataFiles compacts the given groups by reading data with | ||
| // deletes applied, writing new consolidated files, and atomically | ||
| // replacing the old files. Position delete files that are fully | ||
|
|
@@ -378,6 +422,9 @@ func ExecuteCompactionGroup(ctx context.Context, tbl *Table, group CompactionTas | |
| if cfg.scanConcurrency > 0 { | ||
| scanOpts = append(scanOpts, WithMaxConcurrency(cfg.scanConcurrency)) | ||
| } | ||
| if cfg.arrowBatchSize > 0 { | ||
| scanOpts = append(scanOpts, WithArrowBatchSize(cfg.arrowBatchSize)) | ||
| } | ||
|
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. major — All three new ExecuteCompactionGroup forwardings can be deleted with the package still green, unlike the existing targetFileSize forwarding The three new Evidence
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. Should be resolved now. |
||
|
|
||
| // Preserve row lineage only when every source file in the group carries | ||
| // it. A mixed group (some files with FirstRowID, some without — e.g. | ||
|
|
@@ -422,6 +469,12 @@ func ExecuteCompactionGroup(ctx context.Context, tbl *Table, group CompactionTas | |
| if cfg.targetFileSize > 0 { | ||
| writeOpts = append(writeOpts, WithTargetFileSize(cfg.targetFileSize)) | ||
| } | ||
| if cfg.recordBatchBufferSize > 0 { | ||
| writeOpts = append(writeOpts, WithRecordBatchBufferSize(cfg.recordBatchBufferSize)) | ||
| } | ||
| if cfg.parquetRowGroupLimit > 0 { | ||
| writeOpts = append(writeOpts, WithParquetRowGroupLimit(cfg.parquetRowGroupLimit)) | ||
| } | ||
| if preserveLineage { | ||
| // Rebuild the arrow schema from the projected iceberg schema so the | ||
| // reserved row-lineage field IDs (_row_id, _last_updated_sequence_number) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1304,6 +1304,24 @@ func WithRowLineage() ScanOption { | |
| } | ||
| } | ||
|
|
||
| // WithArrowBatchSize caps the number of rows decoded per Arrow record | ||
| // batch when reading data files, overriding the table's | ||
| // read.parquet.batch-size property for this scan. Smaller batches bound | ||
| // the memory a scan holds per decoded batch, which matters when the | ||
| // consumer buffers batches (e.g. a compaction's read+write pipeline). | ||
| // The cap is stored on the scan itself rather than in the options map, | ||
| // so it applies regardless of ordering relative to [WithOptions]. A | ||
| // non-positive value is ignored. | ||
| func WithArrowBatchSize(n int) ScanOption { | ||
| if n <= 0 { | ||
|
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. minor — WithArrowBatchSize is silently discarded when WithOptions is applied after it WithArrowBatchSize stores into scan.options, but WithOptions (table/table.go:1280) does |
||
| return noopOption | ||
| } | ||
|
|
||
| return func(scan *Scan) { | ||
| scan.arrowBatchSize = n | ||
| } | ||
| } | ||
|
|
||
| func (t Table) Scan(opts ...ScanOption) *Scan { | ||
| s := &Scan{ | ||
| identifier: slices.Clone(t.identifier), | ||
|
|
||
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.
minor — WithCompactionReadBatchSize doc overstates the bound it provides
The comment claims the two options together bound "the memory held by the compaction's read+write pipeline: buffered batches times rows per batch". Neither knob bounds the delete-side allocations: GetRecords calls readAllDeleteFiles(ctx, as.fs, tasks, as.concurrency) and readAllDeletionVectors(ctx, as.fs, tasks, as.concurrency) (table/arrow_scanner.go:2179 and :2193), which materialise positional deletes and DV bitmaps for every task in the group up front, sized by delete volume rather than by batch size or buffer depth. On a delete-heavy compaction that term can dominate. Suggest narrowing the wording to the record pipeline specifically and noting the delete-side memory is not covered.