pub struct TieredStorage { /* private fields */ }Expand description
Two-tier storage backend that routes objects by size.
TieredStorage implements Backend and is intended to be used inside a
StorageService, which wraps it with task spawning and panic
isolation.
§Size-Based Routing
Objects are routed at write time based on their size relative to a 1 MiB threshold:
- Objects ≤ 1 MiB go to the
high_volumebackend — optimized for low-latency reads and writes of small objects (e.g. BigTable). - Objects > 1 MiB go to the
long_termbackend — optimized for cost-efficient storage of large objects (e.g. GCS).
§Redirect Tombstones
Because the ObjectId is backend-independent, reads must be able to find an object
without knowing which backend stores it. A naive approach would check the long-term
backend on every read miss in the high-volume backend — but that is slow and expensive.
Instead, when an object is stored in the long-term backend, a redirect tombstone is written in the high-volume backend. It acts as a signpost: “the real data lives in the other backend at this target.” On reads, a single high-volume lookup either returns the object directly or follows the tombstone to long-term storage, without probing both backends.
How tombstones are physically stored is determined by the HighVolumeBackend
implementation — refer to the backend’s own documentation for storage format details.
§Consistency
Consistency across the two backends is maintained through compare-and-swap
operations on the high-volume backend (see
HighVolumeBackend::compare_and_write), not distributed locks. Each
mutating operation reads the current high-volume revision, performs its
work, and then atomically swaps the high-volume entry only if the revision
is still current — rolling back on conflict. Cleanup of unreferenced LT
blobs runs in background tasks so the caller returns as soon as the commit
point is reached. Call Backend::join during shutdown to wait for
outstanding cleanup.
See the module-level documentation for per-operation diagrams.
§Usage
TieredStorage handles only the routing and consistency logic. Wrap it in a
StorageService to add task spawning, panic isolation,
and concurrency limiting.
Implementations§
Trait Implementations§
Source§impl Backend for TieredStorage
impl Backend for TieredStorage
Source§fn put_object<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
id: &'life1 ObjectId,
metadata: &'life2 Metadata,
stream: ClientStream,
) -> Pin<Box<dyn Future<Output = Result<PutResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn put_object<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
id: &'life1 ObjectId,
metadata: &'life2 Metadata,
stream: ClientStream,
) -> Pin<Box<dyn Future<Output = Result<PutResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Source§fn get_object<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 ObjectId,
) -> Pin<Box<dyn Future<Output = Result<GetResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_object<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 ObjectId,
) -> Pin<Box<dyn Future<Output = Result<GetResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_metadata<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 ObjectId,
) -> Pin<Box<dyn Future<Output = Result<MetadataResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_metadata<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 ObjectId,
) -> Pin<Box<dyn Future<Output = Result<MetadataResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn delete_object<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 ObjectId,
) -> Pin<Box<dyn Future<Output = Result<DeleteResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn delete_object<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 ObjectId,
) -> Pin<Box<dyn Future<Output = Result<DeleteResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Auto Trait Implementations§
impl Freeze for TieredStorage
impl !RefUnwindSafe for TieredStorage
impl Send for TieredStorage
impl Sync for TieredStorage
impl Unpin for TieredStorage
impl !UnwindSafe for TieredStorage
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request§impl<L> LayerExt<L> for L
impl<L> LayerExt<L> for L
§fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
Layered].