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