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