SQL Query Performance Trending
Idea
Instrument all database queries with latency tracking so we can observe how query performance trends over time as the user base grows. Distinct from the one-time API performance audit — this is continuous, always-on visibility into SQL response times in production.
Motivation
We’re starting with straightforward query-time Postgres (no Redis, no materialized views). That’s fine at low scale, but we need to know when it stops being fine. If a leaderboard query that takes 8ms at 1k users takes 800ms at 50k users, we want to see that curve forming — not discover it when users start complaining.
Trending data also helps justify (or avoid) optimization work. If queries are holding steady, we don’t need to prematurely add complexity.
Approach
Instrumentation Layer
Use the existing tracing crate integration. Each database call in the provider layer should emit a span with:
db.statement— the query name or template (not the full SQL with parameters, to avoid cardinality explosion)db.operation— SELECT, INSERT, UPDATE, DELETEdb.table— primary table involveddb.duration_ms— query execution time- RPC context — which service/method triggered the query
sqlx already emits tracing spans for every query (sqlx::query target). The key decision is whether the default sqlx spans are sufficient or whether we want custom provider-level spans with richer labels.
Metrics Pipeline
Two complementary paths:
Option A: Trace-derived metrics (via OpenTelemetry)
- OpenTelemetry Collector receives spans from the server
- Connector or span metrics processor derives histograms from
db.duration_ms - Prometheus scrapes the collector
- Grafana dashboards visualize p50/p95/p99 per query name over time
Option B: Direct Prometheus histograms
- Add a
metricscrate histogram (sql_query_duration_seconds) in the provider layer - Label with
query_name,table,operation - Prometheus scrapes the
/metricsendpoint - Simpler pipeline, no collector dependency
Recommendation: Start with Option A if the observability stack already uses OTel Collector. Fall back to Option B if we want something lighter.
Grafana Dashboards
- SQL Overview: p50/p95/p99 latency across all queries, request rate, error rate
- Per-Query Detail: drill down by query name — latency distribution, trend line, outlier spikes
- Slow Query Log: queries exceeding a threshold (e.g., >100ms) with timestamp, query name, duration
- Growth Correlation: overlay query latency trends against user count / score count to see scaling curves
Alerting (Future)
- Alert on p95 > 200ms sustained for 5 minutes
- Alert on p99 > 1s
- Alert on sudden latency jump (>2x baseline over rolling window)
Open Questions
- Should we track individual query executions or just aggregate histograms? Histograms are cheaper and sufficient for trending; individual traces are useful for debugging specific slow requests.
- What cardinality is acceptable for query labels? One label per provider method (e.g.,
list_drills,get_user_achievements) keeps it manageable. Avoid per-parameter labeling. - Should we log slow queries (>100ms) to Loki as structured log events in addition to metrics? Useful for debugging but adds log volume.
- Does sqlx’s built-in
tracingintegration give us enough, or do we need custom spans in each provider method?
Related
- observability-setup — the observability stack this plugs into (Prometheus + Loki + Grafana)
- API performance audit — one-time audit against staging data; this idea covers continuous production monitoring
- deployment-pipeline — dashboard/alerting config would be deployed alongside the server