How zvec Handles Concurrent Writes and Read/Write Isolation: A Deep Dive into the Storage Engine
zvec isolates concurrent reads and writes using two std::shared_mutex instances—schema_handle_mtx_ for shared schema access and write_mtx_ for exclusive write serialization—ensuring thread-safe operations while allowing concurrent queries.
The alibaba/zvec storage engine implements a sophisticated concurrency control mechanism to handle high-throughput vector database workloads. Understanding how zvec handles concurrent writes and read/write isolation is essential for developers building multi-threaded applications that require consistent data views without sacrificing query performance.
The Dual-Mutex Architecture Behind zvec's Concurrency Control
At the core of zvec's thread safety are two distinct mutexes declared in src/include/zvec/db/collection.h. This separation of concerns allows fine-grained control over schema stability versus data mutation.
schema_handle_mtx_: Guarding Schema Stability and Segment Metadata
The schema_handle_mtx_ is a std::shared_mutex that protects the collection schema and the global view of segment metadata managed by the version manager. All public API calls—both reads and writes—acquire a shared lock (std::shared_lock) on this mutex before proceeding. This ensures that the schema cannot be altered while any operation is in progress, providing schema stability across the entire system.
write_mtx_: Serializing Mutating Operations
The second mutex, write_mtx_, also a std::shared_mutex, is dedicated to serializing batches that modify the active writing segment. This includes Insert, Update, Upsert, Delete, and Delete-by-filter operations. Unlike the schema mutex, write operations acquire an exclusive lock (std::lock_guard<std::shared_mutex>) on write_mtx_ inside CollectionImpl::write_impl and the delete-by-filter execution path. This guarantees that only one write batch can manipulate the writing segment at any given time.
How zvec Handles Concurrent Writes: The Write Path Explained
The write path in src/db/collection.cc demonstrates a strict locking hierarchy that prevents write-write conflicts and ensures atomic segment transitions.
Schema Validation Under Shared Lock
When a write operation begins, it first acquires a shared lock on schema_handle_mtx_. This step validates that the document structure conforms to the current schema and ensures that no concurrent schema alterations can occur during the validation phase.
Exclusive Write Locking and Segment Management
Following schema validation, the writer acquires an exclusive lock on write_mtx_:
std::lock_guard<std::shared_mutex> write_lock(write_mtx_);
This exclusive lock ensures that only one write batch runs at a time, eliminating write-write conflicts. While holding this lock, if the current writing segment reaches its maximum document count, switch_to_new_segment_for_writing() is invoked to create a new segment.
Atomic Segment Switching and Version Updates
The segment switching process occurs while the exclusive write lock remains held. The new segment is created, the version manager is updated atomically, and the old segment is handed over to the SegmentManager. The VersionManager (defined in src/db/index/common/version_manager.h) applies the new version metadata while the exclusive lock is still active, guaranteeing that readers see either the old version or the completely new version, never an intermediate state.
The source code contains a comment acknowledging potential future optimization: // TODO: The granularity of the write_lock is too coarse.
Read/Write Isolation in zvec: The Read Path
All read-only APIs—including Query, GroupByQuery, Fetch, and Stats—implement optimistic concurrency control using only the shared schema mutex.
Result<DocPtrList> CollectionImpl::Query(const VectorQuery &query) const {
std::shared_lock lock(schema_handle_mtx_); // shared read lock
// ...
auto segments = get_all_segments(); // includes persisted + writing segment
return sql_engine_->execute(schema_, query, segments);
}
By acquiring only a shared lock on schema_handle_mtx_, read operations allow unlimited concurrent readers. Since readers never attempt to lock write_mtx_, they remain unblocked by ongoing write batches. This design achieves true read/write isolation: writers serialize through the exclusive write mutex while readers proceed concurrently through the shared schema mutex.
Consistency Guarantees and Isolation Levels
zvec provides specific consistency guarantees through its locking hierarchy and version management:
- Schema Stability: Every operation holds a shared lock on
schema_handle_mtx_, preventing schema modifications during active reads or writes. - Segment-Level Isolation: Writes are applied exclusively to the single in-memory writing segment (managed through
src/db/index/segment/segment.h), while reads operate on a snapshot of persisted segments retrieved from theVersionManager. - Read-Committed Semantics: Readers may see the current writing segment only after the writer has flushed or switched segments. This ensures readers never observe partially-written documents or intermediate states during segment transitions.
- Atomic Version Updates: When a segment closes, the
VersionManagerapplies the new version metadata while the exclusivewrite_mtx_lock is held, ensuring readers see consistent snapshots.
Summary
- zvec employs a dual-mutex architecture using
schema_handle_mtx_andwrite_mtx_to handle concurrent writes and read/write isolation. - Shared locks on
schema_handle_mtx_allow unlimited concurrent reads while protecting schema stability. - Exclusive locks on
write_mtx_serialize write batches to the active writing segment, preventing write-write conflicts. - Atomic segment switching via the
VersionManagerensures readers see consistent snapshots without intermediate states. - The design provides read-committed isolation with thread-safe concurrent query execution.
Frequently Asked Questions
What mutex does zvec use to protect the collection schema?
zvec uses schema_handle_mtx_, a std::shared_mutex declared in src/include/zvec/db/collection.h, to protect the collection schema and segment metadata. All public API operations acquire a shared lock on this mutex to ensure schema stability during execution.
How does zvec prevent write-write conflicts?
zvec prevents write-write conflicts through write_mtx_, a std::shared_mutex that is exclusively locked during mutating operations. The CollectionImpl::write_impl method and delete-by-filter paths acquire an exclusive lock using std::lock_guard<std::shared_mutex>, ensuring only one write batch modifies the active writing segment at a time.
Does zvec support concurrent reads during write operations?
Yes, zvec supports concurrent reads during write operations through its dual-mutex architecture. Read-only APIs such as Query and Fetch acquire only a shared lock on schema_handle_mtx_, which does not conflict with the exclusive write_mtx_ held by writers. This allows unlimited concurrent readers to execute alongside active write batches without blocking.
What isolation level does zvec provide for read operations?
zvec provides read-committed isolation semantics for read operations. Readers retrieve a snapshot of persisted segments from the VersionManager and may see the current writing segment only after the writer has flushed or switched segments. This ensures readers never observe partially-written documents or intermediate states during segment transitions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →