Trait BlobStore
pub trait BlobStore: Send + Sync {
Show 18 methods
// Required methods
fn read_blob(&self, guid: BlobGuid, dst: &mut AlignedBlobBuf) -> Result<()>;
fn write_blob(&self, guid: BlobGuid, src: &AlignedBlobBuf) -> Result<()>;
fn delete_blob(&self, guid: BlobGuid) -> Result<()>;
fn list_blobs(&self) -> Result<Vec<BlobGuid>>;
fn flush(&self) -> Result<()>;
// Provided methods
fn alloc_blob_buf_zeroed(&self) -> AlignedBlobBuf { ... }
fn read_blobs(
&self,
guids: &[BlobGuid],
dsts: &mut [AlignedBlobBuf],
) -> Vec<Result<()>> ⓘ { ... }
fn read_blob_range(
&self,
guid: BlobGuid,
byte_offset: u64,
dst: &mut [u8],
) -> Result<()> { ... }
fn read_index_range(
&self,
_guid: BlobGuid,
_byte_offset: u64,
_dst: &mut [u8],
) -> Result<bool> { ... }
fn read_value_segment_range(
&self,
_guid: BlobGuid,
_byte_offset: u64,
_dst: &mut [u8],
) -> Result<bool> { ... }
fn publish_read_index(
&self,
_guid: BlobGuid,
_index_bytes: &[u8],
_value_bytes: &[u8],
) -> Result<()> { ... }
fn delete_read_index(&self, _guid: BlobGuid) -> Result<()> { ... }
fn write_blobs(&self, writes: &[(BlobGuid, &AlignedBlobBuf)]) -> Result<()> { ... }
fn write_blobs_with_data_sync(
&self,
writes: &[(BlobGuid, &AlignedBlobBuf)],
) -> Result<()> { ... }
fn needs_flush(&self) -> bool { ... }
fn has_blob(&self, guid: BlobGuid) -> Result<bool> { ... }
fn store_stats(&self) -> StoreStats { ... }
fn vacuum(&self) -> Result<VacuumStats> { ... }
}Expand description
A blob-granular storage interface.
All implementations are Send + Sync so the buffer manager can
drive concurrent I/O from multiple worker threads.
§Contract
read_blob/write_blobalways operate on a fullPAGE_SIZE-byte frame. Partial I/O is not supported.write_blobreplaces the full frame visible to laterread_blobcalls after it returns. The trait does not require power-loss atomicity for a 512 KB frame; Holt’s WAL/checkpoint protocol is the recovery source of truth.flushblocks until every write that returned before the call is durable on the underlying medium.
Required Methods§
fn read_blob(&self, guid: BlobGuid, dst: &mut AlignedBlobBuf) -> Result<()>
fn read_blob(&self, guid: BlobGuid, dst: &mut AlignedBlobBuf) -> Result<()>
Read blob guid into dst. dst.len() == PAGE_SIZE.
fn write_blob(&self, guid: BlobGuid, src: &AlignedBlobBuf) -> Result<()>
fn write_blob(&self, guid: BlobGuid, src: &AlignedBlobBuf) -> Result<()>
Write src as blob guid. src.len() == PAGE_SIZE.
Returns once the write has been submitted to the medium.
Call BlobStore::flush to wait for it to be durable.
fn delete_blob(&self, guid: BlobGuid) -> Result<()>
fn delete_blob(&self, guid: BlobGuid) -> Result<()>
Delete blob guid. No-op if it doesn’t exist.
fn list_blobs(&self) -> Result<Vec<BlobGuid>>
fn list_blobs(&self) -> Result<Vec<BlobGuid>>
Enumerate every blob currently stored.
Provided Methods§
fn alloc_blob_buf_zeroed(&self) -> AlignedBlobBuf
fn alloc_blob_buf_zeroed(&self) -> AlignedBlobBuf
Allocate a zero-filled blob buffer suitable for this store.
The default is a heap-backed 4 KB-aligned frame. Linux
io_uring file stores override this to lease from
their registered fixed-buffer pool when available.
fn read_blobs(
&self,
guids: &[BlobGuid],
dsts: &mut [AlignedBlobBuf],
) -> Vec<Result<()>> ⓘ
fn read_blobs( &self, guids: &[BlobGuid], dsts: &mut [AlignedBlobBuf], ) -> Vec<Result<()>> ⓘ
Read a batch of full-blob frames: guids[i] into dsts[i]
(the slices must be equal length). Returns one Result per
request — best-effort batch semantics, so a failed slot (e.g.
a not-found guid) does not abort the others.
The default loops over Self::read_blob. Stores that can
issue the reads with more device parallelism than a serial
loop override this: Linux io_uring submits one ring batch
(deep read queue from a single SQ owner), and the pread file
store fans the positional reads out across worker threads. The
buffer manager’s cold-scan read-ahead uses this to fetch
upcoming child blobs at the device’s natural queue depth
instead of one serial round-trip each.
fn read_blob_range(
&self,
guid: BlobGuid,
byte_offset: u64,
dst: &mut [u8],
) -> Result<()>
fn read_blob_range( &self, guid: BlobGuid, byte_offset: u64, dst: &mut [u8], ) -> Result<()>
Read dst.len() bytes starting at byte_offset within blob guid.
Enables page-granular fallback reads when a read-index hit
points at a value that was too large to inline. For O_DIRECT
backends the caller keeps byte_offset, dst.len(), and
dst’s base 4 KB-aligned; the read-whole default below
imposes no such requirement.
The default reads the entire frame and copies the sub-range — correct
for any store but with no I/O saving. FileBlobStore /
MemoryBlobStore override it with a genuine ranged read.
fn read_index_range(
&self,
_guid: BlobGuid,
_byte_offset: u64,
_dst: &mut [u8],
) -> Result<bool>
fn read_index_range( &self, _guid: BlobGuid, _byte_offset: u64, _dst: &mut [u8], ) -> Result<bool>
Read a byte range from the optional read index for guid.
Read indexes are accelerators only: None, corrupt bytes, or a
stale stamp must make callers fall back to authoritative blob
reads. Memory/custom stores default to no read-index support.
fn read_value_segment_range(
&self,
_guid: BlobGuid,
_byte_offset: u64,
_dst: &mut [u8],
) -> Result<bool>
fn read_value_segment_range( &self, _guid: BlobGuid, _byte_offset: u64, _dst: &mut [u8], ) -> Result<bool>
Read a byte range from the optional value segment for guid.
Value segments are addressed by read-index entries. They are rebuildable accelerators only; absence or corruption must make callers fall back to the authoritative blob frame.
fn publish_read_index(
&self,
_guid: BlobGuid,
_index_bytes: &[u8],
_value_bytes: &[u8],
) -> Result<()>
fn publish_read_index( &self, _guid: BlobGuid, _index_bytes: &[u8], _value_bytes: &[u8], ) -> Result<()>
Publish rebuilt read-index bytes for guid.
index_bytes is the routable directory/bucket index;
value_bytes is an optional same-blob payload region used by
large value segments. A failure here must not make committed blob
data unrecoverable; callers may ignore the error after
invalidating any cached index.
fn delete_read_index(&self, _guid: BlobGuid) -> Result<()>
fn delete_read_index(&self, _guid: BlobGuid) -> Result<()>
Delete the optional read index for guid.
fn write_blobs(&self, writes: &[(BlobGuid, &AlignedBlobBuf)]) -> Result<()>
fn write_blobs(&self, writes: &[(BlobGuid, &AlignedBlobBuf)]) -> Result<()>
Write a batch of full-blob images.
The default implementation loops over Self::write_blob.
Stores with a cheaper native batch path should override
this. The contract is conservative: if this returns Err,
the caller must assume an arbitrary prefix may have reached
the store and retry the whole batch later.
fn write_blobs_with_data_sync(
&self,
writes: &[(BlobGuid, &AlignedBlobBuf)],
) -> Result<()>
fn write_blobs_with_data_sync( &self, writes: &[(BlobGuid, &AlignedBlobBuf)], ) -> Result<()>
Write a batch and, if the store can do it cheaply, make the data-file bytes durable before returning.
This is deliberately narrower than Self::flush: callers
must still call flush to persist metadata/manifest changes.
The hook exists for Linux io_uring, where checkpoint
write batches can keep data writes and fdatasync on the
same ring turn, then let the later manifest flush skip the
data sync if no newer writes raced in.
fn needs_flush(&self) -> bool
fn needs_flush(&self) -> bool
Conservative hint for callers that want to skip a no-op
flush. Stores should return true whenever a prior
returned write, delete, or metadata update still needs
Self::flush to make it durable.
fn has_blob(&self, guid: BlobGuid) -> Result<bool>
fn has_blob(&self, guid: BlobGuid) -> Result<bool>
true iff guid exists. Default impl scans list_blobs.
fn store_stats(&self) -> StoreStats
fn store_stats(&self) -> StoreStats
Store-level space counters. Implementations that do not expose packed-file or read-index state may return zeros.
fn vacuum(&self) -> Result<VacuumStats>
fn vacuum(&self) -> Result<VacuumStats>
Reclaim physical store space that is already logically free.
The default is a no-op for memory/custom stores. File-backed stores may relocate live high-water slots into lower reusable holes before trimming packed-file tails and, where supported, release physical blocks for reusable middle slots. GUID mappings remain authoritative and stable.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".