Skip to content

Skip index write interface - #9413

Open
joacoc wants to merge 16 commits into
vortex-data:developfrom
joacoc:skip-index-writer-integration
Open

Skip index write interface#9413
joacoc wants to merge 16 commits into
vortex-data:developfrom
joacoc:skip-index-writer-integration

Conversation

@joacoc

@joacoc joacoc commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

This PR contains the implementation for the skip index write interface. Based on the previous PR #8933, which already had a working example on a Bloom filter prototype. So, this PR removes the Bloom filter bits (except for the file/skip-index integration test) and consolidates the skip-index interface.

What changes are included in this PR?

This PR carries over the following from #8933, with some changes:

  • The SkipIndex trait, which provides an aggregate_fn and registers its required components with a session.
  • WriteStrategyBuilder::with_field_zoned_options.
  • The Bloom skipping-index integration test. The Bloom implementation itself is tracked in #9398.
  • The relocation of the min/max statistics into a new aggregates dir, where other skipping-index aggregate functions can live.

This PR also adds the following notable changes:

  • SkipIndexRef and SkipIndexSessionExt for simpler registration and handling.
  • Updates to ZonedLayoutOptions and ZonedStrategy so that they store configured skipping indexes and resolve their aggregates while writing, rather than during configuration.
  • Updates to StatsRewriteCtx and zoned pruning so rewrite rules can access the aggregate functions, and allow them to use the serialized aggregate options.

What APIs are changed? Are there any user-facing changes?

Yes. This PR contains the following user-facing changes:

  1. WriteStrategyBuilder adds with_field_zoned_options, which configures zoned-layout options for a field.
  2. SkipIndexSessionExt adds skipping-index registration methods to VortexSession.
  3. ZonedLayoutOptions adds with_skip_index, which configures a skipping index for the zoned layout.

@joacoc
joacoc force-pushed the skip-index-writer-integration branch from ce89894 to 8c2bdfd Compare August 18, 2026 15:22
@codspeed-hq

codspeed-hq Bot commented Aug 27, 2026

Copy link
Copy Markdown

Merging this PR will regress 2 benchmarks

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 4 improved benchmarks
❌ 2 regressed benchmarks
✅ 2193 untouched benchmarks
⏩ 206 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation allocate_drop_arrow[0] 402.7 ns 456.9 ns -11.86%
WallTime mul_u32_nonnull_avx512 5.6 µs 6.3 µs -10.23%
WallTime arrow_checked_add_u32_neon[16384] 20.3 µs 13.3 µs +52.74%
Simulation decompress[u64, (4000, 1024)] 85.6 µs 70.4 µs +21.59%
WallTime arrow_checked_add_u32_avx512[16384] 21.3 µs 17.6 µs +21.11%
WallTime add_shapes_neon[(128, PerRowPerRow)] 2 µs 1.8 µs +10.64%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing joacoc:skip-index-writer-integration (97fa19c) with develop (4afdbd4)

Open in CodSpeed

Footnotes

  1. 206 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@joacoc
joacoc force-pushed the skip-index-writer-integration branch from 69ae560 to 868debd Compare September 2, 2026 08:23
@joacoc joacoc changed the title [WIP] Skip index write interface Skip index write interface Sep 2, 2026
@joacoc
joacoc force-pushed the skip-index-writer-integration branch from 931fc7e to 635ad9d Compare September 3, 2026 11:53
@connortsui20 connortsui20 mentioned this pull request Sep 3, 2026
5 tasks
@connortsui20 connortsui20 added the changelog/feature A new feature label Sep 3, 2026
connortsui20 and others added 11 commits September 3, 2026 18:02
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
This is just a cleanup of the bloom filter code that is being
addressed in another pull request.

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
…t objects

Initially the `dtype` was necessary when declaring the `SkipIndex/Options`,
but this requires the caller to carry (or get) the target zone column `dtype` and
instantiating multiple indexes, but we can defer this behaviour to the zone writer,
so that when the zone writer initializes the default aggregation fns,
also initializes the index aggregation fns. At this point,
we can get the `dtype` from the writer column `stream`.

So, this transforms the interface from:
Before: `.with_skip_index(&index, dtype, session)`
After: `.with_skip_index(Arc::new(index))`

But to have this behaviour, I had to update `.with_skip_index()` to store
indexes as `Arc<dyn SkipIndex>` instead of trying to build on call. Also,
I had to add to the zone writer the list of `SkipIndex` that should init
and use during writes.

Still need to dedupe `skip_indexes` when pushing (left a TODO), and since
skip indexes are now shared via `Arc`, I want to double check there's no
internal mutability creeping in that would make that sharing unsafe.

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
This commit adds one new type SkipIndexRef (Arc<dyn SkipIndex>)
to have more freedom handling the skip index. Also added a new trait
called `SkipIndexSessionExt` that the idea is to do
`session.register_skip_index(skip_index);`.
But having something like `session.register(skip_index)`
would be more ideal.

Also the mutation from strategy was removed, the writer was updated
to handle the user fns correctly, and also the test was updated
to use the new bounded expressions.

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
One of the pending TODOs was to remove duplicate skip indexes or
aggregate functions. This relies on the implementation retrieving
the underlying aggregate function ID. In the future, this could be
an "index ID".

Also document the two new module files.

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Skip-index duplicate detection was removed.
I could have retained the code that checks if the same index is added twice,
but it would have made the implementation more complex.
At the end, configuring duplicate skip indexes is undesired,
but it affects performance rather than correctness.

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
@joacoc
joacoc force-pushed the skip-index-writer-integration branch from 635ad9d to 113e6b4 Compare September 3, 2026 16:05
…writer

This commit is mostly about integrating the Bloom filter with the skip
index trait and zoned writer.

But, getting this integration right requires using the aggregate
function options serialized in the file. To make those options
available to rewrite rules, this commit adds `aggregate_fns` to
`StatsRewriteCtx`.

During pruning, `pruning.rs` now adds the aggregate functions
deserialized from the file to the rewrite context before calling
`falsify`. This removes the need for a rewrite rule to store options
when it is registered.

Also, this commit also includes an end-to-end test to
assert that a skip index can be read without declaring any options on
the reader, as it should.

Index registration now requires only the skip index type. Before, it
required a configured instance. The instance is now needed only during
writes, where it defines the options persisted in the file.

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
@joacoc

joacoc commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

One part I'd highlight for review are the changes to rewrite.rs and pruning.rs.

I needed a way to pass the serialized aggregate fn options from the file to StatsRewriteRule during pruning. Passing the aggregate functions through StatsRewriteCtx does the work, but I’m not sure if there’s a better or more appropriate way, since I’m not too familiar with this.

The interface ended up like this:

let session = VortexSession::default();
let index = BloomSkipIndex::new(BloomOptions::default());

// Registration (enough for reads)
session.register_skip_index(&index);

// Configuration for writes
let strategy = WriteStrategyBuilder::default()
    .with_field_aggregates(field_path!(id), [index.aggregate_fn()])
    .build();

session
    .write_options()
    // Required because Bloom filters are not currently part of any edition
    .disable_editions()
    .with_strategy(strategy);

@joacoc
joacoc marked this pull request as ready for review September 4, 2026 16:01
The biggest change in this commit is how the skip index
trait works. Now it defines two types, Aggregate and Scalar,
and I assume that a skip index will only require
one AggregateFnVTable and one ScalarFnVTable, but
potentially multiple rewrite rules.
So, for anyone implementing a skip index, the trait
already defines the limits and registers everything in
a session.

This commit also updates the writer. Given that the index's
underlying aggregate_fn is the only piece that interacts
with the writer, only that function is passed down,
and the rest remains outside it. This also avoids having
to pass an unnecessary new type (SkipIndex) down into
the writer. At the same time, it means that an index
will behave the same way as aggregation fns behave
with unsupported types (by omitting the unsupported aggregate).

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Use repartition block size using writer block size
rather than zone options block size.

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
Zone layout options for the write strategy builder
were introduced just to pass the aggregate fn for a field,
so what if we just passed that aggregate fn rather than
the layout options, and kept the options configuration
as it was before? That's the idea of this commit. It
simplifies aggregate fn handling and reduces complexity,
but is less expressive for layout options.

Signed-off-by: Joaquin Colacci <joaquincolacci@gmail.com>
@joseph-isaacs

Copy link
Copy Markdown
Contributor

What is the intended difference between this and ZonedLayout (

pub type ZonedLayout = Layout<Zoned>;
).

That is also a skip index.

@connortsui20

Copy link
Copy Markdown
Member

@joseph-isaacs this hooks into zoned layout (it explains this in the description). I think this is generally the right direction, we want a nicer interface for aggregations that we know are going to be treated as indexes specifically and not just statistics

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants