Hurricane Performance Benchmark Suite
To measure real-world concurrency, latency distributions, and container scaling characteristics under synthetic load, we created the Django Hurricane Benchmark Suite on GitHub.
The suite evaluates django-hurricane (targeting version 2.0.0rc1 and newer releases powered by Granian's Rust runtime) against PostgreSQL 17, upstream microservices, complex template rendering, and session authentication.
Architecture Overview
The benchmark infrastructure uses Docker Compose to orchestrate isolated container environments driven by headless Locust step-load testing:
The 6 Evaluated Deployment Paradigms
The suite compares six distinct deployment paradigms across Hurricane's runtime engines:
- WSGI (Worker=1 / Single Thread): Hurricane WSGI mode with
--workers 1(strict single-threaded baseline for 1-to-1 comparison with single-process event loop). - WSGI (Auto Threads / Multi-Thread): Hurricane WSGI mode with auto-threads calculation
min(32, cpu_count + 4)threads (traditional production multi-threaded WSGI deployment). - ASGI (Forced Sync Views): Hurricane ASGI mode running standard synchronous Django views, demonstrating the exact penalty of Django's
sync_to_async(thread_sensitive=True)adapter under load. - ASGI (Clean Async / Single Thread): Hurricane ASGI mode with clean async views (
async def), pooledhttpxasync HTTP client, and--workers 1. - ASGI (Auto Threads / Clean Async): Hurricane ASGI mode with clean async views and Hurricane's auto-threads calculation
min(32, cpu_count + 4)threads for non-blocking threadpool dispatch and template rendering. - ASGI +
django-async-backend: Hurricane ASGI mode paired with django-async-backend utilizing true non-blockingpsycopg 3async connection pooling viaProduct.async_objectson the Tokio event loop.
Workload Routes
The test application exercises seven realistic traffic paths reflecting modern production architectures:
| Route | Feature Area | Concurrency Characteristic |
|---|---|---|
POST /api/auth/login | Session & Auth Middleware | Cookie signing, session read/write, user credentials validation. |
GET /api/auth/me | Session Verification | Native async session verification (auser under ASGI). |
GET /catalog/render | Complex Template Rendering | Django Template Language (DTL) overhead, nested loops, filters, context processors. |
GET /api/items & POST /api/items | JSON API Serialization | JSON parsing, model validation, and bulk ORM queries. |
GET /external/fetch | Unreliable Upstream API | Interacts with a mock upstream API (fake-api with randomized latency) simulating third-party network delays. |
GET /catalog/search | Database Queries & Aggregations | Filtering, foreign key joins (select_related), and statistical aggregations on PostgreSQL 17. |
GET /api/gateway/aggregate | Scatter-Gather Microservices API Gateway | Fan-out to 3 microservices: sequential blocking in WSGI vs asyncio.gather() parallel sockets in ASGI. |
Aggregate Performance Metrics (Mixed Workload)
The mixed workload simulates simultaneous user activity across all 7 routes with user concurrency ramping up to 100 concurrent users:
| Flavor | Server Runtime | Throughput | Median (p50) | 95th (p95) | 99th (p99) | Error Rate | Total Requests |
|---|---|---|---|---|---|---|---|
| WSGI (Worker=Auto / Multi-Thread) | Hurricane WSGI | 147.3 req/s | 230.0 ms | 510.0 ms | 570.0 ms | 0.0% | 2,217 |
| ASGI (Forced Sync Views) | Hurricane ASGI | 71.5 req/s | 280.0 ms | 1900.0 ms | 2000.0 ms | 0.0% | 1,073 |
| ASGI (Worker=Auto / Clean Async) | Hurricane ASGI | 60.6 req/s | 300.0 ms | 2300.0 ms | 2600.0 ms | 0.0% | 910 |
| ASGI (Worker=1 / Clean Async) | Hurricane ASGI | 60.1 req/s | 350.0 ms | 2200.0 ms | 2500.0 ms | 0.0% | 901 |
| ASGI + django-async-backend | Hurricane ASGI | 59.8 req/s | 720.0 ms | 1800.0 ms | 2700.0 ms | 0.0% | 898 |
| WSGI (Worker=1 / Single Thread) | Hurricane WSGI | 29.1 req/s | 1300.0 ms | 3000.0 ms | 3100.0 ms | 0.0% | 435 |
Throughput (Requests per Second - Higher is better)
WSGI (Worker=Auto / Multi-Thread) [████████████████████████████████] 147.3 req/s
ASGI (Forced Sync Views) [████████████████ ] 71.5 req/s
ASGI (Worker=Auto / Clean Async) [█████████████ ] 60.6 req/s
ASGI (Worker=1 / Clean Async) [█████████████ ] 60.1 req/s
ASGI + django-async-backend [█████████████ ] 59.8 req/s
WSGI (Worker=1 / Single Thread) [██████ ] 29.1 req/s
95th Percentile Latency (Lower is better)
WSGI (Worker=Auto / Multi-Thread) [█████ ] 510.0 ms
ASGI + django-async-backend [███████████████████ ] 1800.0 ms
ASGI (Forced Sync Views) [████████████████████ ] 1900.0 ms
ASGI (Worker=1 / Clean Async) [███████████████████████ ] 2200.0 ms
ASGI (Worker=Auto / Clean Async) [█████████████████████████ ] 2300.0 ms
WSGI (Worker=1 / Single Thread) [████████████████████████████████] 3000.0 ms
Concurrency Scaling Penalty
As concurrent simulated users ramp up from light load (12 users) through mid load (50 users) to peak concurrency (100 users), response times scale differently across deployment flavors:
| Flavor | Initial Load (~12 Users) p50 / p95 | Mid Load (~50 Users) p50 / p95 | Peak Load (100 Users) p50 / p95 | Latency Scaling Penalty |
|---|---|---|---|---|
| WSGI (Worker=Auto / Multi-Thread) | 60.0 ms / 130.0 ms | 73.0 ms / 240.0 ms | 207.5 ms / 465.0 ms | 3.58x |
| WSGI (Worker=1 / Single Thread) | 240.0 ms / 510.0 ms | 880.0 ms / 1200.0 ms | 1250.0 ms / 2650.0 ms | 5.20x |
| ASGI (Forced Sync Views) | 110.0 ms / 240.0 ms | 180.0 ms / 420.0 ms | 267.5 ms / 1600.0 ms | 6.67x |
| ASGI (Worker=1 / Clean Async) | 99.0 ms / 260.0 ms | 240.0 ms / 530.0 ms | 330.0 ms / 1900.0 ms | 7.31x |
| ASGI + django-async-backend | 92.0 ms / 200.0 ms | 210.0 ms / 770.0 ms | 667.5 ms / 1575.0 ms | 7.88x |
| ASGI (Worker=Auto / Clean Async) | 120.0 ms / 230.0 ms | 230.0 ms / 510.0 ms | 290.0 ms / 1925.0 ms | 8.37x |
Isolated Workload Performance
When endpoints are benchmarked in isolation without cross-traffic contention, the architectural strengths of each paradigm become distinct:
API Gateway / BFF (Scatter-Gather Fan-Out)
| Flavor | Throughput | Median (p50) | 95th (p95) | Error Rate |
|---|---|---|---|---|
| WSGI (Worker=Auto / Multi-Thread) | 100.8 req/s | 410.0 ms | 840.0 ms | 0.0% |
| ASGI (Forced Sync Views) | 87.1 req/s | 390.0 ms | 1100.0 ms | 0.0% |
| ASGI (Worker=Auto / Clean Async) | 47.1 req/s | 770.0 ms | 2300.0 ms | 0.0% |
| ASGI (Worker=1 / Clean Async) | 46.7 req/s | 730.0 ms | 2600.0 ms | 0.0% |
| ASGI + django-async-backend | 39.4 req/s | 950.0 ms | 2900.0 ms | 0.0% |
| WSGI (Worker=1 / Single Thread) | 12.5 req/s | 2800.0 ms | 5600.0 ms | 0.0% |
External Fetch (Upstream API I/O)
| Flavor | Throughput | Median (p50) | 95th (p95) | Error Rate |
|---|---|---|---|---|
| WSGI (Worker=Auto / Multi-Thread) | 175.6 req/s | 150.0 ms | 360.0 ms | 0.0% |
| ASGI (Forced Sync Views) | 107.4 req/s | 330.0 ms | 830.0 ms | 0.0% |
| ASGI + django-async-backend | 74.6 req/s | 570.0 ms | 1500.0 ms | 0.0% |
| ASGI (Worker=1 / Clean Async) | 73.1 req/s | 500.0 ms | 1700.0 ms | 0.0% |
| ASGI (Worker=Auto / Clean Async) | 72.1 req/s | 480.0 ms | 1500.0 ms | 0.0% |
| WSGI (Worker=1 / Single Thread) | 22.9 req/s | 1600.0 ms | 3500.0 ms | 0.0% |
Catalog Search (PostgreSQL Joins & Aggregates)
| Flavor | Throughput | Median (p50) | 95th (p95) | Error Rate |
|---|---|---|---|---|
| WSGI (Worker=Auto / Multi-Thread) | 153.1 req/s | 200.0 ms | 500.0 ms | 0.0% |
| ASGI + django-async-backend | 90.2 req/s | 520.0 ms | 880.0 ms | 0.0% |
| WSGI (Worker=1 / Single Thread) | 89.0 req/s | 520.0 ms | 990.0 ms | 0.0% |
| ASGI (Forced Sync Views) | 73.7 req/s | 630.0 ms | 1300.0 ms | 0.0% |
| ASGI (Worker=Auto / Clean Async) | 70.0 req/s | 670.0 ms | 1400.0 ms | 0.0% |
| ASGI (Worker=1 / Clean Async) | 69.3 req/s | 650.0 ms | 1400.0 ms | 0.0% |
Reproducing the Benchmarks Locally
Clone the repository and run the automated execution suite:
git clone https://github.com/django-hurricane/django-hurricane-bench.git
cd django-hurricane-bench
# Run full sequential benchmark across all 6 flavors:
./scripts/run_benchmark.sh
# Target a specific Hurricane version and concurrency parameters:
HURRICANE_VERSION=2.0.0rc6 BENCH_USERS=150 BENCH_DURATION=45s ./scripts/run_benchmark.sh
For continuous testing, the suite also runs in GitHub Actions via workflow_dispatch, automatically archiving reports under archive/ upon completion.
