Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f220523
Add batched positional reads
joseph-isaacs Aug 12, 2026
d6d69d4
Stream batched positional read results
joseph-isaacs Aug 12, 2026
4d5f554
Monitor positional read batch sizes
joseph-isaacs Aug 12, 2026
5e0f2a4
Keep positional read concurrency saturated
joseph-isaacs Aug 12, 2026
1444be5
Optimize batched object store reads
joseph-isaacs Aug 13, 2026
c36bdb7
Tune local read coalescing
joseph-isaacs Aug 13, 2026
1ecd351
Add optional io_uring local reads
joseph-isaacs Aug 13, 2026
47aa47e
Use direct reads for local scan files
joseph-isaacs Aug 13, 2026
1d6fc4c
Add partial segment range requests
joseph-isaacs Aug 13, 2026
ef156f4
Reuse blocking workers across positional read batches
joseph-isaacs Aug 13, 2026
59bb8be
Submit partial segment ranges as bounded batches
joseph-isaacs Aug 13, 2026
4435efb
Forward grouped reads through the segment cache
joseph-isaacs Aug 13, 2026
e3bfe1b
Keep partial range submissions batched
joseph-isaacs Aug 13, 2026
23eba74
Read Flat arrays from partial segment ranges
joseph-isaacs Aug 12, 2026
be88673
Issue ALPRD partial reads in one round
joseph-isaacs Aug 12, 2026
29978e9
Keep Flat partial reads to one I/O round
joseph-isaacs Aug 12, 2026
cb9012a
Canonicalize ALPRD patch indices once
joseph-isaacs Aug 13, 2026
58a6a85
Fix positional read driver test
joseph-isaacs Aug 14, 2026
41615a2
Optimize partial Flat random access
joseph-isaacs Aug 14, 2026
91d9a93
Run random access CI with batched io_uring
joseph-isaacs Aug 14, 2026
a0cb6d7
Cache decoded ALPRD patches for partial reads
joseph-isaacs Aug 14, 2026
6fe5a3a
Remove decoded ALPRD patch cache
joseph-isaacs Aug 14, 2026
cf4ae3a
Reduce partial ALPRD array overhead
joseph-isaacs Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/pr-bench-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ jobs:
RUST_BACKTRACE: full
VORTEX_EXPERIMENTAL_PATCHED_ARRAY: "1"
FLAT_LAYOUT_INLINE_ARRAY_NODE: "1"
VORTEX_IO_URING: "1"
VORTEX_IO_URING_RINGS: "4"
VORTEX_IO_URING_QUEUE_DEPTH: "128"
VORTEX_IO_URING_MIN_READ_SIZE: "0"
VORTEX_IO_URING_MAX_IN_FLIGHT: "512"
run: |
python3 scripts/random-access-split.py

Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ geoarrow = "0.8.0"
geoarrow-cast = "0.8.0"
get_dir = "0.5.0"
glob = "0.3.2"
io-uring = "0.7.13"
goldenfile = "1"
half = { version = "2.7.1", features = ["std", "num-traits"] }
hashbrown = "0.17.1"
Expand Down
88 changes: 88 additions & 0 deletions benchmark-results/pr-9416-random-access/HANDOVER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# PR #9416 random-access handover

## Scope

- PR: https://github.com/vortex-data/vortex/pull/9416
- Branch: `ji/partial-flat-random-access-batched-ring`
- Worktree: `/mnt/vortex-ssd/worktrees/random-access-pr9416-ring`
- Workload: feature-vectors / uniform, pinned to cores 0-7.
- The decoded ALPRD patch cache was removed. Do not reintroduce a data cache for the I/O comparison.
- The `action/bench-random-access` label was applied after removing the cache.

## Retained change

The ALPRD partial-read path no longer calls `clear_stats` on the newly constructed partial array.
Those `BitPacked`, `Patches`, `ALPRD`, and `FixedSizeList` arrays begin with empty statistics. The
fixed-width path still clears statistics inherited from its serialized array tree.

Three five-second cached runs on 8 cores:

| Variant | Vortex runs | Median | Lance median |
|---|---|---:|---:|
| No-cache baseline | 1.612 / 1.621 / 1.680 ms | 1.621 ms | 1.059 ms |
| Skip redundant ALPRD stats clear | 1.594 / 1.602 / 1.607 ms | 1.601 ms | 1.058 ms |

This is a 1.2% Vortex improvement and changes neither I/O nor serialization. A final cold rerun is
still desirable, although the change occurs entirely after I/O.

## Segment and patch measurements

The complete 105-row measurement is in `feature-vectors-uniform-segments.csv`.

- Uniform selects 105 physical Flat segments.
- Every segment has 256 vectors x 1,024 values = 262,144 values.
- Left buffer: 98,304 bytes per segment; right buffer: 753,664 bytes per segment.
- Patches per segment: min 953, mean 1,060.70, max 1,793.
- Patch density: 0.404627%.
- Patch bytes per segment: min 5,718, mean 6,364.23, max 10,758.
- Totals: 27,525,120 resident values, 111,374 patches, 668,244 patch bytes.
- The query selects 107,520 values and should intersect only about 435 patches, but the reader
reconstructs all 111,374 patches before slicing.
- Main data is already row-sliced: 384 left bytes + 2,944 right bytes per selected vector, or
349,440 bytes total. Patch buffers remain unsliced and add 668,244 bytes.

Conclusion: 256-vector Flat segments are not the main problem because left/right buffers support
partial row reads. Patch read/reconstruction granularity is the leak.

## Rejected experiments

1. `OnceLock<Patches>` cache: hot 1.491 ms, but skipped patch reads and decode on repeated takes.
Removed as an invalid apples-to-apples I/O comparison.
2. Search bitpacked patch indices without bulk decode: median 1.640 ms versus 1.621 ms baseline.
The scalar probes were slower; removed.
3. Merge patch-index/value reads: median 1.741 ms. `FileSegmentSource` already coalesces nearby
reads, so this added slicing work; removed, including the temporary buffer API.
4. Latency-based blocking/io_uring routing: about 1.28 ms hot but about 26.7 ms cold because a small
cold metadata read falsely classified the file as hot; removed.

## Profile and next work

A 10-second no-cache Samply profile kept all eight Tokio workers busy. Dominant resolved self
frames were AArch64 atomics and mimalloc allocation/free, pointing to task/array/future construction
overhead rather than worker serialization. The local profile is
`/tmp/no-cache-feature-uniform.profile.json.gz` and is not portable with this branch.

Next, count allocations and short-lived objects inside `resolve_alprd_pages` and final
canonicalization. Avoid two-stage patch I/O unless separately proven hot and cold: it can reduce
patch-value bytes but adds an I/O round. If a future format change is allowed, serialize ALPRD patch
chunk offsets. The 1,024-value patch chunk exactly matches one feature vector, enabling direct
lookup of the roughly four relevant patches.

## Benchmark environment

```text
taskset -c 0-7
TOKIO_WORKER_THREADS=8
RAYON_NUM_THREADS=8
LANCE_IO_THREADS=8
VORTEX_EXPERIMENTAL_PATCHED_ARRAY=1
FLAT_LAYOUT_INLINE_ARRAY_NODE=1
VORTEX_IO_URING=1
VORTEX_IO_URING_RINGS=4
VORTEX_IO_URING_QUEUE_DEPTH=128
VORTEX_IO_URING_MIN_READ_SIZE=0
VORTEX_IO_URING_MAX_IN_FLIGHT=512
```

Validation completed: nightly formatting, `git diff --check`, `cargo check -p vortex-layout`,
release benchmark builds, feature-vectors/uniform smoke runs, and repeated hot comparisons.
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
segment_id,rows,values,patches,patch_bytes,left_bytes,right_bytes
26,256,262144,1031,6186,98304,753664
62,256,262144,1018,6108,98304,753664
81,256,262144,994,5964,98304,753664
98,256,262144,993,5958,98304,753664
99,256,262144,1011,6066,98304,753664
119,256,262144,1007,6042,98304,753664
124,256,262144,1017,6102,98304,753664
220,256,262144,1022,6132,98304,753664
231,256,262144,1018,6108,98304,753664
232,256,262144,1049,6294,98304,753664
337,256,262144,1041,6246,98304,753664
439,256,262144,1067,6402,98304,753664
475,256,262144,985,5910,98304,753664
479,256,262144,1000,6000,98304,753664
482,256,262144,1038,6228,98304,753664
495,256,262144,1034,6204,98304,753664
502,256,262144,1027,6162,98304,753664
503,256,262144,1039,6234,98304,753664
595,256,262144,1022,6132,98304,753664
627,256,262144,1541,9246,98304,753664
686,256,262144,987,5922,98304,753664
704,256,262144,1016,6096,98304,753664
712,256,262144,988,5928,98304,753664
744,256,262144,1051,6306,98304,753664
785,256,262144,980,5880,98304,753664
808,256,262144,1054,6324,98304,753664
815,256,262144,1008,6048,98304,753664
821,256,262144,1014,6084,98304,753664
840,256,262144,1089,6534,98304,753664
849,256,262144,1015,6090,98304,753664
865,256,262144,1030,6180,98304,753664
908,256,262144,995,5970,98304,753664
1063,256,262144,999,5994,98304,753664
1081,256,262144,1775,10650,98304,753664
1107,256,262144,1067,6402,98304,753664
1131,256,262144,1027,6162,98304,753664
1143,256,262144,1573,9438,98304,753664
1148,256,262144,1051,6306,98304,753664
1187,256,262144,1068,6408,98304,753664
1227,256,262144,1041,6246,98304,753664
1329,256,262144,993,5958,98304,753664
1354,256,262144,973,5838,98304,753664
1356,256,262144,1003,6018,98304,753664
1374,256,262144,1045,6270,98304,753664
1406,256,262144,963,5778,98304,753664
1436,256,262144,1056,6336,98304,753664
1460,256,262144,1000,6000,98304,753664
1573,256,262144,953,5718,98304,753664
1599,256,262144,996,5976,98304,753664
1632,256,262144,1008,6048,98304,753664
1667,256,262144,1033,6198,98304,753664
1763,256,262144,1067,6402,98304,753664
1778,256,262144,1014,6084,98304,753664
1786,256,262144,1002,6012,98304,753664
1816,256,262144,1017,6102,98304,753664
1834,256,262144,1048,6288,98304,753664
1869,256,262144,1042,6252,98304,753664
1910,256,262144,1039,6234,98304,753664
1953,256,262144,1011,6066,98304,753664
1993,256,262144,1061,6366,98304,753664
2010,256,262144,1078,6468,98304,753664
2071,256,262144,1011,6066,98304,753664
2091,256,262144,1047,6282,98304,753664
2096,256,262144,1022,6132,98304,753664
2146,256,262144,993,5958,98304,753664
2176,256,262144,1030,6180,98304,753664
2190,256,262144,1058,6348,98304,753664
2236,256,262144,980,5880,98304,753664
2238,256,262144,1064,6384,98304,753664
2270,256,262144,1053,6318,98304,753664
2282,256,262144,997,5982,98304,753664
2389,256,262144,1064,6384,98304,753664
2447,256,262144,1000,6000,98304,753664
2484,256,262144,1046,6276,98304,753664
2501,256,262144,1050,6300,98304,753664
2567,256,262144,999,5994,98304,753664
2569,256,262144,1051,6306,98304,753664
2585,256,262144,1018,6108,98304,753664
2639,256,262144,1507,9042,98304,753664
2659,256,262144,1013,6078,98304,753664
2694,256,262144,1533,9198,98304,753664
2763,256,262144,1078,6468,98304,753664
2842,256,262144,1067,6402,98304,753664
2981,256,262144,1037,6222,98304,753664
3001,256,262144,982,5892,98304,753664
3007,256,262144,1018,6108,98304,753664
3036,256,262144,990,5940,98304,753664
3057,256,262144,973,5838,98304,753664
3067,256,262144,962,5772,98304,753664
3068,256,262144,1016,6096,98304,753664
3219,256,262144,994,5964,98304,753664
3298,256,262144,1034,6204,98304,753664
3321,256,262144,983,5898,98304,753664
3384,256,262144,1029,6174,98304,753664
3410,256,262144,1012,6072,98304,753664
3422,256,262144,1028,6168,98304,753664
3578,256,262144,1032,6192,98304,753664
3622,256,262144,1101,6606,98304,753664
3637,256,262144,1036,6216,98304,753664
3639,256,262144,975,5850,98304,753664
3682,256,262144,1793,10758,98304,753664
3760,256,262144,996,5976,98304,753664
3849,256,262144,1480,8880,98304,753664
3851,256,262144,999,5994,98304,753664
3865,256,262144,1039,6234,98304,753664
12 changes: 8 additions & 4 deletions benchmarks/datafusion-bench/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,14 @@ async fn register_v2_tables<B: Benchmark + ?Sized>(
.runtime_env()
.object_store(table_url.object_store())?;

let fs: FileSystemRef = Arc::new(ObjectStoreFileSystem::new(
Arc::clone(&store),
SESSION.handle(),
));
let fs: FileSystemRef = if benchmark_base.scheme() == "file" {
Arc::new(ObjectStoreFileSystem::local(SESSION.handle()))
} else {
Arc::new(ObjectStoreFileSystem::new(
Arc::clone(&store),
SESSION.handle(),
))
};
let base_prefix = benchmark_base.path().trim_start_matches('/').to_string();
let fs = fs.with_prefix(base_prefix);

Expand Down
27 changes: 27 additions & 0 deletions encodings/alp/src/alp_rd/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,33 @@ pub struct ALPRDMetadata {
patches: Option<PatchesMetadata>,
}

impl ALPRDMetadata {
pub fn right_bit_width(&self) -> VortexResult<u8> {
u8::try_from(self.right_bit_width).map_err(|_| {
vortex_err!(
"right bit width {} does not fit in u8",
self.right_bit_width
)
})
}

pub fn left_parts_dictionary(&self) -> VortexResult<Buffer<u16>> {
self.dict
.get(..usize::try_from(self.dict_len)?)
.ok_or_else(|| vortex_err!("ALPRD dictionary length is out of bounds"))?
.iter()
.map(|&value| {
u16::try_from(value)
.map_err(|_| vortex_err!("ALPRD dictionary value {value} does not fit in u16"))
})
.collect()
}

pub fn patches(&self) -> Option<&PatchesMetadata> {
self.patches.as_ref()
}
}

impl ArrayHash for ALPRDData {
fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) {
self.left_parts_dictionary.array_hash(state, accuracy);
Expand Down
1 change: 1 addition & 0 deletions encodings/fastlanes/src/bitpacking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod vtable;
pub(crate) use plugin::BitPackedPatchedPlugin;
pub use vtable::BitPacked;
pub use vtable::BitPackedArray;
pub use vtable::BitPackedMetadata;

pub(crate) fn initialize(session: &vortex_session::VortexSession) {
vtable::initialize(session);
Expand Down
16 changes: 16 additions & 0 deletions encodings/fastlanes/src/bitpacking/vtable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ pub struct BitPackedMetadata {
pub(crate) patches: Option<PatchesMetadata>,
}

impl BitPackedMetadata {
pub fn bit_width(&self) -> VortexResult<u8> {
u8::try_from(self.bit_width)
.map_err(|_| vortex_err!("bit width {} does not fit in u8", self.bit_width))
}

pub fn offset(&self) -> VortexResult<u16> {
u16::try_from(self.offset)
.map_err(|_| vortex_err!("bit-packed offset {} does not fit in u16", self.offset))
}

pub fn patches(&self) -> Option<&PatchesMetadata> {
self.patches.as_ref()
}
}

impl ArrayHash for BitPackedData {
fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) {
self.offset.hash(state);
Expand Down
1 change: 1 addition & 0 deletions vortex-array/src/arrays/list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub(crate) mod compute;

mod vtable;
pub use vtable::List;
pub use vtable::ListMetadata;

pub(crate) fn initialize(session: &vortex_session::VortexSession) {
compute::initialize(session);
Expand Down
6 changes: 6 additions & 0 deletions vortex-array/src/arrays/list/vtable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ pub struct ListMetadata {
offset_ptype: i32,
}

impl ListMetadata {
pub fn elements_len(&self) -> u64 {
self.elements_len
}
}

impl ArrayHash for ListData {
fn array_hash<H: Hasher>(&self, _state: &mut H, _accuracy: EqMode) {}
}
Expand Down
Loading
Loading