Skip to content

Django 6.0.7 (gunicorn + psycopg 3) ​

Benchmark · Python · July 2026

Part of the PostgreSQL REST API Benchmark, July 2026 series.

At a glance ​

VersionDjango 6.0.7 on Python 3.14, gunicorn 26 with uvicorn workers (ASGI)
PostgreSQL driverpsycopg 3.3.4 with psycopg_pool — 8 workers × 12 connections ≈ 100 aggregate
Concurrency modelSynchronous views, one gunicorn worker per core (-w $(nproc))
Lines of code183
Podiumsnone — 0 of 38 combinations
Sourcedjango-app-v6.0.7

The verdict in one sentence: you don't pick Django for req/s — but measured on equal terms it is no longer an outlier, and in the payload-heavy combinations it is genuinely good.

Implementation ​

Plain synchronous views with raw cursors (views.py) — no ORM models, no middleware (MIDDLEWARE = []), a single installed app. The fairness-relevant change lives in settings.py:

python
python
'OPTIONS': {
    # Pool is per gunicorn worker; workers x max_size ~= 100 aggregate
    # to match the single-pool services (psycopg_pool default max is only 4).
    'pool': {'min_size': 2, 'max_size': int(os.getenv('POOL_MAX_SIZE', '12'))},
},
dockerfile
dockerfile
CMD gunicorn app.asgi:application -k uvicorn.workers.UvicornWorker \
    -w ${WORKERS:-$(nproc)} -b 0.0.0.0:8000 --log-level warning

In January, Django ran 4 workers on psycopg's default pool of 4 connections each. Note this is already a stripped-down Django — a production deployment with sessions, auth, and a middleware stack would sit below every number on this page.

Results ​

The floor ​

Wherever the request itself is the work, Django is last of 20 — at every concurrency level:

ScenarioResult
Minimal baseline2,601 req/s at 100 VU (#20) — the only service below 6,000; the leader (Go) is 6.5× ahead
Many parameters2,411 req/s at 50 VU (#20)
POST, 10 records2,492 req/s at 50 VU (#20)
perf-test, 1 record358 req/s at 1 VU (#20); 2,005 at 50 VU (#19), ahead of only PostgREST

The pattern is uniform: routing, request-object construction, and Python-side value conversion put a hard per-request ceiling of roughly 2,000–2,600 req/s on four cores, regardless of scenario.

The bright spots ​

Two results break the caricature. First, nested JSON: #4 of 20 in all six combinations — 1,963 / 1,900 / 1,869 req/s at 50 VU across depths 1–3 — behind only Go, Swoole, and FastAPI, and ahead of both Rust frameworks and every Node runtime. Its depth penalty (−4.8% from depth 1 to 3) is the smallest in the field, better even than Go's −6.4%: JsonResponse passes PostgreSQL's pre-serialized JSON through without reprocessing.

Second, the heavy perf-test combinations, where payload dwarfs per-request overhead:

perf-test50 VU100 VU200 VU
100 records174 (#4)173 (#4)171 (#4)
500 records37 (#4)36 (#4)36 (#4)

Again #4, behind only Swoole, Go, and FastAPI. Add 100-record POST (#6, 1,284 req/s at 50 VU) and 500 KB payloads (#5, 322–327 req/s) and a consistent picture emerges: when the database does the work, Django keeps pace with the top quartile.

Latency ​

The per-request ceiling shows up as the field's worst small-request tails: p99 of 82 ms on the minimal baseline at 100 VU, where the leaders sit at 21–23 ms, and 5 ms at 1 VU where the field posts 2–3 ms. In its strong combinations the picture inverts — 57 ms p99 on nested depth 1 at 50 VU is fourth-best, and its heavy perf-test tails track the leaders.

Resource usage ​

Peak memoryAvg memoryAvg CPU
671 MB281 MB229.6%

The highest average CPU of all 20 services — more than Go and Swoole combined and change — and the third-highest peak memory, though still well under the Node cluster deployments (Express 1,094 MB, Fastify 1,177 MB). Django works hard for its numbers.

Analysis ​

The point of including Django was never to watch it win; it was to measure what a full-featured, batteries-included framework actually costs when everything around it is fair. January's setup — 4 workers, 4-connection default pools — understated it; this round's 8 workers × 12 connections is a configuration Django's own documentation would endorse. On those terms, ~2,400 req/s on trivial endpoints is the honest price of the framework, and #4 in the field on payload-heavy work is the honest reward: Django's overhead is per-request, not per-byte.

The fair conclusion cuts both ways. If your API's job is high-frequency small requests, Django costs you roughly 5× the throughput of the mid-field at maximum CPU burn. If your API returns real result sets — reports, nested documents, wide rows — the gap to the leaders shrinks to 15–20%, and everything Django ships in the box comes essentially free.

January → July movement: still last or near-last in the server-bound categories, exactly as in January — the pool fix raised its ceiling, not its rank. The heavy-payload #4 placements are where equal pooling finally let it show something. Rankings only — the methodology changed too much for absolute comparisons.

Explore on GitHub ​

Everything this page claims can be checked against the running code and the raw output:


Series: Introduction · Overall Analysis · Raw Results

Next framework: Fastify →

Comments