Background
lance-spark currently loads zonemap statistics on the driver, but pruning only retains matching fragment IDs. The zoneStart and zoneLength information is discarded, so executors still scan the entire surviving fragment.
Lance draft PR lance-format/lance#8951 adds Java/JNI FragmentSlice support:
new FragmentSlice(fragmentId, rowOffset, rowCount)
A slice represents the physical row range [rowOffset, rowOffset + rowCount) inside a fragment. Native Lance converts it to the dataset _rowid domain and executes it through the existing row-address prefilter path.
This issue tracks the lance-spark integration after that Lance PR is merged and released.
Related umbrella issue: #34.
Proposed changes
- Preserve matching zonemap ranges instead of reducing them to fragment IDs.
- Convert candidate zones to
FragmentSlice values and merge adjacent or overlapping slices in the same fragment.
- Extend
LanceSplit / LanceInputPartition to carry optional fragment slices.
- Keep one fragment per Spark input partition in the first implementation.
- Pass slices to both:
- the ordinary columnar fragment reader;
- the pushed-down
COUNT(*) reader.
- Continue applying the complete pushed filter as a residual predicate.
- Keep using the dataset version pinned during driver planning.
Predicate semantics
Slice pruning must remain conservative:
- predicates pushed separately by Spark are combined with AND;
- AND intersects candidate physical ranges;
- OR unions ranges only when both branches can be analyzed;
- NOT, unsupported expressions, type mismatches, or missing zonemap coverage fall back to scanning the complete affected fragment;
- deleted rows do not compact physical offsets and are removed by the normal Lance scan path.
Initial non-goals
- Generic
Scanner::plan_splits.
- Distributed BTree/Bitmap/FTS segment execution.
- Packing slices from multiple fragments into one Spark partition.
- Replacing the residual filter with zonemap evaluation.
- Exposing row addresses to Java or Spark.
Acceptance criteria
- A predicate matching only part of a fragment scans the corresponding physical slices.
- Multiple disjoint, adjacent, and overlapping zones are handled correctly.
- Deleted rows do not shift slice offsets.
- Multi-column AND and supported OR predicates produce correct ranges.
- Unsupported predicates and incomplete index coverage conservatively retain data.
- Ordinary reads and pushed-down
COUNT(*) return identical results.
- Projection, filter, limit,
_rowaddr, and blob-column paths remain correct.
- Executors use the same pinned dataset snapshot used for driver planning.
- Metrics or tests demonstrate that fewer physical rows/ranges are planned, not only that the final result is correct.
Dependency
Blocked by the merge and Java artifact release of:
Background
lance-spark currently loads zonemap statistics on the driver, but pruning only retains matching fragment IDs. The
zoneStartandzoneLengthinformation is discarded, so executors still scan the entire surviving fragment.Lance draft PR lance-format/lance#8951 adds Java/JNI
FragmentSlicesupport:A slice represents the physical row range
[rowOffset, rowOffset + rowCount)inside a fragment. Native Lance converts it to the dataset_rowiddomain and executes it through the existing row-address prefilter path.This issue tracks the lance-spark integration after that Lance PR is merged and released.
Related umbrella issue: #34.
Proposed changes
FragmentSlicevalues and merge adjacent or overlapping slices in the same fragment.LanceSplit/LanceInputPartitionto carry optional fragment slices.COUNT(*)reader.Predicate semantics
Slice pruning must remain conservative:
Initial non-goals
Scanner::plan_splits.Acceptance criteria
COUNT(*)return identical results._rowaddr, and blob-column paths remain correct.Dependency
Blocked by the merge and Java artifact release of: