为何在Django中异步调用数据库后性能未获提升?
Hey there, let's break down why your async Django setup isn't delivering the QPS boost you expected, and walk through actionable fixes to get it working right.
Key Issues Holding Back Performance
1. Your aiopg Connection Pool Is Misconfigured
Looking at your code:
pool = aiopg.create_pool(dsn)
aiopg.create_pool() is an async function—assigning it directly to pool gives you a coroutine object, not an actual connection pool. Unless you've secretly awaited this elsewhere (which isn't shown in your snippet), every query would be creating a new database connection from scratch. This adds massive overhead and completely negates any async performance gains.
2. Synchronous Code Is Blocking the Event Loop
Even if your database query runs asynchronously, you're doing critical synchronous work in your async handler that blocks the single-threaded event loop:
- Creating
Modelinstances via a list comprehension - Using
ModelSerializer(a fully synchronous component)
Any sync code in your async handler will pause the entire event loop until it finishes. This means your "async" setup is effectively behaving like a synchronous one—no wonder QPS hasn't changed.
3. Gunicorn + Uvloop Configuration Might Be Incorrect
Running an ASGI app with Gunicorn requires an async-compatible worker class. If you're using the default sync worker, your async code is just running in a sync context, wasting all the potential of async I/O.
4. Your Bottleneck Might Not Be Database Queries
If your original synchronous app's slowdown wasn't caused by database I/O (e.g., it was CPU-bound, or waiting on other sync services), converting just DB queries to async won't help. You need to profile to find where time is actually being spent.
Fixes to Try
A. Properly Initialize the aiopg Connection Pool
For a Channels-based ASGI app, initialize the pool during app startup to ensure it's ready for async queries:
import aiopg from channels.routing import ProtocolTypeRouter from django.core.asgi import get_asgi_application dsn = 'dbname=db user=root password=password123 host=postgres-container port=5432' pool = None async def setup_db_pool(): global pool pool = await aiopg.create_pool(dsn) async def application(scope, receive, send): if scope['type'] == 'lifespan': while True: message = await receive() if message['type'] == 'lifespan.startup': await setup_db_pool() await send({'type': 'lifespan.startup.complete'}) elif message['type'] == 'lifespan.shutdown': await pool.close() await send({'type': 'lifespan.shutdown.complete'}) return else: router = ProtocolTypeRouter({ "http": get_asgi_application(), # Add your other protocol routes here }) await router(scope, receive, send)
B. Offload Synchronous Work to a Thread Pool
Use asyncio.to_thread() to run sync operations (like serialization and model instantiation) in a separate thread, so they don't block the event loop:
import asyncio async def list(self, body): query = Model.objects.all().order_by("title") ares = await async_query(str(query.query)) # Move sync work to a thread pool def process_results(): results = [Model(**{ "id": i[0], "title": i[1], "created": i[2].replace(tzinfo=None), "updated": i[3].replace(tzinfo=None) }) for i in ares] return ModelSerializer(results, many=True).data serialized_data = await asyncio.to_thread(process_results) return {"data": serialized_data}
C. Configure Gunicorn for Async Correctly
Use an async worker class optimized for uvloop, and tune worker counts for your workload:
gunicorn --workers=4 --worker-class=uvloop --worker-connections=1000 --bind=0.0.0.0:8000 your_project.asgi:application
If the uvloop worker doesn't play nicely, try aiohttp.GunicornWebWorker instead—it's designed specifically for ASGI apps.
D. Ditch the DIY Async ORM Hack
Manually converting ORM queries to SQL and mapping results back to models is error-prone and misses ORM optimizations. Consider using a purpose-built async ORM:
django-async-orm(experimental but Django-native)sqlalchemy.ext.asyncio(mature, though not tightly integrated with Django)
E. Profile to Find Hidden Bottlenecks
Use tools like py-spy or the async-compatible version of django-debug-toolbar to trace where your requests are spending time. This will help you identify any unexpected sync blocks or delays you haven't considered.
内容的提问来源于stack exchange,提问作者alexyichu

