Какие production-риски есть у Django: blocking code, connection pooling, config, auth, observability, deploy или graceful shutdown?
Ключевые production-риски Django: синхронный ORM блокирует ASGI-воркеры, пул соединений требует pgbouncer, секреты в settings.py утекают в репо, SESSION_COOKIE_SECURE=False открывает session hijacking, отсутствие structured logging и health-check усложняет observability.
Production-риски Django
1. Blocking code в ASGI-окружении
Django ORM — синхронный. При запуске под Uvicorn/Daphne с async views вызов Model.objects.filter() напрямую блокирует event loop, что убивает конкурентность.
# Плохо — блокирует event loop
async def my_view(request):
users = list(User.objects.all()) # блокирующий вызов!
...
# Правильно — sync_to_async обёртка
from asgiref.sync import sync_to_async
async def my_view(request):
users = await sync_to_async(list)(User.objects.all())
# или через database_sync_to_async декоратор
...
2. Connection pooling
Django открывает одно соединение с БД на поток. Gunicorn с --workers=8 --threads=4 создаёт 32 соединения. При масштабировании PostgreSQL быстро упирается в max_connections=100.
# settings.py — уменьшить CONN_MAX_AGE чтобы не держать соединения
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"CONN_MAX_AGE": 60, # секунды (0 = закрывать после каждого запроса)
"OPTIONS": {
"pool": True, # Django 5.1+ встроенный пул
},
}
}
# Или через pgbouncer как внешний пулер (рекомендуется)
3. Конфигурация и секреты
# settings.py — читать секреты только из окружения
import os
from pathlib import Path
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # не os.getenv() — ошибка при отсутствии
DEBUG = os.getenv("DEBUG", "False") == "True"
ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(",")
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ["DB_HOST"],
}
}
4. Auth и безопасность cookies
# settings.py — обязательные настройки для HTTPS
SESSION_COOKIE_SECURE = True # cookie только по HTTPS
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True # недоступен из JS
SESSION_COOKIE_SAMESITE = "Lax" # защита от CSRF
SECURE_HSTS_SECONDS = 31536000
SECURE_SSL_REDIRECT = True
SECURE_BROWSER_XSS_FILTER = True
5. Observability: структурированные логи
# settings.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(name)s %(levelname)s %(message)s",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
},
},
"root": {"handlers": ["console"], "level": "INFO"},
"loggers": {
"django.request": {"level": "WARNING"},
"django.db.backends": {"level": "WARNING"}, # ERROR в проде
},
}
6. Graceful shutdown и health-check
# myapp/views.py — health endpoint для load balancer
from django.db import connections
from django.db.utils import OperationalError
from django.http import JsonResponse
def health_check(request):
try:
connections["default"].ensure_connection()
return JsonResponse({"status": "ok"})
except OperationalError:
return JsonResponse({"status": "db_unavailable"}, status=503)
Gunicorn поддерживает graceful shutdown через сигнал SIGTERM — завершает текущие запросы до graceful_timeout секунд. Настройка: --graceful-timeout 30.
7. Deploy без downtime
gunicorn myproject.wsgi:application \
--workers 4 \
--worker-class gthread \
--threads 2 \
--bind 0.0.0.0:8000 \
--graceful-timeout 30 \
--timeout 60 \
--access-logfile -
Подводные камни
- DEBUG=True в проде — раскрывает полный traceback с переменными окружения (включая SECRET_KEY) всем клиентам при 500-ошибке.
- Миграции при деплое — запуск
migrateво время работы без backward-compatible схемы ломает старые запущенные инстансы. - ALLOWED_HOSTS=[] по умолчанию — при DEBUG=False и пустом ALLOWED_HOSTS все запросы вернут 400.
- Celery воркеры не получают SIGTERM корректно — без
--statedbи правильного обработчика сигналов задачи теряются при остановке контейнера. - django.contrib.staticfiles в проде —
DEBUG=Trueобслуживает статику через Django; в проде нуженcollectstaticи nginx/CDN. - CONN_MAX_AGE=0 плюс pgbouncer в transaction mode — несовместимы с
CONN_MAX_AGE > 0; persistent-соединения и pgbouncer transaction pooling конфликтуют.
What hurts your answer
- Говорить только о запуске Django, но не об эксплуатации
- Не упоминать observability, обновления, безопасность и rollback
- Описывать риски абстрактно, без способов их снижать
What they're listening for
- Видит production-риски Django
- Говорит про monitoring, rollout, rollback и безопасность
- Умеет ранжировать риски по вероятности и влиянию