Skip to content

Commit 931fc7e

Browse files
committed
(improvement) docs, tests and double check removal
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>
1 parent db900e1 commit 931fc7e

3 files changed

Lines changed: 101 additions & 60 deletions

File tree

‎vortex-file/tests/bloom_skip_index.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ fn strategy(
140140

141141
// Adding the aggregate to these field-specific zoned options is the explicit write-side
142142
// opt-in. Registering the index in the session alone does not change the file layout.
143-
options = options.with_skip_index(index.clone())?;
143+
options = options.with_skip_index(index.clone());
144144

145145
Ok(WriteStrategyBuilder::default()
146146
.with_field_zoned_options(field_path!(id), options)

‎vortex-layout/src/layouts/zoned/skip_index/mod.rs‎

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,26 @@
33

44
//! Skipping-index interface and implementations.
55
//!
6-
//! The skip index provides a way to prune zones
7-
//! by using the results of an aggregation function.
8-
//! During writes, an aggregation function calculates stats for each zone.
6+
//! The skip index provides a way to prune zones by using the results
7+
//! of an aggregation function.
8+
//! During writes, the aggregation function calculates stats for each zone.
99
//! During reads, these stats are interpreted to determine whether a zone
1010
//! can be pruned.
1111
//!
12-
//! Unlike a locating index, a skipping index summarizes a contiguous
13-
//! range of rows in a zone. It does not locate matching rows. It only
14-
//! proves that a zone cannot match a predicate.
15-
//!
16-
//! For correct reading and writing, a session must have registered the skip index.
17-
//! If a skip index is not loaded and the session allows unknown plugins,
18-
//! Vortex disables zoned pruning and scans the data normally. Otherwise,
19-
//! opening the file fails.
20-
//!
2112
//! The skip index is coupled to the zoned layout through [`ZonedLayoutOptions`].
2213
//! To set one up, a user must declare a skip index for a field
2314
//! through the writer configuration.
15+
//!
16+
//! ### Difference from a locating index
17+
//!
18+
//! Unlike a locating index, a skipping index summarizes a zone.
19+
//! It does not locate matching rows. It can only prove that a zone
20+
//! cannot match a predicate.
2421
2522
use std::sync::Arc;
2623

27-
use vortex_array::aggregate_fn::AggregateFnId;
2824
use vortex_array::aggregate_fn::AggregateFnRef;
2925
use vortex_array::dtype::DType;
30-
use vortex_error::VortexResult;
31-
use vortex_error::vortex_bail;
3226
use vortex_session::SessionExt;
3327
use vortex_session::VortexSession;
3428

@@ -40,10 +34,13 @@ use super::writer::ZonedLayoutOptions;
4034
/// The writer helper [`ZonedLayoutOptions::with_skip_index`] is the explicit per-column declaration
4135
/// seam. Readers call [`SkipIndexSessionExt::register_skip_index`] on their
4236
/// [`VortexSession`] before opening the file.
37+
///
38+
/// ## Logical and physical representation
39+
///
40+
/// [`SkipIndex`] defines the logical skip-index abstraction. A skip index has no identifier
41+
/// or serialized representation of its own. Instead, it is represented in a Vortex file by
42+
/// the serialized [`AggregateFnRef`] it provides.
4343
pub trait SkipIndex: Send + Sync + 'static {
44-
/// Returns the inner aggregate identifier.
45-
fn aggregate_id(&self) -> AggregateFnId;
46-
4744
/// The aggregate state to persist for `input_dtype`, or `None` when unsupported.
4845
fn aggregate_fn(&self, input_dtype: &DType) -> Option<AggregateFnRef>;
4946

@@ -87,10 +84,6 @@ impl SkipIndexRef {
8784
SkipIndexRef(index_ref)
8885
}
8986

90-
pub fn aggregate_id(&self) -> AggregateFnId {
91-
self.0.aggregate_id()
92-
}
93-
9487
pub fn aggregate_fn(&self, input_dtype: &DType) -> Option<AggregateFnRef> {
9588
self.0.aggregate_fn(input_dtype)
9689
}
@@ -105,25 +98,16 @@ impl ZonedLayoutOptions {
10598
///
10699
/// `WriteStrategyBuilder::with_field_zoned_options` can install the configured options for one
107100
/// field while retaining the default data layout pipeline.
108-
pub fn with_skip_index(mut self, skip_index: SkipIndexRef) -> VortexResult<Self> {
101+
pub fn with_skip_index(mut self, skip_index: SkipIndexRef) -> Self {
109102
let mut skip_indexes = self
110103
.skip_indexes
111104
.take()
112105
.map(|indexes| indexes.to_vec())
113106
.unwrap_or_default();
114107

115-
if skip_indexes
116-
.iter()
117-
.any(|index| index.aggregate_id() == skip_index.aggregate_id())
118-
{
119-
vortex_bail!(
120-
"skip index aggregate {} is already configured",
121-
skip_index.aggregate_id()
122-
);
123-
}
124-
125108
skip_indexes.push(skip_index);
126109
self.skip_indexes = Some(skip_indexes.into());
127-
Ok(self)
110+
111+
self
128112
}
129113
}

‎vortex-layout/src/layouts/zoned/writer.rs‎

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
//! Write-time assembly for zoned layouts.
2-
31
// SPDX-License-Identifier: Apache-2.0
42
// SPDX-FileCopyrightText: Copyright the Vortex contributors
53

4+
//! Write-time assembly for zoned layouts.
5+
66
use std::num::NonZeroUsize;
77
use std::sync::Arc;
88

@@ -112,24 +112,8 @@ impl LayoutStrategy for ZonedStrategy {
112112
if let Some(skip_indexes) = &self.options.skip_indexes {
113113
for skip_index in skip_indexes.iter() {
114114
match skip_index.aggregate_fn(stream.dtype()) {
115-
Some(skip_aggregate_fn) => {
116-
if aggregate_fns
117-
.iter()
118-
.any(|aggregate_fn| aggregate_fn.id() == skip_aggregate_fn.id())
119-
{
120-
vortex_bail!(
121-
"skip index aggregate {} is configured more than once",
122-
skip_aggregate_fn.id()
123-
);
124-
}
125-
126-
aggregate_fns.push(skip_aggregate_fn);
127-
}
128-
None => vortex_bail!(
129-
"skip index aggregate {} is unsupported for type {}",
130-
skip_index.aggregate_id(),
131-
stream.dtype()
132-
),
115+
Some(skip_aggregate_fn) => aggregate_fns.push(skip_aggregate_fn),
116+
None => vortex_bail!("skip index is unsupported for type {}", stream.dtype()),
133117
}
134118
}
135119
}
@@ -230,6 +214,8 @@ mod tests {
230214
use vortex_array::ArrayContext;
231215
use vortex_array::IntoArray;
232216
use vortex_array::aggregate_fn::AggregateFnVTable;
217+
use vortex_array::aggregate_fn::AggregateFnVTableExt;
218+
use vortex_array::aggregate_fn::NumericalAggregateOpts;
233219
use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
234220
use vortex_array::aggregate_fn::fns::bounded_min::BoundedMin;
235221
use vortex_array::aggregate_fn::fns::max::Max;
@@ -256,21 +242,22 @@ mod tests {
256242
use crate::layouts::flat::writer::FlatLayoutStrategy;
257243
use crate::layouts::zoned::Zoned;
258244
use crate::layouts::zoned::schema::default_bounded_stat_max_bytes;
245+
use crate::layouts::zoned::skip_index::SkipIndex;
259246
use crate::segments::TestSegments;
260247
use crate::sequence::SequenceId;
261248
use crate::sequence::SequentialArrayStreamExt;
262249
use crate::session::LayoutSession;
263250

264251
/// Write three zones of primitives through `ctx`, returning the aggregates the zoned
265252
/// layout recorded.
266-
fn write_zones(ctx: LayoutWriterContext) -> VortexResult<Vec<String>> {
253+
fn write_zones_with_options(
254+
ctx: LayoutWriterContext,
255+
options: ZonedLayoutOptions,
256+
) -> VortexResult<Vec<String>> {
267257
let strategy = ZonedStrategy::new(
268258
ChunkedLayoutStrategy::new(FlatLayoutStrategy::default()),
269259
FlatLayoutStrategy::default(),
270-
ZonedLayoutOptions {
271-
block_size: NonZeroUsize::new(3).vortex_expect("non zero"),
272-
..Default::default()
273-
},
260+
options,
274261
);
275262
let (ptr, eof) = SequenceId::root().split();
276263
let stream = ChunkedArray::from_iter([
@@ -306,6 +293,16 @@ mod tests {
306293
.collect())
307294
}
308295

296+
fn write_zones(ctx: LayoutWriterContext) -> VortexResult<Vec<String>> {
297+
write_zones_with_options(
298+
ctx,
299+
ZonedLayoutOptions {
300+
block_size: NonZeroUsize::new(3).vortex_expect("non zero"),
301+
..Default::default()
302+
},
303+
)
304+
}
305+
309306
#[test]
310307
fn unrestricted_context_writes_the_default_aggregates() -> VortexResult<()> {
311308
let written = write_zones(LayoutWriterContext::new(ArrayContext::empty()))?;
@@ -380,4 +377,64 @@ mod tests {
380377
.all(|aggregate_fn| !aggregate_fn.is::<Sum>())
381378
);
382379
}
380+
381+
// TODO (joacoc): replace with bloom skip index after merge <https://github.com/vortex-data/vortex/pull/9398>
382+
// Just a small skip-index implementation used to test the zoned writer until the bloom filter lands.
383+
struct TestSkipIndex {
384+
aggregate_fn: Option<AggregateFnRef>,
385+
}
386+
387+
impl TestSkipIndex {
388+
fn new(aggregate_fn: AggregateFnRef) -> Self {
389+
Self {
390+
aggregate_fn: Some(aggregate_fn),
391+
}
392+
}
393+
394+
fn unsupported() -> Self {
395+
Self { aggregate_fn: None }
396+
}
397+
398+
fn into_ref(self) -> SkipIndexRef {
399+
SkipIndexRef::new(Arc::new(self))
400+
}
401+
}
402+
403+
impl SkipIndex for TestSkipIndex {
404+
fn aggregate_fn(&self, _input_dtype: &DType) -> Option<AggregateFnRef> {
405+
self.aggregate_fn.clone()
406+
}
407+
408+
fn register(&self, _session: &VortexSession) {}
409+
}
410+
411+
#[test]
412+
fn writer_appends_skip_index_aggregate() -> VortexResult<()> {
413+
let options = ZonedLayoutOptions::default().with_skip_index(
414+
TestSkipIndex::new(Sum.bind(NumericalAggregateOpts::default())).into_ref(),
415+
);
416+
let written =
417+
write_zones_with_options(LayoutWriterContext::new(ArrayContext::empty()), options)?;
418+
419+
assert!(
420+
written.contains(&Sum.id().to_string()),
421+
"expected aggregation id present, wrote {written:?}"
422+
);
423+
Ok(())
424+
}
425+
426+
#[test]
427+
fn writer_rejects_skip_index_unsupported_for_dtype() -> VortexResult<()> {
428+
let options =
429+
ZonedLayoutOptions::default().with_skip_index(TestSkipIndex::unsupported().into_ref());
430+
431+
let error =
432+
write_zones_with_options(LayoutWriterContext::new(ArrayContext::empty()), options)
433+
.expect_err("unsupported skip index should fail the write");
434+
assert!(
435+
error.to_string().contains("unsupported for type"),
436+
"unexpected error: {error}"
437+
);
438+
Ok(())
439+
}
383440
}

0 commit comments

Comments
 (0)