Skip to content

Todo API pilot: Go, FastAPI and Express as todo backends - #2

Merged
rshdhere merged 4 commits into
mainfrom
feat/todo-api-pilot
Oct 4, 2026
Merged

rshdhere merged 4 commits into
mainfrom
feat/todo-api-pilot

Conversation

@rshdhere

@rshdhere rshdhere commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Why

Every load-test request used to be a health check, so traces were one span named GET /api/v1/health, logs said nothing, and per-route views had nothing to show. This turns the servers into small todo backends, starting with a pilot of three (one per language family), so traces, logs and dashboards tell a story you can reason about.

What changes

  • Contract (servers/openapi.json): list (paging, done filter), create, get, update, delete. 400 for bad bodies or queries, 404 for missing or non-integer ids, {"error": ...} bodies, trimmed 1-200 character titles, a 1000-todo cap that drops the oldest. In memory, per server.
  • Conformance test (servers/conformance.py, stdlib only): exercises every rule; all three pilot servers pass. CI runs it against every server image that serves the todo API, so the other ten get checked automatically as they are ported.
  • Pilot servers: Go net/http, FastAPI, Express. Each logs rejected requests as one JSON line. FastAPI now runs one process (was 2), so every request sees the same todos; its numbers will drop accordingly.
  • k6 workload (bench/k6/src/workload.ts): 35% list, 25% read, 15% create, 12% update, 5% delete, 3% filtered list, 3% invalid create (400), 2% missing read (404). Expected 4xx count as success, so error rates show only real failures. Requests are named by route and k6's url tag is dropped (no series per todo id). Servers without the todo API automatically get health checks.
  • Dashboards: per-route table (rate, 4xx, 5xx, p50, p99 inside the server) and p99 per route and per operation on every server dashboard; per-route table on Inside the Servers; per-operation matrix on Head to Head. Beyla groups ids under /api/v1/todos/{id}.
  • Safety nets: an unquoted {id} in Beyla's YAML made it crash-loop locally, and nothing caught it. Now CI parses the generated beyla.yaml, and deploy.sh fails unless Beyla is running and scraped.

Tested locally (full stack on Docker Engine)

  • Conformance: all checks pass for Go, FastAPI and Express, both as local processes and as containers; the CI step falls back to a health check for Fiber (not ported yet).
  • A race of the three through the runner: Beyla split every request by route and status; zero real k6 failures. Server-side p99 per route: Go 0.65-0.78 ms, Express 1.3-1.5 ms, FastAPI 1.9-2.9 ms (FastAPI's list route slowest).
  • Dashboards render with the new per-route panels, route-named traces and the servers' rejected-request logs.

Next

Port the other ten servers the same way once this looks right in production; CI's conformance step enforces the contract on each.

…ss servers

- Specify list, create, get, update and delete todos in servers/openapi.json: 400/404 rules, trimmed 1-200 character titles, RFC 3339 timestamps and a 1000-todo cap that drops the oldest
- Add servers/conformance.py, a standard-library checker that exercises every rule of the contract against a running server
- Implement the API in Go (servers/go/todos.go, concurrency-safe store), FastAPI (hand-parsed bodies so errors are 400, not 422) and Express
- Log every rejected request as one JSON line on all three
- Run FastAPI as a single uvicorn process so every request sees the same in-memory todos, and update its description in infra/servers.json
- Add bench/k6/src/workload.ts: weighted list, read, create, update, delete and filtered-list requests plus deliberate 400s and 404s
- Mark each request's expected statuses so deliberate 4xx and 404s for evicted todos do not count as failures
- Name requests by route and drop k6's url system tag so todo ids do not create a series each
- Probe each target in setup() and fall back to health checks for servers without the todo API
- Use the workload in load.ts, break.ts and match.ts, and describe it on the runner's pages and in bench/README.md
- Group Beyla requests under /api/v1/todos and /api/v1/todos/{id}, written as a quoted JSON array because YAML reads an unquoted {id} as a map and Beyla then crash-loops
- Add a per-route table (rate, 4xx, 5xx, p50, p99 inside the server) and p99 per route and per k6 operation to every server dashboard
- Add a per-route, per-server table to Inside the Servers
- Add a per-operation p99 matrix of the racers to Head to Head
…tch a broken Beyla config

- Load each server image after building it, start it, and run servers/conformance.py when it serves the todo API (health check otherwise)
- Parse the generated infra/beyla/beyla.yaml as YAML in the checks job, since Beyla has no config check of its own
- Fail infra/deploy.sh unless Beyla is running and scraped 15 seconds after the stack is up
- Note in infra/README.md that a host without eBPF support now fails the deploy's Beyla check
@rshdhere
rshdhere merged commit abb57dd into main Oct 4, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant