<aside> ⚔️
This page is the how-to. The track overview told you what Obsidio is; this page covers the siege rules, the box you deploy into, the exact API you build, how it is graded, and how points are scored. Read it fully before you write code. The constraints here are the meat of the whole game.
</aside>
Build a small analytics API, containerise it to our spec, and keep it fast and correct while we flood it with heavy traffic on a box that's identical for every team.
We measure your backend under heavy traffic to see how well it holds up. This is the same load testing real engineering teams run against their own systems before shipping, using a standard tool called k6. Two ground rules keep it fair: every team runs in the same resource-capped box, and we publish the exact load and thresholds in advance. There's no hidden test; you optimise against the same script we grade with.
Your backend runs in Docker, capped identically for all teams.
Dockerfile. It builds your app in any language and framework. The inside of the container is yours.8080. We build your image and run it ourselves at grading time.latest tags. If it builds differently on our machine, that's on the submission.⚠️ The core-count gotcha. Your container is CPU-throttled but can still see all of the host's cores. Some runtimes size their worker or thread pools from the visible core count (Go's
GOMAXPROCS, the JVM, Node's cluster module, Uvicorn and Gunicorn workers). If yours does, it may spawn workers for cores it can't use and crater your performance. Set your worker count explicitly to match the 2-core cap.
New to Docker? Every starter in the code bundle already builds and runs, so you clear that hurdle for free. See the [Docker guide] for a walkthrough.
You're building a small service of the kind a trading or analytics firm runs internally. It answers three kinds of request, cheap to expensive, plus a health check. The mix of light and heavy work under load is the whole problem: a naive build lets the expensive requests clog the pipe so even the cheap ones crawl. A good build keeps the fast path fast no matter what the slow path is doing.
The work each endpoint does is specified exactly, so every team does identical work. You don't get to make the heavy endpoint cheaper; you only get to serve the same work more efficiently. Build all four endpoints exactly as below; every response is JSON.
| Endpoint | Weight | Does | Returns (200) | Errors |
|---|---|---|---|---|
GET /health |
not scored | Liveness check | {"status":"ok"} |
— |
GET /price?symbol=SYM |
1 (cheap) | In-memory price lookup, near instant | {"symbol":"AAPL","price":187.42} |
404 {"error":"unknown symbol"} |
GET /stats?symbol=SYM |
3 (medium) | Compute mean, min, max, stddev over the symbol's 500-point series, every request | {"symbol":"AAPL","mean":187.4,"min":183.6,"max":191.2,"stddev":2.1} |
404 {"error":"unknown symbol"} |
GET /risk?seed=VALUE |
10 (heavy) | Apply SHA-256 to seed, hex-encode, feed back in, repeat 50,000 times |
{"seed":"0.48","risk_hash":"<final digest>"} |
— |
The valid symbols and 500-point series are in the starter code. Because seed differs every request, /risk can't be cached or precomputed; because it's deterministic, we can verify your digest (a fake or constant value fails and scores zero).
Per team, we docker build and docker run your container capped at 2 CPU / 2 GB, confirm /health returns 200, run our k6 script against it, and record your score.