refactor(io): introduce composable high-performance storage architecture - #2793
refactor(io): introduce composable high-performance storage architecture#2793LHT129 wants to merge 1 commit into
Conversation
|
/label status/waiting-for-review |
Merge Protections🟢 All 2 merge protections satisfied — ready to merge. Show 2 satisfied protections🟢 Require kind label
🟢 Require version label
|
There was a problem hiding this comment.
Pull request overview
Refactors VSAG’s IO subsystem from the duplicated BasicIO hierarchy into a statically composed ByteIO<Backend, CachePolicy> architecture, migrating existing IO types while preserving public names/config/serialization behavior and adding targeted tests + IO microbench harnesses.
Changes:
- Introduces composable IO core (backends, policies, leases, request/operation types) and migrates Memory/Block/MMap/Buffer/Async/Uring/Reader IO to V2 profiles behind stable aliases.
- Updates layouts/datacells/algorithms to use lease-based reads and centralized IO kind/type dispatch (
IOKind,VisitIOKind). - Adds IO benchmark targets (gated by
ENABLE_IO_BENCHMARKS) and uploads IO perf CSV artifacts in the performance workflow.
Reviewed changes
Copilot reviewed 116 out of 136 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| TASK.md | POC plan/status notes for IO refactor |
| src/reader.cpp | Adjust Reader multi-read error message |
| src/layout/variable_record_layout.h | Switch to concrete IO + add Acquire leases |
| src/layout/fixed_layout.h | Switch to concrete IO + add Acquire/AcquireRange |
| src/layout/fixed_layout_test.cpp | Update test IO + validate Acquire APIs |
| src/layout/byte_range_layout.h | Switch to concrete IO + add Acquire, Size() |
| src/io/uring_io/uring_io.h | Alias UringIO to UringIOV2 |
| src/io/uring_io/uring_io_v2.h | New UringIO V2 profile composition |
| src/io/uring_io/uring_io_test.cpp | Use IO contract tests for uring |
| src/io/uring_io/uring_io_read_object.h | Remove legacy uring read helper |
| src/io/uring_io/uring_io_read_object.cpp | Remove legacy uring read helper impl |
| src/io/uring_io/uring_io_context_guard.cpp | Remove legacy uring context guard |
| src/io/reader_io/reader_io.h | Alias ReaderIO to ReaderIOV2 |
| src/io/reader_io/reader_io.cpp | Remove legacy ReaderIO impl |
| src/io/reader_io/reader_io_v2.h | New ReaderIO V2 declaration |
| src/io/reader_io/reader_io_v2.cpp | New ReaderIO V2 implementation |
| src/io/reader_io/reader_io_test.cpp | Update ReaderIO tests for V2 contract |
| src/io/read_cache/page_cache.h | Add RemoveRange API |
| src/io/read_cache/page_cache.cpp | Implement RemoveRange and stale marking |
| src/io/policy/uring_batch_read.h | New io_uring batch-read policy wrapper |
| src/io/policy/sequential_batch_read.h | New sequential batch-read policy |
| src/io/policy/libaio_batch_read.h | New libaio batch-read policy wrapper |
| src/io/policy/durability_policy.h | Add durability policies (NoFlush/Fsync) |
| src/io/policy/configurable_single_read.h | Add configurable single-read + Acquire |
| src/io/policy/buffered_single_read.h | Add buffered single-read + Acquire/LegacyRead |
| src/io/noncontinuous_io/noncontinuous_io_test.cpp | Update contract tests + add spill coverage |
| src/io/mmap_io/mmap_io.h | Alias MMapIO to MMapIOV2 |
| src/io/mmap_io/mmap_io_v2.h | New MMapIO V2 profile composition |
| src/io/mmap_io/mmap_io_v2_test.cpp | New MMapIOV2 contract/compat tests |
| src/io/mmap_io/mmap_io_test.cpp | Use IO contract tests for mmap |
| src/io/memory_io/memory_io.h | Alias MemoryIO to MemoryIOV2 |
| src/io/memory_io/memory_io.cpp | Remove legacy MemoryIO impl |
| src/io/memory_io/memory_io_v2.h | New MemoryIO V2 profile composition |
| src/io/memory_io/memory_io_test.cpp | Use IO contract tests for memory |
| src/io/memory_block_io/memory_block_io.h | Alias MemoryBlockIO to MemoryBlockIOV2 |
| src/io/memory_block_io/memory_block_io.cpp | Remove legacy MemoryBlockIO impl |
| src/io/memory_block_io/memory_block_io_v2.h | New MemoryBlockIO V2 profile composition |
| src/io/memory_block_io/memory_block_io_test.cpp | Use IO contract tests for block memory |
| src/io/io_headers.h | Replace legacy include with type dispatch |
| src/io/core/uring_read_operation.h | New uring operation type |
| src/io/core/read_request.h | New ReadRequest type |
| src/io/core/read_operation.h | Replace legacy uring guard with ImmediateOperation |
| src/io/core/read_lease.h | New lease/owner types for Acquire |
| src/io/core/io_utils.h | New IO range helpers (CheckedEnd/IsValidRange) |
| src/io/core/io_environment.h | New IOEnvironment + default pools |
| src/io/core/cached_read_operation.h | Cache-wrapped operation variant |
| src/io/core/cached_read_lease.h | Cache-wrapped lease variant |
| src/io/container/io_array_test.cpp | Use IO contract tests |
| src/io/common/io_type_dispatch.h | New VisitIOKind dispatch helper |
| src/io/common/io_type_dispatch_test.cpp | Tests for VisitIOKind mapping |
| src/io/common/io_parameter.h | Add IOKind + Kind/KindFromName |
| src/io/common/io_parameter.cpp | Switch parsing to KindFromName + add Kind() |
| src/io/common/io_parameter_test.cpp | Tests for KindFromName mapping |
| src/io/common/io_contract_test.h | Update contract tests to V2 IO types |
| src/io/CMakeLists.txt | Update IO sources (remove legacy, add new) |
| src/io/cache/no_cache.h | New NoCache policy |
| src/io/buffer_io/buffer_io.h | Alias BufferIO to BufferIOV2 |
| src/io/buffer_io/buffer_io.cpp | Remove legacy BufferIO impl |
| src/io/buffer_io/buffer_io_v2.h | New BufferIO V2 profile composition |
| src/io/buffer_io/buffer_io_test.cpp | Update BufferIO tests for V2 API |
| src/io/backend/posix_file.h | New PosixFile RAII wrapper |
| src/io/backend/posix_file.cpp | PosixFile open/truncate/ownership logic |
| src/io/backend/posix_file_backend.h | New POSIX file backend template |
| src/io/backend/mmap_region.h | New mmap region backend |
| src/io/backend/mmap_region.cpp | mmap region implementation |
| src/io/backend/heap_region.h | New heap region backend |
| src/io/backend/contiguous_backend.h | New contiguous backend wrapper |
| src/io/async_io/direct_io_object.h | Remove legacy direct IO helper |
| src/io/async_io/async_io.h | Alias AsyncIO to AsyncIOV2 |
| src/io/async_io/async_io_v2.h | New AsyncIO V2 profile composition |
| src/io/async_io/async_io_test.cpp | Update AsyncIO tests for V2 API |
| src/impl/filter/extrainfo_wrapper_filter.cpp | Use ExtraInfo lease-based access |
| src/datacell/sparse_vector_datacell.inl | Use layout leases + Kind-based IO strategy |
| src/datacell/sparse_vector_datacell.h | Update IO ownership/type + add ReadLease alias |
| src/datacell/rabitq_split_datacell_factory_impl.h | Kind-based IO combination checks + dispatch |
| src/datacell/multi_vector_datacell.h | Remove BasicIO dependency |
| src/datacell/graph_interface.cpp | Kind-based IO dispatch for GraphDataCell |
| src/datacell/graph_datacell.h | Store concrete IO shared_ptr |
| src/datacell/flatten_interface.h | Add RAII lease wrapper for codes |
| src/datacell/flatten_interface.cpp | Kind-based IO dispatch for flatten cells |
| src/datacell/flatten_datacell.h | Use layout Acquire APIs |
| src/datacell/extra_info_interface.h | Add ExtraInfoLease + Acquire API |
| src/datacell/extra_info_interface.cpp | Use Kind() in ExtraInfo instance creation |
| src/datacell/extra_info_interface_test.cpp | Update tests to use Acquire leases |
| src/datacell/extra_info_datacell.h | Store concrete IO shared_ptr in SetIO |
| src/datacell/disk_sindi_term_datacell.h | Store concrete IO shared_ptr |
| src/datacell/disk_sindi_term_datacell.cpp | Kind-based IO dispatch + mmap lease access |
| src/datacell/bucket_interface.cpp | Kind-based IO dispatch for buckets |
| src/datacell/bucket_datacell.h | Replace read/release with Acquire leases |
| src/datacell/bucket_datacell_test.cpp | Update tracking IO to V2-style MemoryIO base |
| src/analyzer/pyramid_analyzer.cpp | Use AcquireCodesById lease helper |
| src/algorithm/pyramid/pyramid.cpp | Use AcquireCodesById lease helper |
| src/algorithm/hgraph/hgraph.cpp | Use AcquireCodesById lease helper |
| src/algorithm/hgraph/hgraph_mci.cpp | Use AcquireCodesById lease helper |
| src/algorithm/bruteforce/bruteforce.cpp | Use AcquireCodesById lease helper |
| CMakeLists.txt | Add optional IO benchmark subdir |
| cmake/VSAGOptions.cmake | Add ENABLE_IO_BENCHMARKS option |
| benchs/io/io_syscall_count_probe.cpp | New syscall-count probe benchmark |
| benchs/io/io_codegen_probe.cpp | New codegen/ABI probe for IO hot paths |
| benchs/io/CMakeLists.txt | Add IO benchmark/probe targets |
| .github/workflows/performance.yml | Build/run IO benchmarks and upload CSV artifacts |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
e43961c to
7f31f45
Compare
7f31f45 to
9cc29ab
Compare
Separate storage backends, access and cache policies, ownership leases, and completion operations while preserving public IO profiles and serialization compatibility. Keep hot paths statically composed, retain native batch submission, and add differential, sanitizer, concurrency, and compatibility validation. Co-authored-by: opencode <opencode@anthropic.com> Signed-off-by: LHT129 <tianlan.lht@antgroup.com>
LHT129
left a comment
There was a problem hiding this comment.
Code review for the composable IO architecture refactor.
LHT129
left a comment
There was a problem hiding this comment.
Code review for the composable IO architecture refactor.
Change Type
Linked Issue
Why
BasicIO<Derived>currently owns dispatch, logical size, serialization state, range validation, cache orchestration, result ownership, compatibility APIs, and backend fallbacks. Concrete IO classes then repeat file or memory lifecycle logic while mixing the storage medium with single-read, batch-read, durability, direct-I/O, and cache strategies.This works for the existing set of local profiles, but makes the next steps—deeper io_uring support, truly asynchronous reads, batch-aware shared caches, remote Readers, and additional storage backends—hard to add without duplicating complete IO classes or putting runtime abstraction into hot paths.
The goal of this refactor is to separate those responsibilities while preserving the current performance model: static composition, borrowed zero-copy reads, native backend batching, and no virtual dispatch or generic heap-allocated completion on hot paths.
Architecture after the refactor
The architecture has six explicit boundaries:
ByteIO<Backend, CachePolicy>owns logical size, overflow-safe range checks, serialization flow, cache invalidation, and compatibility adapters. Its template composition keeps disabled features removable at compile time.ReadLeaserepresents borrowed, allocator-owned, aligned, cached, and configurable ownership as move-only RAII values. Internal DataCell and algorithm hot paths no longer manually pairneed_releasewithRelease().ReadOperationprovides an immediate completion for synchronous paths and a stable operation boundary for asynchronous engines without forcing synchronous reads to allocate.CachePolicyplans unique page misses, preserves backendReadManybatching, coordinates single-flight loads, isolates shared-cache namespaces, and compiles to a direct backend path when disabled.IOKindandVisitIOKindcentralize the cold-path mapping from configured IO type to the finite set of supported C++ profiles. Users keep the existing IO type strings; arbitrary policy combinations are intentionally not exposed.What Changed
BasicIOimplementation hierarchy with statically composedByteIOprofiles.NoCacheand batch-awareOptionalPageCache, including duplicate-miss coalescing, shared single-flight coordination, failure recovery, and namespace-safe invalidation.BasicIOimplementation and migration-only duplicate paths after differential and performance gates passed.Compatibility Impact
Performance and Concurrency Impact
All authoritative runtime comparisons were run on Linux
lht.devwith an Intel Xeon Platinum 8269CY, GCC 11.4, fixed CPU affinity, byte-identical benchmark sources, alternating order, matched inputs, and checksum validation. Timing claims use multi-sample medians; shared-host direct-I/O tail noise is reported rather than hidden.End-to-end
Checksums match for every workload. No stable end-to-end regression was observed.
Representative hot paths
Syscalls, submissions, allocation, and size
preadcalls before and after.preadcalls before and after.The cache's single-flight state and shared namespace coordination are protected explicitly. Reads may run concurrently when the backend supports them; resize/remap/write still require the documented external synchronization. TSan concurrency-focused tests report no race or deadlock.
The benchmark and syscall/submission probe sources used for these acceptance measurements are preserved on the contributor branch
refactor/io-composable-architecture-with-bench-probes; they are intentionally excluded from this PR's merge diff.Test Evidence
make fmtKey results:
Long-running
[daily]and[tune]suites were intentionally excluded; the acceptance matrix uses targeted correctness, sanitizer, backend-configuration, and representative performance tests instead.Documentation Impact
Risk and Rollback
Checklist