Repository navigation
feat(array): min/max for fixed-size lists, so UUID columns get zone-map stats - #10457
XiangpengHao wants to merge 7 commits into
Conversation
…ap stats `vortex.uuid` is stored as `FixedSizeList(U8, 16)`, and the min/max aggregate skipped fixed-size lists as a stop-gap (vortex-data#8243), so UUID zone maps and footers kept only null counts and `uuid = x` pruned nothing. Compute min/max for fixed-size lists of fixed-width elements (bool, primitive, decimal), ordered by the compare kernel's own row comparator so statistics and comparisons always agree. Non-nullable byte lists such as UUIDs compare as byte slices. Lists wider than 64 bytes per value get none, so embedding-style columns do not grow zone maps. DuckDB's COPY RETURN_STATS and the JNI writer now convert UUID and fixed-size-list bounds instead of failing on them. Signed-off-by: XiangpengHao <xiangpeng@spiraldb.com>
Signed-off-by: XiangpengHao <xiangpeng@spiraldb.com>
Merging this PR will degrade performance by 9.13%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | WallTime | threshold_neon[8] |
1.4 µs | 2 µs | -26.01% |
| ❌ | WallTime | filter_neon[8] |
869 ns | 1,140 ns | -23.77% |
| ❌ | WallTime | threshold_neon[80] |
4.2 µs | 5.2 µs | -20.08% |
| ❌ | WallTime | threshold_neon[64] |
2.6 µs | 3.2 µs | -16.86% |
| ❌ | Simulation | test_scan_filter_to_arrow_array[int_float] |
3.5 ms | 4.1 ms | -13.75% |
| ❌ | Simulation | test_repeated_scan_filter[int_float] |
3.4 ms | 3.9 ms | -11.11% |
| ❌ | WallTime | decode_chunked_neon[i64, 524288] |
13.2 µs | 14.7 µs | -10.3% |
| ❌ | WallTime | filter_neon[64] |
1.7 µs | 1.9 µs | -10.3% |
| ❌ | Simulation | test_repeated_scan_filter[int] |
2.5 ms | 2.7 ms | -10.22% |
| ⚡ | Simulation | test_repeated_scan_scalar_at[int_float] |
486.7 µs | 400.4 µs | +21.58% |
| ⚡ |
Simulation | random_i128[0.01] |
5.9 µs | 5.2 µs | +13.31% |
| ⚡ | WallTime | words_gather_dispatch_avx2[65536] |
1.4 µs | 1.3 µs | +10.87% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing XiangpengHao:xiangpeng/fsl-min-max (dd0aa74) with develop (18aebea)
Footnotes
-
31 benchmarks were run, but are now archived. If they were deleted in another branch, consider rebasing to remove them from the report. Instead if they were added back, click here to restore them. ↩
… own Min/max of an extension array is computed on its storage, which is only meaningful when the type orders like its storage. Add `ExtVTable::orders_by_storage` (default `false`) and opt in timestamp, date, time and UUID. Other extension types, such as JSON, geometries, vectors and unknown foreign types, get no extrema in any encoding. Signed-off-by: XiangpengHao <xiangpeng@spiraldb.com>
| /// Statistics such as zone maps and file footers store the extrema of every chunk they cover, so | ||
| /// the extrema of wide lists such as embedding vectors would grow them without enabling useful | ||
| /// pruning. UUIDs (16 bytes) and small fixed-size vectors fit. | ||
| const MAX_FIXED_SIZE_LIST_MIN_MAX_BYTES: usize = 64; |
There was a problem hiding this comment.
I think we need to make sure this is respected everywhere - if we execute to Columnar Constant might not obey it thus depending on encoding you end up with different stats
There was a problem hiding this comment.
we want to use BoundedMax/Min here too?
robert3005
left a comment
There was a problem hiding this comment.
My clankers are telling me that now we can publish min max stats for degenerate cases where list size is 0. There's a small follow up to publish uuid stats in duckdb.
MinMax capped fixed-size lists at 64 bytes so that wide lists such as embeddings would not grow stored statistics. That made a general aggregate refuse to compute some extrema, and the cap had to be kept consistent across encodings by hand. MinMax now computes any fixed-size list of fixed-width elements, and the size limit is applied where statistics are stored, with the byte budget those writers already use for strings. `lower_bound`/`upper_bound` now take a `Scalar` and dispatch on its dtype: strings and binary are truncated as before, and a fixed-size list, which cannot be shortened without changing its dtype, is kept whole if it fits and has no bound otherwise. `can_bound_extrema` makes the same decision from a dtype, following extensions to their storage. - BoundedMax/BoundedMin reject fixed-size lists wider than `max_bytes`. - Zone maps record fixed-size lists, and extensions stored as one, with BoundedMax/BoundedMin. - File statistics skip min/max for dtypes that cannot be bounded. - The flat and CUDA writers share `StatsSetRef::bound_min_max` instead of each truncating Utf8/Binary stats. Signed-off-by: XiangpengHao <xiangpeng@spiraldb.com>
…gpeng/fsl-min-max
|
Thank you @robert3005 @joseph-isaacs, moved the max cap out, and the length is enforced where the stats are written. the bound applies to min_max’s result, every encoding should get the same stats. |
Summary
vortex.uuidis stored asFixedSizeList(U8, 16), and the min/max aggregate skips fixed-size lists as a stop-gap (#8243, for #8189). UUID zone maps and file footers therefore keep only null counts, anduuid_col = xprunes nothing, which hurts sorted UUIDv7 keys most. This implements min/max for fixed-size lists, so UUID columns get the same zone-map and footer statistics, and the same pruning, as Parquet's UUID logical type.Changes
FixedSizeList(aggregate_fn/fns/min_max/fixed_size_list.rs): lists of fixed-width elements (bool, primitive, decimal), ordered lexicographically by the compare kernel's own row comparator (compare/nested.rs), so statistics and comparisons always agree. Null lists are skipped, null elements order first, floats use total order. Non-nullableu8lists (the UUID case) compare as byte slices, the same order as Postgres, DuckDB and Parquet.MAX_FIXED_SIZE_LIST_MIN_MAX_BYTES(64 bytes per value) get no min/max, so embedding-style columns don't grow zone maps for pruning they can't use. The result is the same for every encoding.vortex.uuidgets min/max through its storage; no UUID-specific code.COPY … RETURN_STATSconverts UUID bounds toUUIDand fixed-size-list bounds toARRAYinstead of failing on them; the JNI writer converts them tojava.util.UUIDandList.vortex/tests/uuid_pruning.rs(a UUID column over four zones;uuid = xkeeps only the zone that can hold it, footer min/max,can_prune),fixed_size_list::tests(ordering vsScalar::partial_cmpandcompare, nulls, floats, sliced and dictionary inputs, the cap at its boundary),vortex-uuidmin/max order, DuckDB conversion tests andcopy_return_stats.slt.JNIWriterTest.testUuidAndFixedSizeListBoundsis added but was not run locally (no JDK).API Changes
Files written with this change carry min/max for UUID and narrow fixed-size-list columns. Engines that receive file statistics (DuckDB
RETURN_STATS, the JNI writer's column statistics) now see bounds for those columns.