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 id,
60 uses_pool_slot,
61 inner: Arc::clone(&self.inner),
62 })
63 }
64
65 pub fn checkpoint(&self) -> RedDBResult<()> {
66 let started = std::time::Instant::now();
71 self.seal_hypertable_chunks_for_checkpoint(
72 self.inner.checkpoint_columnar_emission_budget_chunks,
73 )?;
74 self.persist_probabilistic_snapshots()?;
75 self.inner.db.flush_local_only().map_err(|err| {
76 let msg = err.to_string();
81 crate::telemetry::operator_event::OperatorEvent::CheckpointFailed {
82 lsn: 0,
83 error: msg.clone(),
84 }
85 .emit_global();
86 crate::telemetry::operator_event::OperatorEvent::WalFsyncFailed {
87 path: "<flush_local_only>".to_string(),
88 error: msg.clone(),
89 }
90 .emit_global();
91 RedDBError::Engine(msg)
92 })?;
93 let checkpoint_lsn = self.cdc_current_lsn();
94 self.inner
95 .checkpoint_projection_stats
96 .record_checkpoint(checkpoint_lsn, started.elapsed().as_millis() as u64);
97 if let Err(err) = self.assert_remote_write_allowed("checkpoint") {
98 tracing::warn!(
99 target: "reddb::serverless::lease",
100 error = %err,
101 "checkpoint: skipping remote upload — lease not held"
102 );
103 return Ok(());
104 }
105 self.inner
106 .db
107 .upload_to_remote_backend()
108 .map_err(|err| RedDBError::Engine(err.to_string()))
109 }
110
111 pub(crate) fn assert_remote_write_allowed(&self, action: &str) -> RedDBResult<()> {
118 if self.inner.db.remote_backend.is_none() {
119 return Ok(());
120 }
121 match self.inner.write_gate.lease_state() {
122 crate::runtime::write_gate::LeaseGateState::NotHeld => {
123 self.inner.audit_log.record(
124 action,
125 "system",
126 "remote_backend",
127 "err: writer lease not held",
128 crate::json::Value::Null,
129 );
130 Err(RedDBError::ReadOnly(format!(
131 "writer lease not held — {action} blocked (serverless fence)"
132 )))
133 }
134 _ => Ok(()),
135 }
136 }
137
138 pub fn run_maintenance(&self) -> RedDBResult<()> {
139 self.inner
140 .db
141 .run_maintenance()
142 .map_err(|err| RedDBError::Internal(err.to_string()))
143 }
144
145 pub fn scan_collection(
146 &self,
147 collection: &str,
148 cursor: Option<ScanCursor>,
149 limit: usize,
150 ) -> RedDBResult<ScanPage> {
151 let store = self.inner.db.store();
152 let manager = store
153 .get_collection(collection)
154 .ok_or_else(|| RedDBError::NotFound(collection.to_string()))?;
155
156 let mut entities = manager.query_all(|_| true);
157 entities.sort_by_key(|entity| entity.id.raw());
158
159 let offset = cursor.map(|cursor| cursor.offset).unwrap_or(0);
160 let total = entities.len();
161 let end = total.min(offset.saturating_add(limit.max(1)));
162 let items = if offset >= total {
163 Vec::new()
164 } else {
165 entities[offset..end].to_vec()
166 };
167 let next = (end < total).then_some(ScanCursor { offset: end });
168
169 Ok(ScanPage {
170 collection: collection.to_string(),
171 items,
172 next,
173 total,
174 })
175 }
176
177 pub fn catalog(&self) -> CatalogModelSnapshot {
178 self.inner.db.catalog_model_snapshot()
179 }
180
181 pub fn catalog_consistency_report(&self) -> crate::catalog::CatalogConsistencyReport {
182 self.inner.db.catalog_consistency_report()
183 }
184
185 pub fn catalog_attention_summary(&self) -> CatalogAttentionSummary {
186 crate::catalog::attention_summary(&self.catalog())
187 }
188
189 pub fn collection_attention(&self) -> Vec<CollectionDescriptor> {
190 crate::catalog::collection_attention(&self.catalog())
191 }
192
193 pub fn index_attention(&self) -> Vec<CatalogIndexStatus> {
194 crate::catalog::index_attention(&self.catalog())
195 }
196
197 pub fn graph_projection_attention(&self) -> Vec<CatalogGraphProjectionStatus> {
198 crate::catalog::graph_projection_attention(&self.catalog())
199 }
200
201 pub fn analytics_job_attention(&self) -> Vec<CatalogAnalyticsJobStatus> {
202 crate::catalog::analytics_job_attention(&self.catalog())
203 }
204
205 pub fn stats(&self) -> RuntimeStats {
206 let pool = runtime_pool_lock(self);
207 RuntimeStats {
208 active_connections: pool.active,
209 idle_connections: pool.idle.len(),
210 total_checkouts: pool.total_checkouts,
211 paged_mode: self.inner.db.is_paged(),
212 started_at_unix_ms: self.inner.started_at_unix_ms,
213 store: self.inner.db.stats(),
214 system: SystemInfo::collect(),
215 result_blob_cache: self.inner.result_blob_cache.stats(),
216 kv: self.inner.kv_stats.snapshot(),
217 metrics_ingest: self.inner.metrics_ingest_stats.snapshot(),
218 }
219 }
220}