Ошибка 502 в связке Nginx + Gunicorn/uWSGI в 80% случаев означает не падение приложения, а разрыв TCP-соединения или переполнение очереди запросов. В высоконагруженных Django-проектах неправильный расчет количества воркеров приводит к каскадному отказу системы всего за 10-15 секунд пикового трафика.
Анатомия 502: почему молчит бэкенд
Когда Nginx возвращает 502 Bad Gateway, он констатирует факт: запрос был отправлен, но сервер приложений (Gunicorn или uWSGI) либо закрыл соединение мгновенно, либо не ответил в рамках установленного timeout. Часто проблема кроется в несоответствии типов сокетов: использование TCP-сокета там, где эффективнее Unix-сокет, увеличивает оверхед на сетевой стек на 5-10% и создает лишнюю точку отказа при пиках в 1000+ RPS.
Кейс: проект на Django с 4-ядерным CPU и 8 ГБ RAM выдавал 502-ю при 50 одновременных пользователях. Причина — лимит open files в ОС (ulimit -n 1024), который забивался из-за утечки соединений к БД. После поднятия лимита до 65535 ошибка исчезла. Экспертный вывод: прежде чем править конфиг Gunicorn, проверьте системные лимиты дескрипторов файлов.
Gunicorn: ловушки worker_class и timeout
Типичная ошибка новичка — использование синхронных воркеров (sync) для задач, требующих ожидания (API сторонних сервисов, тяжелые SQL-запросы). В режиме sync один воркер обрабатывает один запрос; если запрос длится 5 секунд, а воркеров всего 4, то 5-й пользователь получит 502-ю ошибку или зависнет. Переход на gevent или eventlet позволяет обрабатывать тысячи соединений, но требует полной asyncio-совместимости библиотек.
Практика показывает: установка timeout в 30 секунд (дефолт) часто избыточна. Для быстрых API оптимально 10-15 секунд. Если запрос длится дольше, лучше вернуть 504 Gateway Timeout через Nginx, чем позволить Gunicorn-воркеру «зависнуть» и вызвать 502 Bad Gateway на Nginx из-за переполнения очереди. Мой выбор для I/O-интенсивных задач — gthread с 2-4 потоками на воркер.
uWSGI: тонкая настройка listen queue
uWSGI мощнее Gunicorn, но сложнее в настройке. Главный виновник 502-й здесь — параметр listen. По умолчанию в Linux лимит очереди сокета (somaxconn) составляет 128. Если uWSGI не успевает забирать запросы, очередь переполняется, и Nginx мгновенно отдает 502. Увеличение listen до 1024 в конфиге uWSGI без соответствующего изменения sysctl -w net.core.somaxconn=1024 в ядре Linux бесполезно.
Сравнение: Gunicorn проще в деплое (настройка за 5 минут), но uWSGI дает прирост производительности в 15-20% за счет более эффективного управления памятью и встроенного кэширования. Однако цена этому — риск «выстрелить в ногу» из-за избытка параметров. Экспертный вывод: используйте uWSGI только если ваш трафик превышает 500 RPS и вам критичен каждый миллисекундный выигрыш.
Связь с ресурсами: CPU, RAM и OOM Killer
Часто 502-я ошибка является следствием работы OOM Killer (Out of Memory). Когда Django-приложение начинает потреблять больше выделенной RAM (например, при выгрузке тяжелого отчета в память), ядро Linux убивает процесс воркера. Nginx видит обрыв соединения и выдает ошибку. В логах /var/log/syslog это выглядит как «Out of memory: Kill process».
Кейс: приложение с утечкой памяти потребляло по 500 МБ на воркер вместо расчетных 200 МБ. При 4 воркерах и 2 ГБ RAM сервер уходил в swap, время ответа росло до 10-20 секунд, что вызывало 502 Bad Gateway на Nginx. Решение: внедрение max-requests в Gunicorn (например, 1000), что заставляет воркер перезапускаться после обработки определенного числа запросов, очищая память. Это стандарт индустрии для борьбы с микро-утечками в Python.
Вывод
Для большинства Django-проектов оптимальным выбором будет Gunicorn с воркерами gthread (количество: 2 * CPU + 1) и обязательным лимитом max-requests для предотвращения утечек памяти. Избегайте синхронных воркеров при наличии внешних API-запросов. Начинайте диагностику всегда с анализа логов ошибок Nginx и системного журнала dmesg — в 90% случаев причина там, а не в коде Python. Если трафик нестабилен и имеет резкие пики, первым делом увеличивайте somaxconn в ядре и параметр listen в сервере приложений.