Оптимизация: Настройка веб-сервера Nginx для улучшения показателей RPS в HTTP API

Автор оригинала: Rohit Gupta

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

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

Эта статья посвящена настройке Nginx для повышения производительности, то есть для увеличения показателей RPS (Requests Per Second) в HTTP API. Я постарался рассказать об оптимизации, которую мы применили в развернутой системе, чтобы обрабатывать десятки тысяч запросов в секунду без траты огромного количества ресурсов.

План действий: необходимо запустить HTTP API (написанный на Python с использованием flask), проксированный с помощью Nginx; требуется высокая пропускная способность. Содержимое API будет меняться с интервалом в один день.

оптимизация
имя существительное

процесс достижения наилучшего результата; наиболее эффективное использование ситуации или ресурса.

Мы использовали супервизор для запуска WSGI Server со следующими конфигурациями:


Команда для супервизора выглядит так:

gunicorn api:app --workers=5 --worker-
class=meinheld.gmeinheld.MeinheldWorker --bind=unix:api.sock

Мы попробовали оптимизировать конфигурацию Nginx и проверили, что лучше всего сработает для нас.

Для оценки производительности API мы использовали wrk с помощью следующей команды:

wrk -t20 -c200 -d20s http://api.endpoint/resource

Конфигурация по умолчанию


Сначала мы выполнили нагрузочное тестирование API без каких-либо изменений и получили следующую статистику:

Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   192.48ms  274.78ms   1.97s    87.18%
    Req/Sec    85.57     29.20   202.00     72.83%
  33329 requests in 20.03s, 29.59MB read
  Socket errors: connect 0, read 0, write 0, timeout 85
Requests/sec:   1663.71
Transfer/sec:      1.48MB

Обновление конфигурации по умолчанию


Давайте обновим стандартную конфигурацию Nginx, то есть nginx.conf в /etc/nginx/nginx.conf

worker_processes auto;
#or should be equal to the CPU core, you can use `grep processor /proc/cpuinfo | wc -l` to find; auto does it implicitly.

worker_connections 1024;
# default is 768; find optimum value for your server by `ulimit -n`

access_log off;
# to boost I/O on HDD we can disable access logs
# this prevent nginx from logging every action in a log file named `access.log`.

keepalive_timeout 15;
# default is 65;
# server will close connection after this time (in seconds)

gzip_vary on;
gzip_proxied any;
gzip_comp_level 2;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# reduces the data that needs to be sent over the network
nginx.conf (/etc/nginx/nginx.conf)

После изменений мы запускаем проверку конфигурации:

sudo nginx -t

Если проверка прошла успешно, можно перезапустить Nginx, чтобы отобразить изменения:

sudo service nginx restart

С такой конфигурацией мы провели нагрузочное тестирование API и получили следующий результат:

Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   145.80ms  237.97ms   1.95s    89.51%
    Req/Sec   107.99     41.34   202.00     66.09%
  42898 requests in 20.03s, 39.03MB read
  Socket errors: connect 0, read 0, write 0, timeout 46
  Non-2xx or 3xx responses: 2
Requests/sec:   2141.48
Transfer/sec:      1.95MB

Эти конфигурации сократили тайм-ауты и увеличили показатели RPS (количество запросов в секунду), но ненамного.

Добавление кеша Nginx


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

Но добавление кеша приводит к его недействительности… это одна из двух трудностей здесь.
В компьютерных науках есть только две сложности: инвалидация кеша и именование вещей.Фил Карлтон

Мы выбираем простое решение очистки каталога кеша с помощью cronjob после обновления содержимого в нижестоящей системе.

Далее всю тяжелую работу будет выполнять Nginx, но теперь мы должны быть уверены, что Nginx готов на 100%!

Чтобы добавить кеширование в Nginx, нужно прописать несколько директив в файл конфигурации Nginx.

Перед этим нам нужно создать каталог для хранения данных кеша:

sudo mkdir -p /data/nginx/cache

Изменения в конфигурации Nginx:

proxy_cache_path /data/nginx/cache keys_zone=my_zone:10m inactive=1d;
server {
    ...
    location /api-endpoint/ {
        proxy_cache my_zone;
        proxy_cache_key "$host$request_uri$http_authorization";
        proxy_cache_valid 404 302 1m;
        proxy_cache_valid 200 1d;
        add_header X-Cache-Status $upstream_cache_status;
    }
    ...
}

Кеширование проксируемых запросов (конфигурация Nginx)

После этого изменения в конфигурации мы провели нагрузочное тестирование API и получили следующий результат:

Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.88ms    5.44ms  88.91ms   81.36%
    Req/Sec     1.59k   500.04     2.95k    62.50%
  634405 requests in 20.06s, 589.86MB read
Requests/sec:  31624.93
Transfer/sec:     29.40MB

Таким образом, мы получили почти 19-кратное увеличение производительности за счет добавления кеширования.
Примечание от эксперта Timeweb:

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

Кеш Nginx в RAM (Random Access Memory)


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

Итак, сначала нужно создать каталог, куда будет монтироваться кеш оперативной памяти:

sudo mkdir -p /data/nginx/ramcache

Чтобы смонтировать созданный каталог в RAM с помощью tmpfs, используйте команду:

sudo mount -t tmpfs -o size=256M tmpfs /data/nginx/ramcache

Это монтирует /data/nginx/ramcache в RAM, выделяя 256 МБ.

Если вы считаете, что хотите отключить RAM-кеш, просто выполните команду:

sudo umount /data/nginx/ramcache

Чтобы автоматически пересоздать каталог кеша в RAM после перезагрузки, нам нужно обновить файл /etc/fstab. Добавьте в него следующую строку:

tmpfs /data/nginx/ramcache tmpfs defaults,size=256M 0 0

Примечание: Также мы должны прописать значение proxy_cache_path с указанием пути до ramcache (/data/nginx/ramcache).

После обновления конфигурации мы снова провели нагрузочное тестирование API и получили следующий результат:

Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.57ms    5.69ms 277.76ms   92.94%
    Req/Sec     1.98k   403.94     4.55k    71.77%
  789306 requests in 20.04s, 733.89MB read
Requests/sec:  39387.13
Transfer/sec:     36.62MB

Хранение кеша в оперативной памяти привело к значительному улучшению почти в 23 раза.

Журнал буферизованного доступа


Мы храним журнал доступа к проксированным приложениям, но можно сначала сохранить журнал в буфере и только потом записать на диск:

  • если следующая строка лога не помещается в буфер
  • если данные в буфере старше, чем указано в параметре flush.

Эта процедура уменьшит частоту записи, выполняемую с каждым запросом. Для этого нам просто нужно добавить параметры buffer и flush с соответствующим значением в директиве access_log:

location / {
    ...
    access_log /var/log/nginx/fast_api.log combined buffer=256k flush=10s;
    error_log /var/log/nginx/fast_api.err.log;
}

Буферный журнал перед записью на диск

Таким образом, согласно приведенной выше конфигурации, изначально журналы доступа будут записываться в буфер и сохраняться на диск только тогда, когда размер буфера достигнет 256 КБ или буферизованные данные станут старше 10 секунд.

Примечание: Здесь объединено имя log_format.

После повторного нагрузочного тестирования мы получили следующий результат:

Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     4.21ms    3.19ms  84.83ms   83.84%
    Req/Sec     2.53k   379.87     6.02k    77.05%
  1009771 requests in 20.03s, 849.31MB read
Requests/sec:  50413.44
Transfer/sec:     42.40MB

Такая конфигурация значительно увеличила количество запросов в секунду, примерно в 30 раз по сравнению с начальным этапом.

Вывод


В этой статье мы обсудили процесс оптимизации конфигурации Nginx для улучшения показателей RPS. Показатели RPS были увеличены с 1663 до ~ 50413 (увеличение примерно в 30 раз), это обеспечивает высокую пропускную способность. Благодаря настройке стандартных параметров можно улучшить производительность системы.

Закончим статью цитатой:
Сначала сделай так, чтобы работало. Потом сделай правильно. Затем оптимизируй.Кент Бек

Источники


Timeweb
VDS, виртуальный хостинг, домены, серверы

Комментарии 5

    +4

    Вы решили побить RuVDS по числу устаревших и легко гуглящихся постов?

      –1
      Sannis, здравствуйте!

      Мы живем по правилу: видишь полезную статью — переведи, чтобы о ней узнало еще больше людей! :)

      Будем благодарны, если вы посоветуете ресурсы с качественными статьями или актуальные для вас направления тем.
      +2
      Содержимое API будет меняться с интервалом в один день.

      У некоторых статичные сайты меняются раз в минуту :)
      А у вас тут API(!) раз в сутки да еще и по крону инвалидируется.
        0
        savostin, здравствуйте!

        Вероятно, автор оригинала статьи указал параметры к директиве proxy_cache_valid просто для примера. Можно изменить время обновления кеша самостоятельно согласно вашим требованиям, хоть до 1 секунды, например: proxy_cache_valid 200 1s;

        При высоком RPS это позволит снизить нагрузку на бэкенд.
        0
        Спасибо за перевод!
        Везде пишут про сжатие, но нигде не видел про кеширование в оперативку. Отдельно такие статьи есть, но даже в голову не приходило их искать. Узнал что-то новое :)

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

        Самое читаемое