Возьмем за пример стек Nginx + Gunicorn + Django.
Бывает, что при наплыве трафика, Nginx отвечает ошибкой 5хх, а в логе Django ошибок нет.

Вы также можете увидеть, что Nginx говорит: "not enough workers". Это фиксится очень просто и уже есть довольно старая статья на Хабре, которая всё ещё актуальна: Ускоряем Nginx за 5 минут / Хабр (habr.com).
Единственное, что в новых версиях worker_processes лучше оставить auto.

Но Вы также можете всё ещё видеть upstream response error (timedout).
В таком случае мы обращаем внимание на Gunicorn.
Есть разные виды воркеров: Sync, Async, Gthread, Tornado, AsyncIO.

Самая распространённая конфигурация из мануалов по настройке:
gunicorn --workers 3 wsgi:app

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

Какое количество воркеров оптимально?
Считается формулой: (Количество ядер процессора)*2+1

Итого, конфигурация выше подходит для серверов с одним ядром, если ядер больше, то можно смело увеличивать количество воркеров по формуле, но если нет, увеличение количества воркеров приведет к принудительной перезагрузке Gunicorn'a системой, т.к. тот начнет утилизировать весь процессор:
gunicorn.service: A process of this unit has been killed by the OOM killer

То есть, для сервера с 2 ядрами, лучшая конфигурация будет такая:
gunicorn --workers 5 wsgi:app

А также, у Sync воркеров есть Gthread класс:
gunicorn --workers 5 --threads 2 wsgi:app
Указывая параметр тредов, воркеры автоматически становятся класса Gthread.

Треды помогут увеличить производительность, если количество воркеров уже не увеличить.
Такая настройка подходит для не слишком требовательных к CPU, быстрых запросов, I/O операций, SQL. В общем и целом, использование тредов помогает уменьшить утилизацию памяти Gunicorn'ом.

Такая вот небольшая шпаргалка по оптимизации Gunicorn, надеюсь, кому-то будет полезно!

UPDATE:
В новых версиях Gunicorn улучшен параллелизм, можете попробовать умножить количество воркеров на 4.
Чтобы не гадать, а найти для своего проекта лучшую конфигурацию, используйте Hey HTTP load generator.
Таким нехитрым путем, я обнаружил, что производительность моего бэкэнда стала лучше без тредов, просто оптимизировав количество воркеров.

Комментарии (2)


  1. IvanZaycev0717
    31.07.2024 07:33
    +3

    если ядер больше, то можно смело увеличивать количество воркеров по формуле

    А почему бы нам не автоматизировать использование данной формулы для автоопределения количества дотсупных логических ядер на виртуальной машине. Например, с помощью такого скрипта gunicorn.conf.py

    import fcntl
    import multiprocessing
    import socket
    import struct
    
    
    def get_ip_address(ifname):
        """Return current IP of our web app."""
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        return socket.inet_ntoa(fcntl.ioctl(
            s.fileno(),
            0x8915,
            struct.pack('256s', bytes(ifname[:15], 'utf-8'))
        )[20:24])
    
    
    ip_address = get_ip_address('eth0')
    
    bind = f'{ip_address}:8000' # Здесь свой порт выберите
    workers = multiprocessing.cpu_count() * 2 + 1
    
    timeout = 2
    preload = True
    loglevel = 'info'

    Ну а дальше все очень просто:

    gunicorn -c ./gunicorn.conf.py wsgi:app


    1. Ryav
      31.07.2024 07:33

      Крайне не рекомендую множить на 2 виртуальные ядра (в моём случае с EC2), поскольку виртуальная машина перестаёт вывозить.