Skip to main content

tael_server/storage/
mod.rs

1mod backend;
2mod blobs;
3#[cfg(feature = "duckdb")]
4mod duckdb_store;
5mod fanout;
6pub mod models;
7mod remote;
8mod search;
9
10pub use backend::{TaelBackend, WalSink};
11pub use blobs::BlobStore;
12#[cfg(feature = "duckdb")]
13pub use duckdb_store::DuckDbStore;
14pub use fanout::FanoutStore;
15pub use remote::{RemoteStore, RemoteWalSink, WAL_EPOCH_HEADER};
16pub use search::SearchIndex;
17
18use anyhow::Result;
19
20use models::{
21    AnomalyReport, CorrelateReport, LogQuery, LogRecord, MetricPoint, MetricQuery, ServiceInfo,
22    Span, SummaryReport, TraceComment, TraceQuery,
23};
24
25/// Storage backend for all telemetry signals. The server depends on
26/// `Arc<dyn Store>`, so backends (DuckDB today, `TaelBackend` next) are
27/// swappable without touching the API, ingest, or query layers.
28///
29/// Synchronous by design — see `docs/tael-backend-impl-plan.md`, Phase 0.
30/// `Send + Sync` so it can be shared as `Arc<dyn Store>` across tokio tasks and
31/// held in axum state.
32pub trait Store: Send + Sync {
33    // ── Spans / traces ──────────────────────────────────────────────
34    fn insert_spans(&self, spans: &[Span]) -> Result<()>;
35    fn query_traces(&self, query: &TraceQuery) -> Result<Vec<Span>>;
36    fn get_trace(&self, trace_id: &str) -> Result<Vec<Span>>;
37    fn list_services(&self) -> Result<Vec<ServiceInfo>>;
38
39    // ── Comments ────────────────────────────────────────────────────
40    fn add_comment(
41        &self,
42        trace_id: &str,
43        span_id: Option<&str>,
44        author: &str,
45        body: &str,
46    ) -> Result<TraceComment>;
47    fn get_comments(&self, trace_id: &str) -> Result<Vec<TraceComment>>;
48
49    // ── Logs ────────────────────────────────────────────────────────
50    fn insert_logs(&self, logs: &[LogRecord]) -> Result<()>;
51    fn query_logs(&self, query: &LogQuery) -> Result<Vec<LogRecord>>;
52
53    // ── Metrics ─────────────────────────────────────────────────────
54    fn insert_metrics(&self, metrics: &[MetricPoint]) -> Result<()>;
55    fn query_metrics(&self, query: &MetricQuery) -> Result<Vec<MetricPoint>>;
56
57    // ── Cross-signal analytics ──────────────────────────────────────
58    fn query_summary(&self, last_seconds: i64, service: Option<&str>) -> Result<SummaryReport>;
59    fn query_anomalies(
60        &self,
61        current_seconds: i64,
62        baseline_seconds: i64,
63        service: Option<&str>,
64    ) -> Result<AnomalyReport>;
65    fn query_correlate(&self, trace_id: &str) -> Result<Option<CorrelateReport>>;
66
67    /// Read-only SQL query surface (`SELECT`/`WITH`) over the telemetry tables,
68    /// returning rows as JSON objects.
69    fn query_sql(&self, sql: &str) -> Result<Vec<serde_json::Value>>;
70
71    // ── Lifecycle / operability (default no-ops) ────────────────────
72    /// Readiness probe — `Ok(())` when this store can serve requests. Backs the
73    /// REST `/readyz` endpoint (`docs/tael-server-scaling-ha.md` §5.4). The
74    /// default is `Ok(())`: an embedded backend that constructed successfully
75    /// and holds its file locks is, by definition, ready. Backends that depend
76    /// on the network (e.g. [`RemoteStore`], [`FanoutStore`](crate::storage))
77    /// override this to probe their dependencies.
78    fn health(&self) -> Result<()> {
79        Ok(())
80    }
81
82    /// Flush durable buffered state ahead of a graceful shutdown. The WAL fsync
83    /// on the write path is the real durability boundary, so this is
84    /// best-effort: it tightens the hot tier's on-disk state so a restart or
85    /// standby replays less WAL (§5.4 "flush the hot tier"). Default is a no-op.
86    fn flush(&self) -> Result<()> {
87        Ok(())
88    }
89
90    /// Standby entrypoint for WAL replication: durably accept a framed WAL
91    /// record shipped from a leader and bring local state up to it
92    /// (`docs/tael-server-scaling-ha.md` §5.1). Backs the
93    /// `POST /internal/wal/records` endpoint. Default: rejected — only the
94    /// tael-backend engine, which owns a WAL, can act as a standby.
95    fn apply_framed_wal(&self, _framed: &[u8]) -> Result<()> {
96        anyhow::bail!("this store does not accept WAL replication")
97    }
98}