Performance#
QuizBase is built and load-tested as a developer-grade API. This page lists the measured numbers, the methodology behind them, and how we keep them honest.
All figures below come from k6 sustained load tests against production (
quizbase.runriva.com) on 2026-05-30. We re-run baseline + spike scenarios quarterly and after significant changes — this run lands lower p95 across the board than the previous one. Latest raw output: k6-baseline-2026-05-30.json.
Service Level Objectives#
Measured over a full 5-minute sustained run at 50 RPS (15,002 requests, 0.01% errors). Figures are client-side, measured over the public internet — your latency depends on your distance to the EU region.
| Metric | Target | Measured (2026-05-30) |
|---|---|---|
| p95, discovery endpoints (categories/stats/tags…) | < 200ms | ~45ms |
| p95, questions browse + PK lookup | < 500ms | ~90ms |
| p95, random (randomized sampling, heaviest) | < 800ms | ~265ms |
| Error rate (sustained 50 RPS) | < 1% | 0.01% (2 / 15,002) |
| Sustained throughput | 50 RPS | 48.7 RPS (15,002 requests) |
| Uptime | 99.9% | Live status |
x-performance extension on every operation in the OpenAPI spec reports the same numbers per-endpoint — machine-readable for your monitoring or SDK telemetry.
Per-endpoint performance#
Sustained 50 RPS for 5 minutes (60% public discovery / 30% browse / 10% random + PK lookup), 15,002 requests measured. Mix matches realistic API usage — most clients are discovery/browse, fewer random pulls, fewest PK lookups.
| Endpoint | p50 | p90 | p95 | Sustained RPS |
|---|---|---|---|---|
| GET /api/v1/categories | 22ms | 29ms | 43ms | 50 |
| GET /api/v1/stats | 23ms | 28ms | 41ms | 50 |
| GET /api/v1/topics | 22ms | 28ms | 46ms | 50 |
| GET /api/v1/tags | 22ms | 28ms | 45ms | 50 |
| GET /api/v1/subcategories | 22ms | 28ms | 40ms | 50 |
| GET /api/v1/questions | 57ms | 69ms | 91ms | 50 |
| GET /api/v1/questions/random (broad) | 211ms | 245ms | 265ms | 50 |
| GET /api/v1/questions/:id | 53ms | 65ms | 82ms | 50 |
Numbers are client-side over the public internet (TCP + TLS + network RTT included), so they are an upper bound on what the API itself spends. /v1/me, /v1/usage, /v1/languages, /v1/topics/:slug, /v1/report are not part of the load mix (small payloads, low-traffic endpoints) but stay within the same SLO tiers.
Spike & burst#
Spike (50→100→50 RPS) and burst (50→200→50 RPS) scenarios run as part of the periodic suite to verify graceful degradation under traffic bursts — error rate stays at 0% across the baseline range. Each baseline run ships with its spike/burst summaries alongside the raw data.
Methodology#
- Tool: k6 v1.7.1 (Grafana),
constant-arrival-rateandramping-arrival-rateexecutors. - Scenarios: baseline (50 RPS / 5 min), spike (50→100→50 RPS), burst (50→200→50 RPS), narrow filter (30 RPS / 3 min).
- Mix: 60% public discovery / 30% browse / 10% random + PK lookup — weighted to realistic API usage.
- Target: production (
quizbase.runriva.com), EU region. Rate limiter bypassed with an internal benchmarking key so the numbers reflect end-to-end request handling rather than the limiter. Measured client-side over the public internet.
Quarterly drift mechanism#
We re-run pnpm load:baseline + pnpm load:spike quarterly. Each run produces a fresh k6-baseline-DATE-summary.json and updates the per-endpoint x-performance block in openapi.json. If p95 regresses by more than 1.5× vs the previous run we investigate before marking the new run as the baseline (deployment, dependency upgrade, data growth, query plan change).
The lastMeasured field on every x-performance block is the date of the last successful baseline.
Raw data#
For skeptics and benchmarkers: k6-baseline-2026-05-27.json — full k6 summary metrics (p50/p90/p95/p99 per endpoint, throughput, request counts). Aggregates only.
See also#
- API Reference — interactive Scalar render of the spec.
- /openapi.json — OpenAPI 3.1,
x-performanceper operation. - Errors and retries — what
429looks like and how to back off. - Rate limits in practice — pacing strategies that keep you under 429.