reddb_server/runtime/
impl_catalog_accessors.rs1use super::*;
8
9fn runtime_pool_lock(runtime: &RedDBRuntime) -> std::sync::MutexGuard<'_, PoolState> {
10 runtime
11 .inner
12 .pool
13 .lock()
14 .unwrap_or_else(|poisoned| poisoned.into_inner())
15}
16
17impl RedDBRuntime {
18 pub fn acquire(&self) -> RedDBResult<RuntimeConnection> {
19 self.acquire_connection(true)
20 }
21
22 pub(crate) fn acquire_wire_connection(&self) -> RedDBResult<RuntimeConnection> {
25 self.acquire_connection(false)
26 }
27
28 fn acquire_connection(&self, uses_pool_slot: bool) -> RedDBResult<RuntimeConnection> {
29 let mut pool = self
30 .inner
31 .pool
32 .lock()
33 .map_err(|e| RedDBError::Internal(format!("connection pool lock poisoned: {e}")))?;
34 if uses_pool_slot && pool.pooled_active >= self.inner.pool_config.max_connections {
35 return Err(RedDBError::Internal(
36 "connection pool exhausted".to_string(),
37 ));
38 }
39
40 let id = if let Some(id) = pool.idle.pop() {
41 id
42 } else {
43 let id = pool.next_id;
44 pool.next_id += 1;
45 id
46 };
47 pool.active += 1;
48 if uses_pool_slot {
49 pool.pooled_active += 1;
50 }
51 pool.total_checkouts += 1;
52 drop(pool);
53
54 self.inner.node_load_telemetry.record_connect();
57
58 Ok(RuntimeConnection {
59 background_tasks: self.background_tasks.clone(),
60 id,
61 uses_pool_slot,
62 inner: Arc::clone(&self.inner),
63 })
64 }
65
66 pub fn checkpoint(&self) -> RedDBResult<()> {
67 self.checkpoint_local()?;
72 if let Err(err) = self.assert_remote_write_allowed("checkpoint") {
73 tracing::warn!(
74 target: "reddb::serverless::lease",
75 error = %err,
76 "checkpoint: skipping remote upload — lease not held"
77 );
78 return Ok(());
79 }
80 self.inner
81 .db
82 .upload_to_remote_backend()
83 .map_err(|err| RedDBError::Engine(err.to_string()))
84 }
85
86 pub(super) fn checkpoint_local(&self) -> RedDBResult<()> {
87 let started = std::time::Instant::now();
88 self.seal_hypertable_chunks_for_checkpoint(
89 self.inner.checkpoint_columnar_emission_budget_chunks,
90 )?;
91 let store = self.inner.db.store();
92 let publication = store.vault_publication_lock.lock();
93 self.prepare_paged_counts()?;
96 self.persist_probabilistic_snapshots()?;
97 self.inner.db.flush_local_only().map_err(|err| {
98 let msg = err.to_string();
103 crate::telemetry::operator_event::OperatorEvent::CheckpointFailed {
104 lsn: 0,
105 error: msg.clone(),
106 }
107 .emit_global();
108 crate::telemetry::operator_event::OperatorEvent::WalFsyncFailed {
109 path: "<flush_local_only>".to_string(),
110 error: msg.clone(),
111 }
112 .emit_global();
113 RedDBError::Engine(msg)
114 })?;
115 drop(publication);
116 let checkpoint_lsn = self.cdc_current_lsn();
117 self.inner
118 .checkpoint_projection_stats
119 .record_checkpoint(checkpoint_lsn, started.elapsed().as_millis() as u64);
120 Ok(())
121 }
122
123 pub(crate) fn assert_remote_write_allowed(&self, action: &str) -> RedDBResult<()> {
130 if self.inner.db.remote_backend.is_none() {
131 return Ok(());
132 }
133 match self.inner.write_gate.lease_state() {
134 crate::runtime::write_gate::LeaseGateState::NotHeld => {
135 self.inner.audit_log.record(
136 action,
137 "system",
138 "remote_backend",
139 "err: writer lease not held",
140 crate::json::Value::Null,
141 );
142 Err(RedDBError::ReadOnly(format!(
143 "writer lease not held — {action} blocked (serverless fence)"
144 )))
145 }
146 _ => Ok(()),
147 }
148 }
149
150 pub fn run_maintenance(&self) -> RedDBResult<()> {
151 self.inner
152 .db
153 .run_maintenance()
154 .map_err(|err| RedDBError::Internal(err.to_string()))
155 }
156
157 pub fn scan_collection(
158 &self,
159 collection: &str,
160 cursor: Option<ScanCursor>,
161 limit: usize,
162 ) -> RedDBResult<ScanPage> {
163 let store = self.inner.db.store();
164 let manager = store
165 .get_collection(collection)
166 .ok_or_else(|| RedDBError::NotFound(collection.to_string()))?;
167
168 let offset = cursor.map(|cursor| cursor.offset).unwrap_or(0);
169 let limit = limit.max(1);
170 let read_bytes = usize::try_from(self.memory_budget().resolved_bytes / 16)
171 .unwrap_or(usize::MAX)
172 .clamp(1, 16 * 1024 * 1024);
173 let (total, items) = manager.try_page_in_id_order(offset, limit, read_bytes)?;
174 let end = total.min(offset.saturating_add(limit));
175 let next = (end < total).then_some(ScanCursor { offset: end });
176
177 Ok(ScanPage {
178 collection: collection.to_string(),
179 items,
180 next,
181 total,
182 })
183 }
184
185 pub fn catalog(&self) -> CatalogModelSnapshot {
186 self.inner.db.catalog_model_snapshot()
187 }
188
189 pub fn try_catalog(&self) -> RedDBResult<CatalogModelSnapshot> {
192 if self
193 .inner
194 .db
195 .store()
196 .config()
197 .resident_memory_bytes_max
198 .is_none()
199 {
200 return Ok(self.catalog());
201 }
202 let read_bytes = usize::try_from(self.memory_budget().resolved_bytes / 16)
203 .unwrap_or(usize::MAX)
204 .clamp(1, 16 * 1024 * 1024);
205 let _scratch = self
206 .try_reserve_query_buffer(read_bytes * 2)
207 .ok_or_else(|| {
208 self.memory_accounting().record_admission_denied();
209 RedDBError::MaterializationLimitExceeded {
210 executor: "catalog",
211 limit: usize::try_from(self.memory_budget().resolved_bytes)
212 .unwrap_or(usize::MAX),
213 current: usize::try_from(self.memory_accounting().total_used_bytes())
214 .unwrap_or(usize::MAX)
215 .saturating_add(1),
216 }
217 })?;
218 Ok(self.inner.db.try_catalog_model_snapshot(read_bytes)?)
219 }
220
221 pub fn catalog_consistency_report(&self) -> crate::catalog::CatalogConsistencyReport {
222 self.inner.db.catalog_consistency_report()
223 }
224
225 pub fn catalog_attention_summary(&self) -> CatalogAttentionSummary {
226 crate::catalog::attention_summary(&self.catalog())
227 }
228
229 pub fn collection_attention(&self) -> Vec<CollectionDescriptor> {
230 crate::catalog::collection_attention(&self.catalog())
231 }
232
233 pub fn index_attention(&self) -> Vec<CatalogIndexStatus> {
234 crate::catalog::index_attention(&self.catalog())
235 }
236
237 pub fn graph_projection_attention(&self) -> Vec<CatalogGraphProjectionStatus> {
238 crate::catalog::graph_projection_attention(&self.catalog())
239 }
240
241 pub fn analytics_job_attention(&self) -> Vec<CatalogAnalyticsJobStatus> {
242 crate::catalog::analytics_job_attention(&self.catalog())
243 }
244
245 pub fn stats(&self) -> RuntimeStats {
246 let pool = runtime_pool_lock(self);
247 RuntimeStats {
248 active_connections: pool.active,
249 idle_connections: pool.idle.len(),
250 total_checkouts: pool.total_checkouts,
251 paged_mode: self.inner.db.is_paged(),
252 started_at_unix_ms: self.inner.started_at_unix_ms,
253 store: self.inner.db.stats(),
254 system: SystemInfo::collect(),
255 result_blob_cache: self.inner.result_blob_cache.stats(),
256 kv: self.inner.kv_stats.snapshot(),
257 metrics_ingest: self.inner.metrics_ingest_stats.snapshot(),
258 }
259 }
260}