Django Comparison
A natural question for anyone coming from Python: is the Kyrux admin fast enough in practice, or does it only look fast in isolated microbenchmarks? Instead of answering by intuition, we set up both sides — same machine, same Postgres, equivalent configuration — and measured throughput and CPU/memory usage.
A one-time measurement, on a single development machine, two runs per side. Treat it as a comparative order of magnitude, not a production benchmark with multiple statistical replicas.
Methodology
Both sides ran the same load: 200 seeded rows in an equivalent table, real session-based authentication (login via form, not a mock), 32 concurrent connections, client and server in separate processes (so the load client's own CPU doesn't get mixed into the server's measurement).
- Kyrux:
SERVER_WORKERS=4(GOMAXPROCS=4), real Postgres with a connection pool, admin with one model registered (admin.Register). - Django: 6.0 +
gunicorn --workers 4 --worker-class sync(matching Kyrux's number of "concurrent execution units"),CONN_MAX_AGE = None(persistent connection, so Django isn't unfairly penalized with a per-request reconnect — Django's own default),list_per_page = 20(matched to Kyrux'sdefaultPageSize— Django's own default is 100, which would give it an unfair edge in a per-page cost comparison). - Neither side was tuned beyond what's needed for a fair comparison (no extra index, no queryset caching, minimal model-registration config on both admins).
Throughput
| Route | Kyrux (req/s) | Django (req/s) | Kyrux advantage |
|---|---|---|---|
GET / (public page, no DB) |
~49,000–64,000 | ~4,100–5,900 | ~11x |
GET /admin/login/ |
~20,000–38,000 | ~670–850 | ~38x |
GET /admin/ (dashboard) |
~14,000–19,000 | ~330–370 | ~47x |
GET /admin/<model>/new/ (empty form) |
~13,000–17,000 | ~285–295 | ~52x |
GET /admin/<model>/<pk>/ (detail, 1 row) |
~6,100 | ~258–259 | ~24x |
GET /admin/<model>/ (list, 200 rows, real pagination) |
~2,300–3,600 | ~156 | ~19x |
Kyrux won on every route tested, in both runs, with margins from ~11x
(public route, no DB) to ~52x (empty admin form). The relatively slowest
route on the Kyrux side is the paginated list — not because of the
database (the query is a fast SELECT ... LIMIT 21), but because ~92% of
that handler's cost is html/template execution iterating the page's
~20 rows via reflection. Even so, it's still ~19x faster than Django
doing the same work.
Resource usage (CPU and memory)
Measured by isolating the server process from the load-client process (without that separation, the client's own memory/CPU would contaminate the server reading). Memory read via PSS, not raw RSS — important because gunicorn uses a pre-fork model (1 master + 4 workers): a good chunk of each worker's memory is shared via copy-on-write with the master, and summing raw RSS across all 5 processes would overstate actual consumption.
| Kyrux | Django (gunicorn) | |
|---|---|---|
| Server processes | 1 (single binary, goroutines) | 5 (1 master + 4 sync workers) |
| Idle memory (PSS) | ~82 MB | ~149 MB |
| Peak memory (PSS) under sustained load | ~149 MB | ~177 MB |
| CPU under load (range observed across runs) | 82%–161% (of 400% theoretical) | 85%–107% (of ~500% theoretical) |
Django starts out using nearly 2x more memory than Kyrux before serving a single request — a fixed cost of loading the Python interpreter, Django itself, and keeping 4 process copies alive at all times. Kyrux, being a single compiled binary with goroutines inside one process, pays that cost once. On the CPU side, even at its observed peak (161%) Kyrux stayed far from saturating its 400% theoretical ceiling — while delivering ~11x to ~52x more requests than Django in the same window.
Being honest about the numbers
This comparison has already been redone once on this same codebase, after two internal fixes (a benchmark-client bug that was undermeasuring Kyrux, and a memory-allocation tweak in the admin) — and Kyrux's advantage grew between the first and second run, not because Django got worse, but because Kyrux genuinely got faster. That's a lesson in itself: any framework comparison's "X times faster" reflects each side's state of optimization at the moment it was measured, not a constant of the language. Treat the numbers above for what they are — a measurement taken today, on one specific machine — not a permanent promise.