Files
llmgw/internal/store/migrations/sqlite/0015_usage_rollup.sql
Ilker ManapandClaude Opus 5.5 9f479b9a70 Show each team's first-token and latency percentiles, errors and refusals
Percentiles are read from the histograms kept with the usage sums and shown
per group on the usage tab and in the people page's panels. Requests refused
by a limit are recorded as 429s against their owner, free of charge, so a team
hitting its limits shows as such. Usage can be grouped by model.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 19:50:16 +02:00

208 lines
14 KiB
SQL

-- Usage summed as it is written, per key, person, project and model: into
-- five-minute slices (usage_5m), hours (usage_1h) and days (usage_1d).
--
-- Every chart and total read the raw table, one row per request, and summed it
-- again on every page view: fine for thousands of requests a day, and a full
-- scan of tens of millions on a cluster in real use. The sums are kept in the
-- transaction that writes the rows, so they never disagree with them; the raw
-- table stays for following one request, and can be given a retention of its
-- own (auth.usage_retention).
--
-- Three widths because how well a slice compresses depends on traffic: many
-- keys each sending a little leave a five-minute slice with nearly a row per
-- request, which an hour or a day folds much further. A chart reads the
-- coarsest width its slices are made of; a total since a moment reads the
-- rows up to the next five-minute slice, those slices up to the hour, hours
-- up to the day and days from there — exact, and a month in about thirty
-- rows per key and model.
--
-- Latency and time to first token are kept as histograms, a count per fixed
-- bucket (ms: <=100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000, 120000,
-- and above), which is what a percentile per team or model is read from after
-- the rows are gone; a mean cannot do that. ttft_* counts only requests whose
-- first token was measured. limited counts requests refused with 429 — a rate,
-- token or budget limit — which are recorded with no tokens and no cost.
CREATE TABLE usage_5m (
bucket INTEGER NOT NULL,
key_id TEXT NOT NULL,
user_id TEXT NOT NULL,
project_id TEXT NOT NULL,
model TEXT NOT NULL,
served_model TEXT NOT NULL,
requests INTEGER NOT NULL DEFAULT 0,
errors INTEGER NOT NULL DEFAULT 0,
limited INTEGER NOT NULL DEFAULT 0,
prompt_tokens INTEGER NOT NULL DEFAULT 0,
completion_tokens INTEGER NOT NULL DEFAULT 0,
cost_micros INTEGER NOT NULL DEFAULT 0,
latency_ms_sum INTEGER NOT NULL DEFAULT 0,
queue_ms_sum INTEGER NOT NULL DEFAULT 0,
ttft_ms_sum INTEGER NOT NULL DEFAULT 0,
ttft_n INTEGER NOT NULL DEFAULT 0,
last_ts INTEGER NOT NULL DEFAULT 0,
lat_0 INTEGER NOT NULL DEFAULT 0,
lat_1 INTEGER NOT NULL DEFAULT 0,
lat_2 INTEGER NOT NULL DEFAULT 0,
lat_3 INTEGER NOT NULL DEFAULT 0,
lat_4 INTEGER NOT NULL DEFAULT 0,
lat_5 INTEGER NOT NULL DEFAULT 0,
lat_6 INTEGER NOT NULL DEFAULT 0,
lat_7 INTEGER NOT NULL DEFAULT 0,
lat_8 INTEGER NOT NULL DEFAULT 0,
lat_9 INTEGER NOT NULL DEFAULT 0,
lat_10 INTEGER NOT NULL DEFAULT 0,
ttft_0 INTEGER NOT NULL DEFAULT 0,
ttft_1 INTEGER NOT NULL DEFAULT 0,
ttft_2 INTEGER NOT NULL DEFAULT 0,
ttft_3 INTEGER NOT NULL DEFAULT 0,
ttft_4 INTEGER NOT NULL DEFAULT 0,
ttft_5 INTEGER NOT NULL DEFAULT 0,
ttft_6 INTEGER NOT NULL DEFAULT 0,
ttft_7 INTEGER NOT NULL DEFAULT 0,
ttft_8 INTEGER NOT NULL DEFAULT 0,
ttft_9 INTEGER NOT NULL DEFAULT 0,
ttft_10 INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (bucket, key_id, user_id, project_id, model, served_model)
);
CREATE INDEX usage_5m_by_bucket ON usage_5m(bucket);
CREATE INDEX usage_5m_by_project ON usage_5m(project_id, bucket);
CREATE INDEX usage_5m_by_user ON usage_5m(user_id, bucket);
CREATE INDEX usage_5m_by_key ON usage_5m(key_id, bucket);
INSERT INTO usage_5m (bucket, key_id, user_id, project_id, model, served_model,
requests, errors, limited, prompt_tokens, completion_tokens, cost_micros,
latency_ms_sum, queue_ms_sum, ttft_ms_sum, ttft_n, last_ts,
lat_0, lat_1, lat_2, lat_3, lat_4, lat_5, lat_6, lat_7, lat_8, lat_9, lat_10,
ttft_0, ttft_1, ttft_2, ttft_3, ttft_4, ttft_5, ttft_6, ttft_7, ttft_8, ttft_9, ttft_10)
SELECT (ts / 300000) * 300000, key_id, COALESCE(user_id, ''), project_id, model, served_model,
COUNT(*), SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END), SUM(CASE WHEN status = 429 THEN 1 ELSE 0 END),
SUM(prompt_tokens), SUM(completion_tokens), SUM(cost_micros),
SUM(latency_ms), SUM(queue_ms), SUM(ttft_ms), SUM(CASE WHEN ttft_ms > 0 THEN 1 ELSE 0 END), MAX(ts),
SUM(CASE WHEN latency_ms <= 100 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 100 AND latency_ms <= 250 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 250 AND latency_ms <= 500 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 500 AND latency_ms <= 1000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 1000 AND latency_ms <= 2500 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 2500 AND latency_ms <= 5000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 5000 AND latency_ms <= 10000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 10000 AND latency_ms <= 30000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 30000 AND latency_ms <= 60000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 60000 AND latency_ms <= 120000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 120000 THEN 1 ELSE 0 END),
SUM(CASE WHEN ttft_ms > 0 AND ttft_ms <= 100 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 100 AND ttft_ms <= 250 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 250 AND ttft_ms <= 500 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 500 AND ttft_ms <= 1000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 1000 AND ttft_ms <= 2500 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 2500 AND ttft_ms <= 5000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 5000 AND ttft_ms <= 10000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 10000 AND ttft_ms <= 30000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 30000 AND ttft_ms <= 60000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 60000 AND ttft_ms <= 120000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 120000 THEN 1 ELSE 0 END)
FROM usage
GROUP BY 1, 2, 3, 4, 5, 6;
CREATE TABLE usage_1h (
bucket INTEGER NOT NULL,
key_id TEXT NOT NULL,
user_id TEXT NOT NULL,
project_id TEXT NOT NULL,
model TEXT NOT NULL,
served_model TEXT NOT NULL,
requests INTEGER NOT NULL DEFAULT 0,
errors INTEGER NOT NULL DEFAULT 0,
limited INTEGER NOT NULL DEFAULT 0,
prompt_tokens INTEGER NOT NULL DEFAULT 0,
completion_tokens INTEGER NOT NULL DEFAULT 0,
cost_micros INTEGER NOT NULL DEFAULT 0,
latency_ms_sum INTEGER NOT NULL DEFAULT 0,
queue_ms_sum INTEGER NOT NULL DEFAULT 0,
ttft_ms_sum INTEGER NOT NULL DEFAULT 0,
ttft_n INTEGER NOT NULL DEFAULT 0,
last_ts INTEGER NOT NULL DEFAULT 0,
lat_0 INTEGER NOT NULL DEFAULT 0,
lat_1 INTEGER NOT NULL DEFAULT 0,
lat_2 INTEGER NOT NULL DEFAULT 0,
lat_3 INTEGER NOT NULL DEFAULT 0,
lat_4 INTEGER NOT NULL DEFAULT 0,
lat_5 INTEGER NOT NULL DEFAULT 0,
lat_6 INTEGER NOT NULL DEFAULT 0,
lat_7 INTEGER NOT NULL DEFAULT 0,
lat_8 INTEGER NOT NULL DEFAULT 0,
lat_9 INTEGER NOT NULL DEFAULT 0,
lat_10 INTEGER NOT NULL DEFAULT 0,
ttft_0 INTEGER NOT NULL DEFAULT 0,
ttft_1 INTEGER NOT NULL DEFAULT 0,
ttft_2 INTEGER NOT NULL DEFAULT 0,
ttft_3 INTEGER NOT NULL DEFAULT 0,
ttft_4 INTEGER NOT NULL DEFAULT 0,
ttft_5 INTEGER NOT NULL DEFAULT 0,
ttft_6 INTEGER NOT NULL DEFAULT 0,
ttft_7 INTEGER NOT NULL DEFAULT 0,
ttft_8 INTEGER NOT NULL DEFAULT 0,
ttft_9 INTEGER NOT NULL DEFAULT 0,
ttft_10 INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (bucket, key_id, user_id, project_id, model, served_model)
);
CREATE INDEX usage_1h_by_bucket ON usage_1h(bucket);
CREATE INDEX usage_1h_by_project ON usage_1h(project_id, bucket);
CREATE INDEX usage_1h_by_user ON usage_1h(user_id, bucket);
CREATE INDEX usage_1h_by_key ON usage_1h(key_id, bucket);
INSERT INTO usage_1h (bucket, key_id, user_id, project_id, model, served_model,
requests, errors, limited, prompt_tokens, completion_tokens, cost_micros,
latency_ms_sum, queue_ms_sum, ttft_ms_sum, ttft_n, last_ts,
lat_0, lat_1, lat_2, lat_3, lat_4, lat_5, lat_6, lat_7, lat_8, lat_9, lat_10,
ttft_0, ttft_1, ttft_2, ttft_3, ttft_4, ttft_5, ttft_6, ttft_7, ttft_8, ttft_9, ttft_10)
SELECT (ts / 3600000) * 3600000, key_id, COALESCE(user_id, ''), project_id, model, served_model,
COUNT(*), SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END), SUM(CASE WHEN status = 429 THEN 1 ELSE 0 END),
SUM(prompt_tokens), SUM(completion_tokens), SUM(cost_micros),
SUM(latency_ms), SUM(queue_ms), SUM(ttft_ms), SUM(CASE WHEN ttft_ms > 0 THEN 1 ELSE 0 END), MAX(ts),
SUM(CASE WHEN latency_ms <= 100 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 100 AND latency_ms <= 250 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 250 AND latency_ms <= 500 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 500 AND latency_ms <= 1000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 1000 AND latency_ms <= 2500 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 2500 AND latency_ms <= 5000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 5000 AND latency_ms <= 10000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 10000 AND latency_ms <= 30000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 30000 AND latency_ms <= 60000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 60000 AND latency_ms <= 120000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 120000 THEN 1 ELSE 0 END),
SUM(CASE WHEN ttft_ms > 0 AND ttft_ms <= 100 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 100 AND ttft_ms <= 250 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 250 AND ttft_ms <= 500 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 500 AND ttft_ms <= 1000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 1000 AND ttft_ms <= 2500 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 2500 AND ttft_ms <= 5000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 5000 AND ttft_ms <= 10000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 10000 AND ttft_ms <= 30000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 30000 AND ttft_ms <= 60000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 60000 AND ttft_ms <= 120000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 120000 THEN 1 ELSE 0 END)
FROM usage
GROUP BY 1, 2, 3, 4, 5, 6;
CREATE TABLE usage_1d (
bucket INTEGER NOT NULL,
key_id TEXT NOT NULL,
user_id TEXT NOT NULL,
project_id TEXT NOT NULL,
model TEXT NOT NULL,
served_model TEXT NOT NULL,
requests INTEGER NOT NULL DEFAULT 0,
errors INTEGER NOT NULL DEFAULT 0,
limited INTEGER NOT NULL DEFAULT 0,
prompt_tokens INTEGER NOT NULL DEFAULT 0,
completion_tokens INTEGER NOT NULL DEFAULT 0,
cost_micros INTEGER NOT NULL DEFAULT 0,
latency_ms_sum INTEGER NOT NULL DEFAULT 0,
queue_ms_sum INTEGER NOT NULL DEFAULT 0,
ttft_ms_sum INTEGER NOT NULL DEFAULT 0,
ttft_n INTEGER NOT NULL DEFAULT 0,
last_ts INTEGER NOT NULL DEFAULT 0,
lat_0 INTEGER NOT NULL DEFAULT 0,
lat_1 INTEGER NOT NULL DEFAULT 0,
lat_2 INTEGER NOT NULL DEFAULT 0,
lat_3 INTEGER NOT NULL DEFAULT 0,
lat_4 INTEGER NOT NULL DEFAULT 0,
lat_5 INTEGER NOT NULL DEFAULT 0,
lat_6 INTEGER NOT NULL DEFAULT 0,
lat_7 INTEGER NOT NULL DEFAULT 0,
lat_8 INTEGER NOT NULL DEFAULT 0,
lat_9 INTEGER NOT NULL DEFAULT 0,
lat_10 INTEGER NOT NULL DEFAULT 0,
ttft_0 INTEGER NOT NULL DEFAULT 0,
ttft_1 INTEGER NOT NULL DEFAULT 0,
ttft_2 INTEGER NOT NULL DEFAULT 0,
ttft_3 INTEGER NOT NULL DEFAULT 0,
ttft_4 INTEGER NOT NULL DEFAULT 0,
ttft_5 INTEGER NOT NULL DEFAULT 0,
ttft_6 INTEGER NOT NULL DEFAULT 0,
ttft_7 INTEGER NOT NULL DEFAULT 0,
ttft_8 INTEGER NOT NULL DEFAULT 0,
ttft_9 INTEGER NOT NULL DEFAULT 0,
ttft_10 INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (bucket, key_id, user_id, project_id, model, served_model)
);
CREATE INDEX usage_1d_by_bucket ON usage_1d(bucket);
CREATE INDEX usage_1d_by_project ON usage_1d(project_id, bucket);
CREATE INDEX usage_1d_by_user ON usage_1d(user_id, bucket);
CREATE INDEX usage_1d_by_key ON usage_1d(key_id, bucket);
INSERT INTO usage_1d (bucket, key_id, user_id, project_id, model, served_model,
requests, errors, limited, prompt_tokens, completion_tokens, cost_micros,
latency_ms_sum, queue_ms_sum, ttft_ms_sum, ttft_n, last_ts,
lat_0, lat_1, lat_2, lat_3, lat_4, lat_5, lat_6, lat_7, lat_8, lat_9, lat_10,
ttft_0, ttft_1, ttft_2, ttft_3, ttft_4, ttft_5, ttft_6, ttft_7, ttft_8, ttft_9, ttft_10)
SELECT (ts / 86400000) * 86400000, key_id, COALESCE(user_id, ''), project_id, model, served_model,
COUNT(*), SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END), SUM(CASE WHEN status = 429 THEN 1 ELSE 0 END),
SUM(prompt_tokens), SUM(completion_tokens), SUM(cost_micros),
SUM(latency_ms), SUM(queue_ms), SUM(ttft_ms), SUM(CASE WHEN ttft_ms > 0 THEN 1 ELSE 0 END), MAX(ts),
SUM(CASE WHEN latency_ms <= 100 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 100 AND latency_ms <= 250 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 250 AND latency_ms <= 500 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 500 AND latency_ms <= 1000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 1000 AND latency_ms <= 2500 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 2500 AND latency_ms <= 5000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 5000 AND latency_ms <= 10000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 10000 AND latency_ms <= 30000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 30000 AND latency_ms <= 60000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 60000 AND latency_ms <= 120000 THEN 1 ELSE 0 END), SUM(CASE WHEN latency_ms > 120000 THEN 1 ELSE 0 END),
SUM(CASE WHEN ttft_ms > 0 AND ttft_ms <= 100 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 100 AND ttft_ms <= 250 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 250 AND ttft_ms <= 500 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 500 AND ttft_ms <= 1000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 1000 AND ttft_ms <= 2500 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 2500 AND ttft_ms <= 5000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 5000 AND ttft_ms <= 10000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 10000 AND ttft_ms <= 30000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 30000 AND ttft_ms <= 60000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 60000 AND ttft_ms <= 120000 THEN 1 ELSE 0 END), SUM(CASE WHEN ttft_ms > 0 AND ttft_ms > 120000 THEN 1 ELSE 0 END)
FROM usage
GROUP BY 1, 2, 3, 4, 5, 6;