Files
goclaw/migrations/000016_usage_snapshots.up.sql
T
Viet Tran 73389d2715 fix(ui): align usage data contracts, add timezone setting, and fix empty usage page (#146)
- Fix 6 data contract mismatches between Go backend JSON tags and React
  frontend TypeScript interfaces (field renames, response envelope changes)
- Add timezone selector to topbar with 12 common timezone options
- Replace date-fns formatting with native Intl.DateTimeFormat for
  timezone-aware chart labels (reduces bundle ~20KB)
- Add missing SnapshotTimeSeries fields (memory_docs, memory_chunks,
  kg_entities, kg_relations) that caused empty usage page
- Add error banner to usage page for API error visibility
- Sanitize backend error messages in usage HTTP handlers
- Add batch chunking (max 3000 rows) for snapshot upserts
- Remove userId display from topbar
- Add usage analytics i18n strings for en/vi/zh
2026-03-11 14:22:03 +07:00

80 lines
3.3 KiB
SQL

-- ============================================================
-- Part 1: New indexes on EXISTING tables (optimize aggregation)
-- ============================================================
-- Traces: snapshot worker scans by start_time for root traces only
-- Replaces Seq Scan (2.5ms→0.1ms at current scale, critical at 100K+ rows)
CREATE INDEX IF NOT EXISTS idx_traces_start_root ON traces (start_time DESC)
WHERE parent_trace_id IS NULL;
-- Spans: snapshot worker joins on trace_id filtering by span_type
-- Current idx_spans_trace is (trace_id, start_time) — start_time useless here
-- This index lets PG filter span_type IN the index, avoiding wide-row fetches
CREATE INDEX IF NOT EXISTS idx_spans_trace_type ON spans (trace_id, span_type);
-- ============================================================
-- Part 2: usage_snapshots table
-- ============================================================
CREATE TABLE IF NOT EXISTS usage_snapshots (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
bucket_hour TIMESTAMPTZ NOT NULL,
agent_id UUID,
provider VARCHAR(50) NOT NULL DEFAULT '',
model VARCHAR(200) NOT NULL DEFAULT '',
channel VARCHAR(50) NOT NULL DEFAULT '',
-- Token metrics
input_tokens BIGINT NOT NULL DEFAULT 0,
output_tokens BIGINT NOT NULL DEFAULT 0,
cache_read_tokens BIGINT NOT NULL DEFAULT 0,
cache_create_tokens BIGINT NOT NULL DEFAULT 0,
thinking_tokens BIGINT NOT NULL DEFAULT 0,
-- Cost
total_cost NUMERIC(12,6) NOT NULL DEFAULT 0,
-- Counts
request_count INTEGER NOT NULL DEFAULT 0,
llm_call_count INTEGER NOT NULL DEFAULT 0,
tool_call_count INTEGER NOT NULL DEFAULT 0,
error_count INTEGER NOT NULL DEFAULT 0,
unique_users INTEGER NOT NULL DEFAULT 0,
-- Duration
avg_duration_ms INTEGER NOT NULL DEFAULT 0,
-- Memory & Knowledge Graph (point-in-time counts)
memory_docs INTEGER NOT NULL DEFAULT 0,
memory_chunks INTEGER NOT NULL DEFAULT 0,
kg_entities INTEGER NOT NULL DEFAULT 0,
kg_relations INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================
-- Part 3: usage_snapshots indexes
-- ============================================================
-- Time-series queries: GROUP BY bucket_hour ORDER BY bucket_hour
CREATE INDEX IF NOT EXISTS idx_usage_snapshots_bucket ON usage_snapshots (bucket_hour DESC);
-- Agent-scoped time-series: WHERE agent_id = $1 ORDER BY bucket_hour
CREATE INDEX IF NOT EXISTS idx_usage_snapshots_agent_bucket ON usage_snapshots (agent_id, bucket_hour DESC);
-- Cross-filter: WHERE provider = $1 AND bucket_hour BETWEEN ...
CREATE INDEX IF NOT EXISTS idx_usage_snapshots_provider_bucket ON usage_snapshots (provider, bucket_hour DESC)
WHERE provider != '';
-- Cross-filter: WHERE channel = $1 AND bucket_hour BETWEEN ...
CREATE INDEX IF NOT EXISTS idx_usage_snapshots_channel_bucket ON usage_snapshots (channel, bucket_hour DESC)
WHERE channel != '';
-- Upsert dedup: ON CONFLICT — prevents duplicate snapshot rows
CREATE UNIQUE INDEX IF NOT EXISTS idx_usage_snapshots_unique ON usage_snapshots (
bucket_hour,
COALESCE(agent_id, '00000000-0000-0000-0000-000000000000'),
provider, model, channel
);