Skip to main content

reddb_server/runtime/
impl_serverless.rs

1use super::*;
2
3impl RedDBRuntime {
4    pub fn serverless_file_plan(&self) -> Option<reddb_file::ServerlessFilePlan> {
5        let data_path = self.inner.db.options().data_path.as_ref()?;
6        let generation = self
7            .primary_logical_head_lsn()
8            .max(self.cdc_current_lsn())
9            .max(1);
10        Some(reddb_file::ServerlessFilePlan::for_data_path(
11            data_path, generation,
12        ))
13    }
14
15    fn serverless_file_plan_for_generation(
16        &self,
17        generation: u64,
18    ) -> Option<reddb_file::ServerlessFilePlan> {
19        let plan = self.serverless_file_plan()?;
20        Some(plan.for_generation(generation))
21    }
22
23    fn serverless_local_cache_for_generation(
24        &self,
25        generation: u64,
26    ) -> Option<reddb_file::ServerlessLocalCache> {
27        let plan = self.serverless_file_plan_for_generation(generation)?;
28        Some(plan.local_cache())
29    }
30
31    fn serverless_collection_snapshot_bytes(&self, collection: &str) -> RedDBResult<Vec<u8>> {
32        let store = self.inner.db.store();
33        let _publication = store.vault_publication_lock.lock();
34        let pending_vault_versions = store.pending_vault_versions.read();
35        let source = self
36            .inner
37            .db
38            .store()
39            .get_collection(collection)
40            .ok_or_else(|| {
41                RedDBError::Internal(format!("serverless collection not found: {collection}"))
42            })?;
43        let snapshot = crate::storage::unified::UnifiedStore::with_config(
44            crate::storage::unified::UnifiedStoreConfig::default(),
45        );
46        let mut error: Option<RedDBError> = None;
47        source.for_each_entity(|entity| {
48            if pending_vault_versions.contains(&entity.id) {
49                return true;
50            }
51            let cloned = entity.clone();
52            match snapshot.insert_auto(collection, cloned) {
53                Ok(id) => {
54                    if let Some(metadata) = source.get_metadata(entity.id) {
55                        if let Err(err) = snapshot.set_metadata(collection, id, metadata) {
56                            error = Some(RedDBError::Internal(err.to_string()));
57                            return false;
58                        }
59                    }
60                    true
61                }
62                Err(err) => {
63                    error = Some(RedDBError::Internal(err.to_string()));
64                    false
65                }
66            }
67        });
68        if let Some(error) = error {
69            return Err(error);
70        }
71        Ok(snapshot.to_binary_dump_bytes())
72    }
73
74    pub fn publish_serverless_generation(
75        &self,
76    ) -> RedDBResult<Option<reddb_file::ServerlessGenerationPointer>> {
77        let Some(base_plan) = self.serverless_file_plan() else {
78            return Ok(None);
79        };
80        let store = self.inner.db.store();
81        let _publication = store.vault_publication_lock.lock();
82        self.flush()?;
83        let next_generation = match base_plan.read_current_pointer_verified() {
84            Ok(pointer) => base_plan
85                .generation
86                .max(pointer.generation.saturating_add(1)),
87            Err(reddb_file::RdbFileError::Io(err))
88                if err.kind() == std::io::ErrorKind::NotFound =>
89            {
90                base_plan.generation
91            }
92            Err(err) => {
93                return Err(RedDBError::InvalidOperation(format!(
94                    "corrupt serverless generation: {err}"
95                )));
96            }
97        };
98        let plan = reddb_file::ServerlessFilePlan::new(
99            base_plan.root,
100            base_plan.namespace,
101            next_generation,
102        )
103        .with_cache_policy(base_plan.cache_policy);
104        let mut extent_index = reddb_file::ServerlessExtentIndex::new(plan.generation);
105        let mut collections = self.inner.db.store().list_collections();
106        collections.sort();
107        collections.dedup();
108        if collections.is_empty() {
109            collections.push("__database__".to_string());
110        }
111        let mut collection_data = Vec::new();
112        for collection in collections {
113            let payload = if collection == "__database__" {
114                self.inner.db.store().to_binary_dump_bytes()
115            } else {
116                self.serverless_collection_snapshot_bytes(&collection)?
117            };
118            let offset = collection_data.len() as u64;
119            collection_data.extend_from_slice(&payload);
120            extent_index.push(
121                plan.collection_data_extent_ref(collection, offset, &payload, true)
122                    .map_err(|err| RedDBError::Internal(err.to_string()))?,
123            );
124        }
125        let secondary_index =
126            reddb_file::ServerlessSecondaryIndex::from_extent_index(&extent_index);
127        let pointer = plan
128            .publish_core_generation(&extent_index, &collection_data, &secondary_index.encode())
129            .map_err(|err| RedDBError::Internal(err.to_string()))?;
130        Ok(Some(pointer))
131    }
132
133    pub fn read_current_serverless_generation_verified(
134        &self,
135    ) -> RedDBResult<Option<reddb_file::ServerlessGenerationPointer>> {
136        let Some(plan) = self.serverless_file_plan() else {
137            return Ok(None);
138        };
139        match plan.read_current_pointer_verified() {
140            Ok(pointer) => Ok(Some(pointer)),
141            Err(reddb_file::RdbFileError::Io(err))
142                if err.kind() == std::io::ErrorKind::NotFound =>
143            {
144                Ok(None)
145            }
146            Err(err) => Err(RedDBError::InvalidOperation(format!(
147                "corrupt serverless generation: {err}"
148            ))),
149        }
150    }
151
152    pub fn hydrate_current_serverless_collection(
153        &self,
154        collection: &str,
155    ) -> RedDBResult<Option<Vec<reddb_file::ServerlessHydratedRange>>> {
156        let Some(pointer) = self.read_current_serverless_generation_verified()? else {
157            return Ok(None);
158        };
159        let Some(plan) = self.serverless_file_plan_for_generation(pointer.generation) else {
160            return Ok(None);
161        };
162        let secondary =
163            reddb_file::ServerlessSecondaryIndex::read_from_path(plan.secondary_index_path())
164                .map_err(|err| {
165                    RedDBError::InvalidOperation(format!("corrupt serverless generation: {err}"))
166                })?;
167        let hydration = secondary.hydration_plan_for_collection(collection);
168        plan.hydrate_local_plan(&hydration)
169            .map(Some)
170            .map_err(|err| {
171                RedDBError::InvalidOperation(format!("serverless hydrate failed: {err}"))
172            })
173    }
174
175    pub fn hydrate_current_serverless_key(
176        &self,
177        collection: &str,
178        key: &[u8],
179    ) -> RedDBResult<Option<Vec<reddb_file::ServerlessHydratedRange>>> {
180        let Some(pointer) = self.read_current_serverless_generation_verified()? else {
181            return Ok(None);
182        };
183        let Some(plan) = self.serverless_file_plan_for_generation(pointer.generation) else {
184            return Ok(None);
185        };
186        let index = reddb_file::ServerlessExtentIndex::read_from_path(plan.extent_index_path())
187            .map_err(|err| {
188                RedDBError::InvalidOperation(format!("corrupt serverless generation: {err}"))
189            })?;
190        let hydration = index.hydration_plan_for_key(collection, key);
191        plan.hydrate_local_plan(&hydration)
192            .map(Some)
193            .map_err(|err| {
194                RedDBError::InvalidOperation(format!("serverless hydrate failed: {err}"))
195            })
196    }
197
198    pub fn hydrate_current_serverless_range(
199        &self,
200        collection: &str,
201        range_start: &[u8],
202        range_end: &[u8],
203    ) -> RedDBResult<Option<Vec<reddb_file::ServerlessHydratedRange>>> {
204        let Some(pointer) = self.read_current_serverless_generation_verified()? else {
205            return Ok(None);
206        };
207        let Some(plan) = self.serverless_file_plan_for_generation(pointer.generation) else {
208            return Ok(None);
209        };
210        let index = reddb_file::ServerlessExtentIndex::read_from_path(plan.extent_index_path())
211            .map_err(|err| {
212                RedDBError::InvalidOperation(format!("corrupt serverless generation: {err}"))
213            })?;
214        let hydration = index
215            .hydration_plan_for_range(collection, range_start, range_end)
216            .map_err(|err| RedDBError::InvalidOperation(err.to_string()))?;
217        plan.hydrate_local_plan(&hydration)
218            .map(Some)
219            .map_err(|err| {
220                RedDBError::InvalidOperation(format!("serverless hydrate failed: {err}"))
221            })
222    }
223
224    pub fn hydrate_current_serverless_key_cached(
225        &self,
226        collection: &str,
227        key: &[u8],
228    ) -> RedDBResult<Option<Vec<reddb_file::ServerlessHydratedRange>>> {
229        let Some(pointer) = self.read_current_serverless_generation_verified()? else {
230            return Ok(None);
231        };
232        let Some(plan) = self.serverless_file_plan_for_generation(pointer.generation) else {
233            return Ok(None);
234        };
235        let Some(cache) = self.serverless_local_cache_for_generation(pointer.generation) else {
236            return Ok(None);
237        };
238        let index = reddb_file::ServerlessExtentIndex::read_from_path(plan.extent_index_path())
239            .map_err(|err| {
240                RedDBError::InvalidOperation(format!("corrupt serverless generation: {err}"))
241            })?;
242        let hydration = index.hydration_plan_for_key(collection, key);
243        plan.hydrate_local_plan_cached(&hydration, &cache)
244            .map(Some)
245            .map_err(|err| {
246                RedDBError::InvalidOperation(format!("serverless hydrate failed: {err}"))
247            })
248    }
249
250    pub fn prefetch_current_serverless_hot_extents_cached(
251        &self,
252    ) -> RedDBResult<Option<Vec<reddb_file::ServerlessHydratedRange>>> {
253        let Some(pointer) = self.read_current_serverless_generation_verified()? else {
254            return Ok(None);
255        };
256        let Some(plan) = self.serverless_file_plan_for_generation(pointer.generation) else {
257            return Ok(None);
258        };
259        let Some(cache) = self.serverless_local_cache_for_generation(pointer.generation) else {
260            return Ok(None);
261        };
262        let index = reddb_file::ServerlessExtentIndex::read_from_path(plan.extent_index_path())
263            .map_err(|err| {
264                RedDBError::InvalidOperation(format!("corrupt serverless generation: {err}"))
265            })?;
266        plan.prefetch_hot_extents_cached(&index, &cache)
267            .map(Some)
268            .map_err(|err| {
269                RedDBError::InvalidOperation(format!("serverless hydrate failed: {err}"))
270            })
271    }
272}