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

Оптимізація: Налаштування веб-сервера Nginx для поліпшення показників RPS в HTTP API
9 хв. читання
03 жовтня 2020

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

Ми зіткнулися з вказаною вище проблемою, коли розгорнута нами система, якою щодня користувалося мільйон користувачів, які підключалися сплесками час від часу. Це означає, що розгортання кількох серверів або їх масштабування не буде в даній ситуації оптимальним рішенням.

Ця стаття присвячена налаштуванню 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-кратне збільшення продуктивності внаслідок додавання кешування.

Кеш Nginx в RAM

Зробімо ще один крок вперед! В цей час дані нашого кеша зберігаються на диску. А якщо ми збережемо ці дані в 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 секунд.

Після повторного тестування навантаження ми отримали такий результат:

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 разів), це забезпечує високу пропускну здатність. Завдяки налаштуванню стандартних параметрів можна поліпшити продуктивність системи. Закінчимо статтю цитатою:

Спочатку зроби так, щоб працювало. Потім зроби правильно. Потім оптимізуй. - Кент Бек

Джерела

Джерело: Optimizations: Tuning Nginx for better RPS of an HTTP API

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація