Skip to main content

valence_backend_redis/
fleet.rs

1//! Multi-node Redis fleet routing for bench campaigns.
2
3use std::sync::Arc;
4
5use valence_core::backend::DatabaseBackend;
6use valence_core::Result;
7
8use crate::backend::RedisBackend;
9use crate::config::{FleetRedisBackendBuilder, RedisConfig};
10
11/// Routes table operations across standalone Redis nodes by table hash.
12#[derive(Debug)]
13pub struct FleetRedisBackend {
14    backends: Vec<RedisBackend>,
15}
16
17impl FleetRedisBackend {
18    /// Start a builder for explicit fleet wiring.
19    pub fn builder() -> FleetRedisBackendBuilder {
20        FleetRedisBackendBuilder::new()
21    }
22
23    /// Connect using env defaults via builder (shorthand).
24    ///
25    /// # Errors
26    ///
27    /// Returns [`valence_core::Error::Internal`] when env config is incomplete, or
28    /// [`valence_core::Error::Database`] on connect failure.
29    pub async fn from_env() -> Result<Self> {
30        Self::builder().from_env_defaults().build().await
31    }
32
33    /// Connect to every URL with shared key prefix.
34    ///
35    /// # Errors
36    ///
37    /// Returns [`valence_core::Error::Database`] when any node connection fails.
38    pub async fn connect_with_urls(urls: Vec<String>, key_prefix: String) -> Result<Self> {
39        let mut backends = Vec::with_capacity(urls.len());
40        for url in urls {
41            backends.push(
42                RedisBackend::connect_with_config(RedisConfig::new(url, key_prefix.clone()))
43                    .await?,
44            );
45        }
46        Ok(Self { backends })
47    }
48
49    fn backend_for_table(&self, table: &str) -> &RedisBackend {
50        let idx = table_slot_index(table, self.backends.len());
51        &self.backends[idx]
52    }
53}
54
55fn table_slot_index(table: &str, n: usize) -> usize {
56    if n == 0 {
57        return 0;
58    }
59    table
60        .bytes()
61        .fold(0usize, |acc, b| acc.wrapping_add(usize::from(b)))
62        % n
63}
64
65#[async_trait::async_trait]
66impl DatabaseBackend for FleetRedisBackend {
67    fn engine_id(&self) -> &'static str {
68        crate::ENGINE_ID
69    }
70
71    fn capabilities(&self) -> valence_core::BackendCapabilities {
72        self.backends[0].capabilities()
73    }
74
75    async fn execute_compiled_query(
76        &self,
77        compiled: &valence_core::CompiledQuery,
78    ) -> Result<Vec<serde_json::Value>> {
79        self.backends[0].execute_compiled_query(compiled).await
80    }
81
82    async fn ensure_schemaless_table(&self, table: &str) -> Result<()> {
83        for backend in &self.backends {
84            backend.ensure_schemaless_table(table).await?;
85        }
86        Ok(())
87    }
88
89    async fn get_record(&self, table: &str, id: &str) -> Result<Option<serde_json::Value>> {
90        self.backend_for_table(table).get_record(table, id).await
91    }
92
93    async fn create_record(
94        &self,
95        table: &str,
96        content: serde_json::Value,
97    ) -> Result<serde_json::Value> {
98        self.backend_for_table(table)
99            .create_record(table, content)
100            .await
101    }
102
103    async fn update_record(
104        &self,
105        table: &str,
106        id: &str,
107        content: serde_json::Value,
108    ) -> Result<serde_json::Value> {
109        self.backend_for_table(table)
110            .update_record(table, id, content)
111            .await
112    }
113
114    async fn merge_record(
115        &self,
116        table: &str,
117        id: &str,
118        patch: serde_json::Value,
119    ) -> Result<serde_json::Value> {
120        self.backend_for_table(table)
121            .merge_record(table, id, patch)
122            .await
123    }
124
125    async fn upsert_record(
126        &self,
127        table: &str,
128        id: &str,
129        content: serde_json::Value,
130    ) -> Result<serde_json::Value> {
131        self.backend_for_table(table)
132            .upsert_record(table, id, content)
133            .await
134    }
135
136    async fn delete_record(&self, table: &str, id: &str) -> Result<()> {
137        self.backend_for_table(table).delete_record(table, id).await
138    }
139
140    async fn relate_edge(
141        &self,
142        from: &valence_core::RecordId,
143        edge_table: &str,
144        to: &valence_core::RecordId,
145    ) -> Result<()> {
146        self.backend_for_table(from.table())
147            .relate_edge(from, edge_table, to)
148            .await
149    }
150
151    async fn unrelate_edge(
152        &self,
153        from: &valence_core::RecordId,
154        edge_table: &str,
155        to: &valence_core::RecordId,
156    ) -> Result<()> {
157        self.backend_for_table(from.table())
158            .unrelate_edge(from, edge_table, to)
159            .await
160    }
161
162    async fn get_edge_targets(
163        &self,
164        from: &valence_core::RecordId,
165        edge_table: &str,
166    ) -> Result<Vec<valence_core::RecordId>> {
167        self.backend_for_table(from.table())
168            .get_edge_targets(from, edge_table)
169            .await
170    }
171
172    async fn define_unique_index(&self, table: &str, field: &str) -> Result<()> {
173        for backend in &self.backends {
174            backend.define_unique_index(table, field).await?;
175        }
176        Ok(())
177    }
178
179    fn ttl_capability(&self) -> valence_core::ttl::BackendTtlCapability {
180        crate::ttl::ttl_capability()
181    }
182
183    async fn apply_ttl_policy(
184        &self,
185        table: &str,
186        policy: &valence_core::ttl::SchemaTtlPolicy,
187    ) -> Result<()> {
188        for backend in &self.backends {
189            backend.apply_ttl_policy(table, policy).await?;
190        }
191        Ok(())
192    }
193}
194
195/// Install a fleet backend as `Arc<dyn DatabaseBackend>`.
196///
197/// # Errors
198///
199/// Propagates builder/`build` errors (missing config or connection failure).
200pub async fn connect_fleet_arc(
201    builder: FleetRedisBackendBuilder,
202) -> Result<Arc<dyn DatabaseBackend>> {
203    Ok(Arc::new(builder.build().await?))
204}