MySQL Connection Timeout Issues in Django ASGI Application
The "MySQL server has gone away" error typically occurs when a database connection has been idle for too long and the server closes it, but your application still tries to use it.
Causes of Your Issue
When using Django with ASGI, there's a key difference in how database connections are managed compared to WSGI:
-
ASGI's Long-Running Nature: Unlike WSGI where connections are typically closed after each request, ASGI applications can maintain long-running processes.
-
Connection Pooling: Your setting of CONN_MAX_AGE=600
isn't working because ASGI frameworks often maintain their own connection pools outside of Django's connection management.
Solutions
1. Implement Connection Health Checks
Add middleware or signals to verify connection health before queries:
from django.db import connections
from django.db.utils import OperationalError
def check_connection():
for conn in connections.all():
try:
c = conn.cursor()
c.execute("SELECT 1")
c.close()
except OperationalError:
conn.close()
2. Use a Database Connection Pool
Consider using a connection pooling library like django-db-connection-pool
:
DATABASES = {
'default': {
'ENGINE': 'dj_db_conn_pool.backends.mysql',
'NAME': 'your_db_name',
'USER': 'your_user',
'PASSWORD': 'your_password',
'HOST': 'your_host',
'PORT': '3306',
'POOL_OPTIONS': {
'POOL_SIZE': 20,
'MAX_OVERFLOW': 10,
'RECYCLE': 300, # Recycle connections after 5 minutes
}
}
}
3. Adjust MariaDB Configuration
You could also modify your MariaDB server settings:
wait_timeout = 31536000 # 1 year in seconds
interactive_timeout = 31536000
However, this is generally not recommended as it can lead to resource exhaustion.
4. Explicitly Close Connections in ASGI Lifecycle Events
In your ASGI application, explicitly close connections at appropriate lifecycle points:
from django.db import connections
async def lifespan(scope, receive, send):
message = await receive()
if message["type"] == "lifespan.startup":
# Application startup
await send({"type": "lifespan.startup.complete"})
elif message["type"] == "lifespan.shutdown":
# Application shutdown - close all DB connections
for conn in connections.all():
conn.close()
await send({"type": "lifespan.shutdown.complete"})
The most reliable approach is typically a combination of connection pooling with proper health checks to ensure stale connections are detected and refreshed.