Skip to content

feat: add distributed zonemap index build with configurable segments#516

Merged
hamersaw merged 1 commit into
lance-format:mainfrom
beinan:user/beinan/zonemap-distributed-v2
Jun 4, 2026
Merged

feat: add distributed zonemap index build with configurable segments#516
hamersaw merged 1 commit into
lance-format:mainfrom
beinan:user/beinan/zonemap-distributed-v2

Conversation

@beinan

@beinan beinan commented May 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add zonemap as a new index type in CREATE INDEX DDL with distributed build support
  • Batch fragments into configurable segments via num_segments option (defaults to spark.default.parallelism)
  • Each segment is built in parallel on Spark executors and committed as a logical index on the driver
  • Zonemap indexes currently support single column only

What Changed

  • AddIndexExec.scala: Zonemap-specific path with ZonemapIndexJob/ZonemapIndexTask and commitIndexSegments
  • create-index.md: Document zonemap index type, options, and usage
  • Tests: unit tests for segment creation/validation and integration test

Notes

Test plan

  • CI passes (lint, unit tests, integration tests across all Spark/Scala versions)
  • Zonemap index creation with default segment count
  • Zonemap index creation with explicit num_segments
  • Repeated zonemap index creation replaces existing segments
  • Query correctness after zonemap index creation

🤖 Generated with Claude Code

@github-actions github-actions Bot added the enhancement New feature or request label May 11, 2026
@beinan beinan marked this pull request as ready for review May 11, 2026 20:24
beinan

This comment was marked as low quality.

@hamersaw hamersaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks pretty good. Thanks for the PR! A few things that we should tighten up IMO.

Comment thread lance-spark-base_2.12/src/main/java/org/lance/spark/read/LanceScanBuilder.java Outdated
Comment thread lance-spark-base_2.12/src/main/java/org/lance/spark/read/LanceScanBuilder.java Outdated
Comment thread lance-spark-base_2.12/src/main/java/org/lance/spark/read/LanceScanBuilder.java Outdated
@beinan beinan force-pushed the user/beinan/zonemap-distributed-v2 branch from 416231b to fbe05fb Compare May 12, 2026 21:55
@beinan

beinan commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review! All feedback has been addressed in the latest push (force-pushed as a single clean commit on latest main):

Scan-side changes removed entirely:

  • Removed useScalarIndex, forcePostScanFiltering, and shouldForcePostScanFiltering — zonemap fragment pruning works via the existing ZonemapFragmentPruner path without needing special scan flags. No scan-side files are modified in this PR anymore.

Index creation fixes:

  • Race in commitIndexSegments: Now captures pre-commit UUIDs and only removes indexes with those UUIDs, so concurrent writers' segments are never deleted.
  • batchFragments accuracy: Switched from ceil(N/K) to index-based slicing (slice(i*N/K, (i+1)*N/K)) to guarantee the requested segment count.
  • num_segments validation: Bounds check for <= 0 and type validation, passed through constructor (no duplicate extraction).
  • Segment failure handling: try/catch with clear error message about Lance GC cleanup.

@beinan beinan force-pushed the user/beinan/zonemap-distributed-v2 branch from 29ef8a5 to 78bb5ea Compare May 15, 2026 22:39
@beinan

beinan commented May 17, 2026

Copy link
Copy Markdown
Contributor Author

@hamersaw All review feedback has been addressed — the key change since your last review is simplifying commitIndexSegments to rely on Lance core's built-in atomic replacement (single transaction for add+remove). Would you mind taking another look when you get a chance? Thanks!

@hamersaw

Copy link
Copy Markdown
Collaborator

@LuciferYang do you mind making a pass here? Specifically, I'm interested with how this compares to your proposal (#513) to support building distributed ZoneMap indexes.

@LuciferYang

Copy link
Copy Markdown
Collaborator

@LuciferYang do you mind making a pass here? Specifically, I'm interested with how this compares to your proposal (#513) to support building distributed ZoneMap indexes.

will give feedback later today.

@LuciferYang

Copy link
Copy Markdown
Collaborator

@hamersaw @beinan Had a closer look. After the May 12 force-push on #516, the two PRs are adjacent rather than overlapping.

#516 #513 distributed #513 consolidated (opt-in)
Tasks 1 per num_segments batch 1 per fragment 1 per fragment (compute), driver write
Segments on disk num_segments (default = min(fragments, defaultParallelism)) = fragment count 1
Commit API commitExistingIndexSegments manual AddIndexOperation + Transaction + CommitBuilder same as distributed
Upstream blocker none (project's already on lance-core 7.0.0-beta.10) none lance-format/lance#6779 + #6780, both unmerged

The main difference between the two distributed paths is that num_segments on #516 is one knob doing two jobs: parallelism and segment count are the same lever. At num_segments=1 you get one segment, but the work serialises onto a single executor — not the same as #513's consolidated path, which keeps compute parallel and only centralises the write. That decoupled corner only opens up once #6779/#6780 land.

For reference, sf=100 store_sales (234 fragments, ~288M rows) under #513: distributed = 15.0 s / 234 segments / 1.1 MB; consolidated = 28.1 s / 1 segment / 138 KB. The ~8× footprint drop is per-file header amortisation.

What I'd want to inherit from #516:

Suggested path:

  1. Land feat: add distributed zonemap index build with configurable segments #516 as-is.
  2. After it lands, I'll rebase feat(sql): support USING zonemap with distributed and consolidated build paths #513 distributed onto commitExistingIndexSegments. The fragment-id exception wrapping in ZonemapFragmentTask is worth porting onto ZonemapIndexTask while we're there. After that, feat(sql): support USING zonemap with distributed and consolidated build paths #513 only carries the consolidated path.
  3. Consolidated lands as an opt-in (spark.lance.zonemap.consolidate.enabled) once #6779/#6780 release. Default-off — driver allocator holds every per-fragment Arrow batch until writeZonemapIndexFromBatches consumes them, so it can regress at very-high fragment counts.

One nit on #516, will leave inline: targetTasks = math.min(fragmentIds.size, n) silently clamps num_segments=1000 to fragment count. The doc string reads like num_segments is a target, not an upper bound. Either log when clamping or reword the doc.

@beinan

beinan commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

@LuciferYang Thanks for the thorough comparison — the side-by-side table is really helpful.

You're right about num_segments doing double duty. The latest push addresses the nit:

  • num_segments doc now clarifies it's an upper bound clamped to fragment count
  • Added a log message when clamping occurs so it's not silent
  • Switched batchFragments to index-based slicing to guarantee the requested segment count

Agreed on the suggested path — happy to land #516 as the distributed foundation, then have #513 rebase its distributed path onto commitExistingIndexSegments and carry the consolidated path as an opt-in once #6779/#6780 land. The fragment-id exception wrapping from ZonemapFragmentTask is a good addition to port over as well.

@LuciferYang LuciferYang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Does the PR description also need to be updated?

Comment thread docs/src/operations/ddl/create-index.md Outdated

@LuciferYang LuciferYang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The "What Changed" section still references LanceScanBuilder.java, LanceScan.java, LanceInputPartition.java, LanceFragmentScanner.java, and LanceCountStarPartitionReader.java, plus the bullet "Add segmented zonemap scan support with Spark-side post-scan filtering fallback" in Summary. None of this is in the current diff.

others LGTM

Add zonemap as a new index type in CREATE INDEX DDL with distributed build support.
Each segment is built in parallel on Spark executors and committed as a logical index
on the driver.

Co-Authored-By: Beinan Wang <beinanwang@microsoft.com>
@beinan beinan force-pushed the user/beinan/zonemap-distributed-v2 branch from 804c1b9 to 5c18049 Compare May 27, 2026 21:23
@beinan

beinan commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

The "What Changed" section still references LanceScanBuilder.java, LanceScan.java, LanceInputPartition.java, LanceFragmentScanner.java, and LanceCountStarPartitionReader.java, plus the bullet "Add segmented zonemap scan support with Spark-side post-scan filtering fallback" in Summary. None of this is in the current diff.

others LGTM

Sorry for my delay, just updated. can we merge this pr? @LuciferYang @hamersaw

@hamersaw hamersaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the iterations on this!

@hamersaw hamersaw merged commit c9df56e into lance-format:main Jun 4, 2026
17 checks passed
LuciferYang added a commit to LuciferYang/lance-spark that referenced this pull request Jun 4, 2026
Black-box end-to-end gate for lance-format#510: a standalone `CREATE INDEX ... USING
zonemap` index (the lance-format#516 build path) must feed per-column min/max to Spark's
CBO, and the kill-switch must turn it off. Asserts the optimizer's row-count
estimate for an impossible range predicate tightens only when column stats are
reported — the proof the unit tests (ColumnStatsAggregatorTest) cannot give.

- BaseZonemapColumnStatsTest (base): logic + two cases (standalone zonemap,
  num_segments>1 segmented build).
- 3.4 / 3.5 runners; 4.0 / 4.1 inherit the 3.5 runner via build-helper.

Requires rebase onto main for lance-format#516's USING zonemap support to run green.
ivscheianu added a commit to ivscheianu/lance-spark that referenced this pull request Jun 5, 2026
Resolves conflict in AddIndexExec.scala between:
- feat: support deferred index creation with WITH (train=false)
- feat: add distributed zonemap index build with configurable segments (lance-format#516)

The resolution preserves both intents:
- ZONEMAP early-returns via useLogicalSegmentCommit before the train check,
  so train=false correctly applies only to BTree/FTS (non-segmented) paths.
- createIndexJob is only called when train=true, avoiding wasted work.
ivscheianu pushed a commit to ivscheianu/lance-spark that referenced this pull request Jun 12, 2026
…ance-format#516)

## Summary

- Add zonemap as a new index type in `CREATE INDEX` DDL with distributed
build support
- Batch fragments into configurable segments via `num_segments` option
(defaults to `spark.default.parallelism`)
- Each segment is built in parallel on Spark executors and committed as
a logical index on the driver
- Zonemap indexes currently support single column only

## What Changed

- `AddIndexExec.scala`: Zonemap-specific path with
`ZonemapIndexJob`/`ZonemapIndexTask` and `commitIndexSegments`
- `create-index.md`: Document zonemap index type, options, and usage
- Tests: unit tests for segment creation/validation and integration test

## Notes

- Rebased cleanly onto current `main`
- Depends on lance-core `7.0.0-beta.10` or newer which includes zonemap
segment support
- Supersedes PR lance-format#473 and closed PR lance-format#466

## Test plan

- [x] CI passes (lint, unit tests, integration tests across all
Spark/Scala versions)
- [x] Zonemap index creation with default segment count
- [x] Zonemap index creation with explicit `num_segments`
- [x] Repeated zonemap index creation replaces existing segments
- [x] Query correctness after zonemap index creation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Beinan Wang <beinanwang@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants