Skip to content

perf(table): pre-size positional delete positions - #1923

Open
fallintoplace wants to merge 2 commits into
apache:mainfrom
fallintoplace:perf/preallocate-positional-delete-set
Open

perf(table): pre-size positional delete positions#1923
fallintoplace wants to merge 2 commits into
apache:mainfrom
fallintoplace:perf/preallocate-positional-delete-set

Conversation

@fallintoplace

Copy link
Copy Markdown
Contributor

What changed

  • Sum the lengths of the positional-delete Arrow chunks before creating the set.
  • Use that total as the initial map capacity.
  • Keep the existing validation and insertion logic unchanged.
  • Keep nil chunks safe and add coverage for duplicates across chunks.
  • Add a focused benchmark for 1K, 100K, and 1M positions.

Benchmark

Ran on an Apple M1 Pro (darwin/arm64) with Go go1.26.3.

go test ./table -run '^$' -bench '^BenchmarkCollectPosDeletePositions$' -benchmem -count=5 -benchtime=1s

Median of 5 runs:

Input ns/op before ns/op after B/op before -> after allocs/op before -> after
1K positions, 1 chunk 39215 11898 74456 -> 36992 22 -> 6
100K positions, 4 chunks 3427952 1500532 4729533 -> 2364594 532 -> 258
1M positions, 16 chunks 58026680 45005787 75615856 -> 37832769 8210 -> 4098

Checks

  • go test ./...
  • go vet ./...
  • go test -race ./table

@zeroshade zeroshade left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unique-position benchmark improves, but sizing the set from raw row count creates severe over-allocation for valid duplicate-heavy delete files.

Blocking — Bound preallocation by expected unique cardinality (table/arrow_scanner.go:466–474)

totalPositions := 0
for _, chunk := range positionalDeletes {
    if chunk != nil {
        totalPositions += chunk.Len()
    }
}

deletes := make(set[int64], totalPositions) // raw rows, not unique positions

Position deletes are combined across files and may legitimately overlap. With 1,000,000 identical positions across 16 chunks, I measured:

  • Previous growth strategy: cardinality 1, 0 B/op, roughly 4–6 ms
  • This change: cardinality 1, approximately 37.8 MB/op, 4,098 allocations, roughly 7–20 ms

At larger scales or across concurrent scan tasks, valid overlapping deletes can reserve gigabytes despite producing a tiny set. The current benchmarks use only unique values, so they hide this regression. The capacity allocation also happens before type/null/negative validation.

Please retain cardinality-driven growth or use a benchmark-justified bounded hint such as min(totalPositions, fixedCap). Add duplicate-heavy benchmark coverage alongside the unique cases.


This review was drafted by an AI-assisted tool and confirmed by an Iceberg Go maintainer. After you've addressed the points above and pushed an update, an Iceberg Go maintainer — a real person — will take the next look at the PR. The findings cite the project's review criteria; if you think one of them is mis-applied, please reply on the PR and a maintainer will weigh in.

More on how Iceberg Go handles maintainer review:
CONTRIBUTING.md.

@fallintoplace
fallintoplace force-pushed the perf/preallocate-positional-delete-set branch from 00ec6b4 to eb2939b Compare August 27, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants