- PostgreSQL schema: orgs, users, api_keys, provider_configs, routing_rules, cost_tables, feature_flags - TimescaleDB schema: request_events hypertable, hourly/daily continuous aggregates, compression, retention - docker-compose.yml: postgres, timescaledb, redis for local dev - README with quick start, architecture overview, pricing tiers - .env.example, .gitignore
89 lines
3.0 KiB
SQL
89 lines
3.0 KiB
SQL
-- 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');
|