-- dd0c/route V1 — TimescaleDB schema (telemetry) -- Request events (the core telemetry table) CREATE TABLE request_events ( time TIMESTAMPTZ NOT NULL DEFAULT now(), org_id UUID NOT NULL, original_model TEXT NOT NULL, routed_model TEXT NOT NULL, provider TEXT NOT NULL, strategy TEXT NOT NULL, latency_ms INT NOT NULL, status_code SMALLINT NOT NULL, is_streaming BOOLEAN NOT NULL DEFAULT false, prompt_tokens INT NOT NULL DEFAULT 0, completion_tokens INT NOT NULL DEFAULT 0, cost_original NUMERIC(10, 6), cost_actual NUMERIC(10, 6), cost_saved NUMERIC(10, 6) ); -- Convert to hypertable (7-day chunks) SELECT create_hypertable('request_events', 'time', chunk_time_interval => INTERVAL '7 days'); -- Indexes for dashboard queries CREATE INDEX idx_events_org_time ON request_events (org_id, time DESC); CREATE INDEX idx_events_model ON request_events (org_id, original_model, time DESC); -- Continuous aggregates for dashboard rollups -- Hourly rollup CREATE MATERIALIZED VIEW request_events_hourly WITH (timescaledb.continuous) AS SELECT time_bucket('1 hour', time) AS bucket, org_id, original_model, routed_model, provider, strategy, COUNT(*) AS request_count, AVG(latency_ms)::INT AS avg_latency_ms, PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY latency_ms)::INT AS p99_latency_ms, SUM(prompt_tokens) AS total_prompt_tokens, SUM(completion_tokens) AS total_completion_tokens, SUM(cost_original) AS total_cost_original, SUM(cost_actual) AS total_cost_actual, SUM(cost_saved) AS total_cost_saved FROM request_events GROUP BY bucket, org_id, original_model, routed_model, provider, strategy; -- Refresh policy: every hour, lag 10 minutes SELECT add_continuous_aggregate_policy('request_events_hourly', start_offset => INTERVAL '2 hours', end_offset => INTERVAL '10 minutes', schedule_interval => INTERVAL '1 hour'); -- Daily rollup CREATE MATERIALIZED VIEW request_events_daily WITH (timescaledb.continuous) AS SELECT time_bucket('1 day', time) AS bucket, org_id, original_model, routed_model, COUNT(*) AS request_count, SUM(prompt_tokens) AS total_prompt_tokens, SUM(completion_tokens) AS total_completion_tokens, SUM(cost_original) AS total_cost_original, SUM(cost_actual) AS total_cost_actual, SUM(cost_saved) AS total_cost_saved FROM request_events GROUP BY bucket, org_id, original_model, routed_model; SELECT add_continuous_aggregate_policy('request_events_daily', start_offset => INTERVAL '3 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '1 day'); -- Compression policy: compress chunks older than 30 days ALTER TABLE request_events SET ( timescaledb.compress, timescaledb.compress_segmentby = 'org_id', timescaledb.compress_orderby = 'time DESC' ); SELECT add_compression_policy('request_events', INTERVAL '30 days'); -- Retention policy: drop data older than 1 year (free tier: 30 days via app logic) SELECT add_retention_policy('request_events', INTERVAL '365 days');