[python] Batch vector writes when building global indexes - #9752
[python] Batch vector writes when building global indexes#9752TheR1sing3un wants to merge 1 commit into
Conversation
JingsongLi
left a comment
There was a problem hiding this comment.
Reviewed be9cf53. Requirement fit: supported; no actionable implementation finding.
The Arrow-aware writer removes per-element Python conversion and per-row buffered writes from actual vector-index ingestion. Range filtering happens before vector validation, null vectors retain their row-count semantics, and scalar fallback preserves invalid-vector errors and the successfully written prefix.
The 36 focused build/batch tests passed. Independent byte/counter parity checks covered all three list layouts, slices/child offsets, nulls, nontrivial chunk boundaries and large row IDs. A native baseline-vs-batch build smoke test on 4,096 × 64 vectors also produced identical temporary input hashes and query IDs/distances. Whole-shard Arrow materialization and training allocation remain separate costs; this PR's conversion/write improvement is independently useful.
Purpose
Vindex builds currently convert Arrow vector columns to nested Python lists, validate every float in Python, then serialize and write each row separately. This adds substantial CPU overhead even when the vectors are already stored as contiguous float32 values.
Add an Arrow-aware
write_batch()fast path for float32 List, LargeList and FixedSizeList arrays with int64 row IDs. Validate dimensions and finite values in batches and write row IDs and vector buffers through memoryviews. Handle sliced arrays and null parent vectors correctly. Invalid vectors and unsupported layouts fall back to the existing scalar path, preserving error messages and successfully written prefixes.The builder feeds bounded batches (at most 10,000 rows) into the new path, applies shard filtering and relative IDs, and rejects null row IDs before writing any batch. The scalar writer interface remains available.
Whole-shard Arrow reading and the original training sampler remain in place; full-text and sorted-index writing are unchanged.
Validation
python -m pytest pypaimon/tests/vindex_batch_write_test.py pypaimon/tests/global_index_build_test.py -q: 36 passed.flake8 --config dev/cfg.iniandgit diff --check.Benchmark and ablation
Reproduction script:
paimon-python/dev/benchmark_vindex_batch_write.py.Environment: macOS 26.4.1 arm64, Python 3.9.6, NumPy 2.0.2, PyArrow 19.0.1, paimon-vindex 0.4.0; CPU, OMP_NUM_THREADS=1, OPENBLAS_NUM_THREADS=1. Three fresh sequential processes per configuration, shuffled order, 36 runs total. Dataset preparation is separate. Filesystem cache is uncontrolled; Arrow reported sandbox restrictions on some sysctl CPU probes in all modes.
Primary dataset: one index shard in a real Paimon data-evolution Parquet table, row tracking enabled, 65,536 valid non-null vectors of 256 float32 elements (64 MiB raw vectors), batch size 1,024.
Ingestion-only measurements include reading, conversion/validation and temporary-file writing, but skip native training/serialization. Hash verification time is excluded. Values are medians of three runs; RSS is absolute process peak, including roughly 150 MiB of import/runtime overhead.
Columnar preparation accounts for most of the throughput gain; batching file writes adds another reduction. The ablation splits buffer writes into per-row buffered Python file calls, not OS syscalls. It compares controlled pipeline variants rather than inserting timers into per-element loops.
The complete native IVF-Flat build (nlist=16, 25% training sample, L2) takes 3.419 → 0.228 s, with peak RSS 942.23 → 507.19 MiB. This includes training and index-file serialization, excludes hash verification, snapshot commit and subsequent query validation, and is about 15.0x faster in this local experiment.
A second dataset of 16,384 × 768 float32 vectors (48 MiB) gives ingestion times of 2.347 / 2.329 / 0.123 / 0.081 s for the four modes above. Batch ingestion on the primary dataset takes 0.146 / 0.123 / 0.119 s at batch sizes 256 / 1,024 / 4,096.
These results do not establish equivalent speedups for cold disk, remote storage or GPU workloads. Whole-shard Arrow buffers and native training/index allocations still contribute to memory use.
Example reproduction from
paimon-python:Run each of
baseline,scalar-batches,convert-onlyandbatchthree times in fresh processes. Add--nativeto include native building and query validation. Use a separate warehouse with--rows 16384 --dimension 768for the high-dimensional dataset.