DjangoSeniorExperience

Какие 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 и безопасность
  • Умеет ранжировать риски по вероятности и влиянию

Related topics