Оптимизация стадии инициализации Django

Автор: admin от 29-12-2017, 21:40, посмотрело: 34

Если у вас Django проект работает на синхронных воркерах и вы периодически их перезапускаете (например, в gunicorn это опция --max-requests), полезно было бы знать, что по-умолчанию после каждого перезапуска воркера, первый запрос к нему обрабатывается гораздо дольше, чем последующие.



В этой статье расскажу как я решил эту и другие проблемы, вызывающие аномальные задержки на случайных запросах.

CONN_MAX_AGE, которая только лишь(?) по историческим причинам заставляет ваше Django приложение работать как php скрипт из нулевых. Так что правило:



В настройки Django БД адаптера добавить CONN_MAX_AGE=None, чтобы подключения были постоянными.

Я бы даже и не заметил этого. Но, по какой-то причине, вызов psycopg2.connect иногда подвисает ровно на 5 секунд. Вот в этом я до конца не разобрался. Параллельно запущенный скрипт, который вызывает эту функцию раз в 10 секунд работал стабильно и подключался к БД быстрее чем за секунду за все время пока был запущен.



Но эти два правила конфиликтуют друг с другом, т.к. перед форком в основном процессе создаются подключения к БД и кешу. Дочерние процессы наследуют открытые сокеты основного процесса. В результате, это приводит к неопределённому поведению, когда несколько процессов будут одновременно работать с одним сокетом. Поэтому, перед форком, нужно закрыть все подключения:



# Close connections to database and cache before or after forking.
# Without this, child processes will share these connections and this is not supported.
def close_network_connections():
    from django import db
    from django.core import cache
    from django.conf import settings

    for conn in db.connections:
        db.connections[conn].close()

    django_redis_close_connection = getattr(settings, 'DJANGO_REDIS_CLOSE_CONNECTION', False)
    settings.DJANGO_REDIS_CLOSE_CONNECTION = True
    cache.close_caches()
    settings.DJANGO_REDIS_CLOSE_CONNECTION = django_redis_close_connection

if os.environ.get('WSGI_FULL_INIT'):
    make_init_request()
    # in case wsgi module preloaded in master process (i.e. `gunicorn --preload`)
    if os.environ.get('WSGI_FULL_INIT_CLOSE_CONNECTIONS'):
        close_network_connections()


Т.о. при использовании --preload и WSGI_FULL_INIT, нужно еще задать WSGI_FULL_INIT_CLOSE_CONNECTIONS.



В результате, аномальные задержки были полностью исключены. Но есть пара крайних случаев, когда они все же могут возникнуть:



Если все воркеры начнут перезапускаться одновременно. Это вполне вероятная ситуация, т.к. если запросы между воркерами распределяются приблизительно равномерно, то и max-requests наступит приблизительно в одно и то же время. Поэтому:



Запускай gunicorn c max-requests-jitter, чтобы воркеры не перезапускались одновременно, даже если они это делают достаточно быстро.

Также задержка может возникнуть во время первого запроса, когда создаются подключения к БД и другим внешним системам.



Это можно решить, но у меня нет идей как написать код независимый от используемого wsgi сервера. В gunicorn можно добавить обработчик для post_worker_init и там еще раз вызвать make_init_request(), тогда воркер перед получением первого запроса будет готов на 100%. Чтобы не усложнять, было решено пока что обойтись без этого, ведь мы и так добились того, что больше задержек на практике не наблюдается.



Источник: Хабрахабр

Категория: Информационная безопасность » Криптография

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *