Skip to main content

objectstore_service/backend/
mod.rs

1//! Storage backend implementations.
2//!
3//! This module contains the [`Backend`](common::Backend) trait and its
4//! implementations. Each backend adapts a specific storage system (BigTable,
5//! GCS, local filesystem, S3-compatible) to a uniform interface that
6//! [`StorageService`](crate::StorageService) consumes.
7//!
8//! Two-tier routing is encapsulated in [`TieredStorage`](tiered::TieredStorage)
9//! and can be configured via [`StorageConfig::Tiered`].
10
11use anyhow::Result;
12use serde::{Deserialize, Serialize};
13
14use crate::change_stream::ChangeStreamFactory;
15
16pub mod bigtable;
17pub mod changelog;
18pub mod common;
19pub mod counting;
20mod extensions;
21pub mod gcs;
22pub mod in_memory;
23pub mod local_fs;
24pub mod s3_compatible;
25pub mod tiered;
26
27#[cfg(test)]
28pub(crate) mod testing;
29
30/// Storage backend configuration.
31///
32/// The `type` field in YAML or `__TYPE` in environment variables determines which variant is used.
33///
34/// Used to configure storage backends via [`from_config`].
35#[derive(Debug, Clone, Deserialize, Serialize)]
36#[serde(tag = "type", rename_all = "lowercase")]
37pub enum StorageConfig {
38    /// Local filesystem storage backend (type `"filesystem"`).
39    FileSystem(local_fs::FileSystemConfig),
40
41    /// S3-compatible storage backend (type `"s3compatible"`).
42    S3Compatible(s3_compatible::S3CompatibleConfig),
43
44    /// [Google Cloud Storage] backend (type `"gcs"`).
45    ///
46    /// [Google Cloud Storage]: https://cloud.google.com/storage
47    Gcs(gcs::GcsConfig),
48
49    /// [Google Bigtable] backend (type `"bigtable"`).
50    ///
51    /// [Google Bigtable]: https://cloud.google.com/bigtable
52    BigTable(bigtable::BigTableConfig),
53
54    /// Tiered storage backend (type `"tiered"`).
55    ///
56    /// Routes objects across two backends based on size: small objects go to
57    /// `high_volume`, large objects go to `long_term`. Nesting `Tiered` inside
58    /// another `Tiered` is not supported and will return an error at startup.
59    Tiered(tiered::TieredStorageConfig),
60}
61
62/// Constructs a type-erased [`Backend`](common::Backend) from the given [`StorageConfig`].
63///
64/// Backends that configured a [`ChangeStream`](crate::change_stream::ChangeStream) get one
65/// from `streams`; the rest report nothing.
66pub async fn from_config(
67    config: StorageConfig,
68    streams: &ChangeStreamFactory,
69) -> Result<Box<dyn common::Backend>> {
70    Ok(match config {
71        StorageConfig::Tiered(c) => {
72            let hv = hv_from_config(c.high_volume, streams).await?;
73            let lt = lt_from_config(c.long_term, streams).await?;
74            let log = Box::new(changelog::NoopChangeLog);
75            Box::new(tiered::TieredStorage::new(hv, lt, log))
76        }
77        // All non-Tiered variants are handled by from_leaf_config. A wildcard
78        // is intentional here: any new leaf variant should fall through to
79        // from_leaf_config, which will handle it or produce a compile error.
80        _ => from_leaf_config(config, streams).await?,
81    })
82}
83
84async fn from_leaf_config(
85    config: StorageConfig,
86    streams: &ChangeStreamFactory,
87) -> Result<Box<dyn common::Backend>> {
88    Ok(match config {
89        StorageConfig::FileSystem(c) => Box::new(local_fs::LocalFsBackend::new(c, streams)),
90        StorageConfig::S3Compatible(c) => Box::new(
91            s3_compatible::S3CompatibleBackend::without_token(c, streams),
92        ),
93        StorageConfig::Gcs(c) => Box::new(gcs::GcsBackend::new(c, streams).await?),
94        StorageConfig::BigTable(c) => Box::new(bigtable::BigTableBackend::new(c, streams).await?),
95        StorageConfig::Tiered(_) => anyhow::bail!("nested tiered storage is not supported"),
96    })
97}
98
99/// Configuration for the high-volume backend in a [`tiered::TieredStorageConfig`].
100///
101/// Only backends that implement [`common::HighVolumeBackend`] are valid here.
102/// Currently this is limited to BigTable.
103#[derive(Debug, Clone, Deserialize, Serialize)]
104#[serde(tag = "type", rename_all = "lowercase")]
105pub enum HighVolumeStorageConfig {
106    /// [Google Bigtable] backend.
107    ///
108    /// [Google Bigtable]: https://cloud.google.com/bigtable
109    BigTable(bigtable::BigTableConfig),
110}
111
112/// Constructs a type-erased [`common::HighVolumeBackend`] from the given config.
113async fn hv_from_config(
114    config: HighVolumeStorageConfig,
115    streams: &ChangeStreamFactory,
116) -> Result<Box<dyn common::HighVolumeBackend>> {
117    Ok(match config {
118        HighVolumeStorageConfig::BigTable(c) => {
119            Box::new(bigtable::BigTableBackend::new(c, streams).await?)
120        }
121    })
122}
123
124/// Configuration for the long-term backend in a [`tiered::TieredStorageConfig`].
125///
126/// Only backends that implement [`common::MultipartUploadBackend`] are valid here.
127#[derive(Debug, Clone, Deserialize, Serialize)]
128#[serde(tag = "type", rename_all = "lowercase")]
129pub enum MultipartUploadStorageConfig {
130    /// Local filesystem storage backend (type `"filesystem"`).
131    FileSystem(local_fs::FileSystemConfig),
132
133    /// [Google Cloud Storage] backend (type `"gcs"`).
134    ///
135    /// [Google Cloud Storage]: https://cloud.google.com/storage
136    Gcs(gcs::GcsConfig),
137}
138
139/// Constructs a type-erased [`common::MultipartUploadBackend`] from the given config.
140async fn lt_from_config(
141    config: MultipartUploadStorageConfig,
142    streams: &ChangeStreamFactory,
143) -> Result<Box<dyn common::MultipartUploadBackend>> {
144    Ok(match config {
145        MultipartUploadStorageConfig::FileSystem(c) => {
146            Box::new(local_fs::LocalFsBackend::new(c, streams))
147        }
148        MultipartUploadStorageConfig::Gcs(c) => Box::new(gcs::GcsBackend::new(c, streams).await?),
149    })
150}