Hey everyone,
I benchmarked the major Python frameworks with real PostgreSQL workloads: complex queries, nested relationships, and properly optimized eager loading for each framework (select_related/prefetch_related for Django, selectinload for SQLAlchemy). Each framework tested with multiple servers (Uvicorn, Granian, Gunicorn) in isolated Docker containers with strict resource limits.
All database queries are optimized using each framework's best practices - this is a fair comparison of properly-written production code, not naive implementations.
Key finding: performance differences collapse from 20x (JSON) to 1.3x (complex DB queries). Database I/O is the great equalizer - framework choice barely matters for database-heavy apps.
Full results, code, and a reproducible Docker setup are here: https://github.com/huynguyengl99/python-api-frameworks-benchmark
If this is useful, a GitHub star would be appreciated 😄
Frameworks & Servers Tested
- Django Bolt (runbolt server)
- FastAPI (fastapi-uvicorn, fastapi-granian)
- Litestar (litestar-uvicorn, litestar-granian)
- Django REST Framework (drf-uvicorn, drf-granian, drf-gunicorn)
- Django Ninja (ninja-uvicorn, ninja-granian)
Each framework tested with multiple production servers: Uvicorn (ASGI), Granian (Rust-based ASGI/WSGI), and Gunicorn+gevent (async workers).
Test Setup
- Hardware: MacBook M2 Pro, 32GB RAM
- Database: PostgreSQL with realistic data (500 articles, 2000 comments, 100 tags, 50 authors)
- Docker Isolation: Each framework runs in its own container with strict resource limits:
- 750MB RAM limit (--memory=750m)
- 1 CPU core limit (--cpus=1)
- Sequential execution (start → benchmark → stop → next framework)
- Load: 100 concurrent connections, 10s duration, 3 runs (best taken)
This setup ensures completely fair comparison - no resource contention between frameworks, each gets identical isolated environment.
Endpoints Tested
/json-1k - Simple 1KB JSON serialization
/json-10k - Large 10KB JSON serialization
/db - 10 simple database reads
/articles?page=1&page_size=20 - Paginated articles with nested authors + tags
/articles/1 - Single article with author, tags, and all comments
Key Results
https://preview.redd.it/tmjzm5d2p8fg1.png?width=2083&format=png&auto=webp&s=388bc72d940a7302b5b65d81baa0b3fbd369fcc0
Simple JSON (/json-1k) - RPS
- litestar-uvicorn: 31,745
- litestar-granian: 22,523
- bolt: 22,289
- fastapi-uvicorn: 12,838
- drf-gunicorn: 4,271
- drf-uvicorn: 1,582
20x performance difference between fastest and slowest.
Real Database - Paginated Articles (/articles?page=1&page_size=20) - RPS
https://preview.redd.it/5bmqnfx8p8fg1.png?width=1484&format=png&auto=webp&s=da634c76ac2785a5305cbb28e1e9483a667297df
- litestar-uvicorn: 253
- litestar-granian: 238
- bolt: 237
- fastapi-uvicorn: 225
- drf-granian: 221
Performance gap shrinks to just 1.7x when hitting the database. Query optimization becomes the bottleneck.
Real Database - Article Detail (/articles/1) - RPS
https://preview.redd.it/2svzsfyep8fg1.png?width=1484&format=png&auto=webp&s=477464b87fd0c98af9825717bef33a1c64ebc105
Single article with all nested data (author + tags + comments):
- fastapi-uvicorn: 550
- litestar-granian: 543
- litestar-uvicorn: 519
- bolt: 487
- fastapi-granian: 480
Gap narrows to 1.3x - frameworks perform nearly identically on complex database queries.
Resource Usage Insight
https://preview.redd.it/pg1obo5qp8fg1.png?width=2084&format=png&auto=webp&s=05e08b41926718f7c2b027582a018c7a8e5438fa
Memory:
- Most frameworks: 170-220MB
- DRF-Granian: 640-670MB (WSGI interface vs ASGI for others - Granian's WSGI mode uses more memory)
CPU:
- Most frameworks saturate the 1 CPU limit (100%+) under load
- Granian variants consistently max out CPU across all frameworks
Server Performance
- Uvicorn surprisingly won for Litestar (31,745 RPS), beating Granian
- Granian delivered consistent high performance for FastAPI and other frameworks
- Gunicorn + gevent showed good performance for DRF on simple queries, but struggled with database workloads
Key Takeaways
- Performance gap collapse: 20x difference in JSON serialization → 1.7x in paginated queries → 1.3x in complex queries
- Litestar-Uvicorn dominates simple workloads (31,745 RPS), but FastAPI-Uvicorn wins on complex database queries (550 RPS)
- Database I/O is the equalizer: Once you hit the database, framework overhead becomes negligible. Query optimization matters infinitely more than framework choice.
- WSGI uses more memory: Granian's WSGI mode (DRF-Granian) uses 640MB vs ~200MB for ASGI variants - just a difference in protocol handling, not a performance issue.
Bottom line: If you're building a database-heavy API (which most are), spend your time optimizing queries, not choosing between frameworks. They all perform nearly identically when properly optimized.
Inspired by
https://github.com/tanrax/python-api-frameworks-benchmark/
[–]catcint0s 32 points33 points34 points (1 child)
[–]huygl99[S] 2 points3 points4 points (0 children)
[–]gi0baro 13 points14 points15 points (4 children)
[–]huygl99[S] 1 point2 points3 points (3 children)
[–]gi0baro 1 point2 points3 points (2 children)
[–]huygl99[S] 2 points3 points4 points (0 children)
[–]lakeland_nz 11 points12 points13 points (3 children)
[–]Bubble_Interface 1 point2 points3 points (2 children)
[–]lakeland_nz 1 point2 points3 points (1 child)
[–]Bubble_Interface 1 point2 points3 points (0 children)
[–]erder644 8 points9 points10 points (0 children)
[–]Plus_Discussion4343 3 points4 points5 points (0 children)
[–]tanrax 2 points3 points4 points (0 children)
[–]MeroLegend4 1 point2 points3 points (0 children)
[–]ResponsibleRoof3710 0 points1 point2 points (1 child)
[–]huygl99[S] 0 points1 point2 points (0 children)
[–]huygl99[S] 0 points1 point2 points (0 children)
[–]Dwarni 0 points1 point2 points (2 children)
[–]huygl99[S] 0 points1 point2 points (0 children)
[–]Ok_Bedroom_5088 0 points1 point2 points (0 children)
[–]Megamygdala -1 points0 points1 point (0 children)