o

otto

a048a0flibrary

No description provided.

0 stars0 forksUnknownupdated 2 months ago
Open repo

otto

An entry for Rinha de Backend 2026, written in Odin. The challenge: a fraud-detection API that turns each transaction into a 14-dimensional vector and answers with an exact 5-nearest-neighbour vote over a fixed set of 3,000,000 labelled reference vectors — all within 1.0 CPU / 350 MB total, on a Mac Mini Late 2014 (Intel Haswell).

POST /fraud-score  →  vectorize → quantize → exact 5-NN → {"approved", "fraud_score"}
GET  /ready        →  200 once warm

Approach

The whole thing is built around two ideas: precompute everything that never changes, and keep the per-request hot path branch-light and SIMD-friendly.

  • Vectorization (src/vector.odin) follows DETECTION_RULES.md exactly, including the -1 sentinels for a missing previous transaction. Every value is quantized ×10000 into i16. The reference dataset ships with 4-decimal precision, so integer math at this scale is lossless and reproduces the exact distance ordering used to generate the truth labels.
  • The index (src/model.odin) is a single balanced KD-tree with a 16-dim bounding box per node, built offline by tools/preprocess and serialized to a flat, mmap-friendly file (~126 MB) that is baked into the image. Splits are taken on the widest dimension, so the large-range binary dims (is_online, card_present, unknown_merchant, history sentinels) are split first — naturally separating the data the way an explicit bucket partition would.
  • Search (src/classifier.odin) is exact 5-NN: a nearer-child-first descent that prunes any subtree whose box lower bound can't beat the current 5th-nearest. Distances are squared Euclidean over i16 lanes accumulated in i64, written to auto-vectorize under -microarch:haswell.
  • HTTP (src/server.odin) is a hand-rolled single-threaded epoll server (HTTP/1.1 keepalive). The JSON body is parsed in place by a fixed-schema parser (src/payload.odin) — no allocation on the hot path. There are only 6 possible answers (fraud count 0–5), so the full HTTP responses are precomputed at startup and the handler just writes the right one.
  • Deployment: nginx round-robin in front of two identical Odin processes, each pinned to a fraction of the CPU (see docker-compose.yml).

Results

Metric Value
Detection vs. contest truth (54,100 entries) 0 FP, 0 FN, 0 errors → score_det = 3000 (max)
Search latency (microbench, pessimistic random queries) mean ~170 µs, p99 ~710 µs (single thread)
Model size ~126 MB (fits the 155 MB per-instance budget)
Per-instance RSS ~127 MB

In-distribution traffic prunes far better than the random-query microbench, so real latency is lower. Detection is exact, so the score is bounded only by p99.

Layout

src/            library package `otto` (logic + tests)
  vector.odin     14-dim vectorization, quantization, MCC/normalization constants
  timeparse.odin  ISO-8601 → epoch / hour / day-of-week
  payload.odin    fixed-schema, allocation-free JSON request parser
  model.odin      KD-tree build + binary model format + reference-JSON parser
  classifier.odin exact 5-NN search + brute-force oracle (for tests)
  server.odin     epoll HTTP/1.1 server, precomputed responses
  *_test.odin     unit + property tests
cmd/server/     the API executable (imports `otto`)
tools/
  preprocess/     references.json → model.bin (build time)
  bench/          search-latency microbenchmark
  verify/         accuracy gate vs test-data.json
Dockerfile, docker-compose.yml, nginx.conf
.github/workflows/  ci.yml (test+build), image.yml (build+push GHCR)

Develop

Requires the Odin compiler (pacman -S odin on Arch/Manjaro, or nix-shell).

make test         # unit + property tests (search == brute-force oracle)
make data         # fetch references.json.gz (from the local harness or upstream)
make verify       # build the model, check 0 FP/FN against the contest truth
make bench        # search-latency microbenchmark
make up           # build + run the full stack (nginx + 2 api) via docker compose
make k6           # run the contest k6 load test against :9999
make bench-vm     # run the whole load test inside an isolated banger microVM

The reference dataset (resources/references.json.gz, ~50 MB) is not vendored; make data fetches it. The upstream contest repo lives in rinha-de-backend-2026/ (git-ignored) and is used as the local benchmark harness.

make bench-vm builds and load-tests the stack inside a throwaway banger Firecracker microVM (2 vCPU / 8 GB, mimicking the grading Mac Mini) so nothing touches host Docker, ports, or network state, and tears it down afterwards (KEEP=1 keeps it). Measured there: p99 1.93 ms, 0 FP / 0 FN / 0 errors, final_score 5715 / 6000 under the enforced 1.0 CPU / 350 MB budget.

Submission

Source lives on main. The submission branch carries only docker-compose.yml, nginx.conf, and info.json at the root, referencing the public GHCR image built by the image workflow. The load balancer listens on 9999.

Roadmap

Detection is already maxed; future work targets p99 only: per-query thread fan-out to cut head-of-line blocking, a hand-tuned SIMD distance kernel if auto-vectorization underperforms, and a zero-copy SCM_RIGHTS load balancer in place of nginx if the LB ever shows up in the budget.

Package Info
Version
a048a0f
License
Unknown
Author
@thales-maciel
Type
library
Forks
0
Created
2 months ago
Updated
2 months ago
Health
maintained
has releases
has readme
has license
Activity
last 56 weeks