Пули потоків: прискорюємо NGINX в 9 і більше разів

Як відомо, для обробки з'єднань NGINX використовує асинхронний подієвий підхід. Замість того, щоб виділяти на кожен запит окремий потік або процес (як це роблять сервери з традиційною архітектурою), NGINX мультиплексирует обробку безлічі сполук і запитів в одному робочому процесі. Для цього застосовуються сокети в неблокирующем режимі і такі ефективні методи роботи з подіями, як epoll і kqueue.

За рахунок малого і постійного кількості повних потоків обробки (зазвичай по одному на ядро) досягається економія пам'яті, а також ресурсів процесора на переключення контекстів. Всі переваги даного підходу ви можете добре спостерігати на прикладі самого NGINX, який здатний обробляти мільйони запитів одночасно і добре масштабуватися.

Кожен процес витрачає пам'ять і кожне перемикання між ними вимагає додаткових циклів процесора, а також призводить до вимивання L-кешей

У медалі є і зворотня сторона. Головною проблемою асинхронного підходу, а краще навіть сказати «ворогом» — є блокуючі операції. І, на жаль, багато авторів сторонніх модулів, не розуміючи принципів функціонування NGINX, намагаються виконувати блокуючі операції у своїх модулях. Такі операції здатні повністю вбити продуктивність NGINX і їх слід уникати будь-якою ціною.

Але навіть у поточній реалізації NGINX не завжди можливо уникнути блокувань. І для вирішення даної проблеми в NGINX версії 1.7.11 був представлений новий механізм «пулів потоків». Що це таке і як його застосовувати розглянемо далі, а для початку познайомимося з нашим ворогом в обличчя.

Проблема
Для кращого розуміння проблеми спершу розберемося докладніше в основних моментах щодо того, як працює NGINX.

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



Такими подіями можуть бути: події таймера, надходження нових даних або відправлення відповіді і звільнення місця в буфері, повідомлення про помилки в з'єднанні або його закриття. NGINX отримує пачку таких подій і починає їх по черзі обробляти, виконуючи необхідні дії. Так вся обробка черги подій відбувається в простому циклі в одному потоці. NGINX витягує з черги події одне за іншим і робить якісь дії, наприклад, пише в сокет дані або читає. У більшості випадків це відбувається настільки швидко (найчастіше це просто копіювання невеликих обсягів даних в пам'яті), що можна вважати обробку всіх подій миттєвою.

Вся обробка відбувається простим циклом в одному потоці.

Але що станеться, якщо спробувати виконати якусь довгу і важку операцію? Весь цикл обробки подій зупиниться на очікуванні завершення цієї операції.

Так, під блокуючої операцією ми маємо на увазі будь-яку операцію, яка затримує цикл обробки подій на суттєвий час. Операції можна назвати блокуючими з різних причин. Наприклад, NGINX може бути зайнятий довгої ресурсномісткою обчислювальної операцією, або він може очікувати доступу до якогось ресурсу (жорсткого диска, мьютексу, бібліотечної викликом, котрий чекає відповіді від бази даних в синхронному режимі, тощо). Ключовим моментом тут є те, що під час виконання цих операцій робочий процес не може робити більше нічого корисного, не може обробляти інші події, хоча в нас є ще вільні ресурси, і події, які очікують далі в черзі, можуть їх використовувати.

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



Схожа ситуація відбувається в NGINX, коли файл, який потрібно надіслати, знаходиться не в пам'яті, а на жорсткому диску. Диски повільні (особливо ті, що обертаються), а інші запити, які чекають своєї обробки в черзі, можуть не вимагати доступу до жорсткого диска, але все одно, змушені чекати. В результаті зростають затримки та ресурси системи можуть не використовуватися повністю.



Деякі операційні системи надають інтерфейси для асинхронного читання файлів і NGINX вміє ефективно використовувати їх (див. опис директиви aio). Гарним прикладом такої системи є FreeBSD. На жаль, не можна сказати того ж про Linux. Хоча в Linux існує якийсь асинхронний інтерфейс для читання файлів, але він має ряд істотних недоліків. Одним з таких є вимоги на вирівнювання звернень до файлу і буфера для читання і NGINX успішно з цим справляється. Але друга проблема гірше. Для асинхронного читання потрібна установка прапора
O_DIRECT
на дескриптору файлу. Це означає, що всі дані будуть читатися з диска, минаючи кеш сторінок операційної системи (т. зв. page cache), що в багатьох випадках не є оптимальним і суттєво збільшує навантаження на дискову підсистему.

Зокрема для вирішення даної проблеми в NGINX 1.7.11 і був представлений новий механізм пулів потоків. Вони поки що не включені в NGINX Plus, але ви можете зв'язатися з відділом продажів, якщо бажаєте випробувати збірку NGINX Plus R6 з пулами потоків.

А тепер розберемо детальніше, що ж вони з себе представляють і як функціонують.

Пули потоків
Повернемося до нашому невдалому продавцю. Але на цей раз він виявився кмітливіший (або це після того, як його побили розлючені покупці?) і організував кур'єрську службу. Тепер, коли покупець запитує товар, якого немає на прилавку, то замість того, щоб залишати прилавок, вирушаючи за товаром самостійно і змушуючи всіх інших чекати, він відправляє запит на доставку товару в кур'єрську службу і продовжує обслуговувати черга покупців. Таким чином лише ті покупці, чиїх замовлень не виявилося в магазині, очікують доставки, а продавець тим часом може без проблем обслуговувати інших.



У разі NGINX роль кур'єрської служби виконує пул потоків. Він складається з черги завдань і набору окремих легковагих потоків, які обробляють цю чергу. Коли робочого процесу вимагає виконати якусь потенційно довгу операцію, то він замість того, щоб займатися цим самостійно, поміщає завдання на обробку в чергу пулу, звідки його відразу ж може забрати будь-який вільний потік в обробку.



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

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

У даний момент вивантаження операцій в пул потоків реалізована тільки для системного виклику read() на більшості операційних систем, а також для sendfile() на Linux. Ми продовжимо дослідження даного питання і, ймовірно, в майбутньому реалізуємо виконання та інших операцій пулом потоків, якщо це дасть виграш в продуктивності.

Тестуємо продуктивність
Настав час перейти від теорії до практики. Для демонстрації ефекту від використання пулів потоків проведемо невеликий експеримент. А саме відтворимо найбільш важкі умови, змусивши NGINX виконувати суміш блокуючих і неблокирующих читань, коли проблема блокувань на звернення до диска проявить себе в повній мірі.

Для це потрібно набір даних, який гарантовано не поміститься в кеш операційної системи. На машини з об'ємом оперативної пам'яті до 48 Гб було згенеровано 256 Гб файлів по 4 Мб кожний, містять рандомные дані і запущений NGINX версії 1.9.0 для їх роздачі.

Конфігурація досить проста:

worker_processes 16;

events {
accept_mutex off;
}

http {
include mime.types;
default_type application/octet-stream;

access_log off;
sendfile on;
sendfile_max_chunk 512k;

server {
listen 8000;

location / {
root /storage;
}
}
}

Як ви можете помітити, для отримання кращих показників зроблений невеликий тюнінг: відключено логування, відключений accept_mutex, включений sendfile і налаштоване значення sendfile_max_chunk. Останнє дозволяє скоротити час блокування на виклик
sendfile()
, оскільки в цьому випадку NGINX не стане намагатися прочитати і відправити весь файл за раз, а буде це робити частинами по 512 кілобайт.

Машина оснащена двома процесорами Intel Xeon E5645 (всього 12 ядер, 24 HyperThreading потоку) і мережевим інтерфейсом 10 Гбіт. Дискова підсистема представляє з себе 4 жорстких диска Western Digital WD1003FBYX об'єднаних в RAID10 масив. Все це управляється операційною системою Ubuntu Server 14.04.1 LTS.

В якості клієнтів виступають дві аналогічні за характеристиками машини. На одній з них запущений wrk, створює постійне навантаження Lua-скриптом. Скрипт запитує файли зі сховища у випадковому порядку використовуючи 200 паралельних з'єднань. Назвемо цю навантаження паразитної.

З іншої машини-клієнта ми будемо запускати
wrk
, який буде запитувати один і той же файл в 50 потоків. Оскільки до даного файлу йде постійне звернення, то, на відміну від файлів, запитуваних у випадковому порядку, він не буде встигати вимиватися з кешу операційної системи і його читання завжди буде відбуватися з пам'яті. Назвемо таку навантаження тестовій.

Продуктивність ми будемо вимірювати за показниками
ifstat
на сервері і статистикою
wrk
з другої машини-клієнта.

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

% ifstat-bi eth2
eth2
Kbps in out Kbps
5531.24 1.03 e+06
4855.23 812922.7
5994.66 1.07 e+06
5476.27 981529.3
6353.62 1.12 e+06
5166.17 892770.3
5522.81 978540.8
6208.10 985466.7
6370.79 1.12 e+06
6123.33 1.07 e+06

Як видно з даної конфігурацією і під навантаженням сервер здатний видавати близько одного гігабіта в секунду. При цьому в top-е можна спостерігати, що всі робочі процеси NGINX перебувають більшу частину часу в стані блокування на I/O (позначені літерою
D
):

top - 10:40:47 up 11 days, 1:32, 1 user, load average: 49.61, 45.77 62.89
Завдання: 375 total, 2 running, 373 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.3 sy, 0.0 ni, 67.7 id 31.9 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 49453440 total, 49149308 used, 304132 free, 98780 buffers
KiB Swap: 10474236 total, 20124 used, 10454112 free, 46903412 cached Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4639 vbart 20 0 47180 28152 496 D 0.7 0.1 0:00.17 nginx
4632 vbart 20 0 47180 28196 536 D 0.3 0.1 0:00.11 nginx
4633 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.11 nginx
4635 vbart 20 0 47180 28136 480 D 0.3 0.1 0:00.12 nginx
4636 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.14 nginx
4637 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.10 nginx
4638 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.12 nginx
4640 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.13 nginx
4641 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.13 nginx
4642 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.11 nginx
4643 vbart 20 0 47180 28276 536 D 0.3 0.1 0:00.29 nginx
4644 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.11 nginx
4645 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.17 nginx
4646 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.12 nginx
4647 vbart 20 0 47180 28208 532 D 0.3 0.1 0:00.17 nginx
4631 vbart 20 0 47180 756 252 S 0.0 0.1 0:00.00 nginx
4634 vbart 20 0 47180 28208 536 D 0.0 0.1 0:00.11 nginx
4648 vbart 20 0 25232 1956 1160 R 0.0 0.0 0:00.08 top
25921 vbart 20 0 121956 2232 1056 S 0.0 0.0 0:01.97 sshd
25923 vbart 20 0 40304 4160 2208 S 0.0 0.0 0:00.53 zsh

В даному випадку все залежить продуктивність дискової підсистеми, при цьому процесор більшу частину часу простоює. Результати
wrk
також не втішні:

Running 1m test @ http://192.0.2.1:8000/1/1/1
12 threads and connections 50
Thread Stats Avg Stdev Max ± Stdev
Latency 7.42 s 5.31 s 24.41 s 74.73%
Req/Sec 0.15 0.36 1.00 84.62%
488 requests in 1.01 m, 2.01 GB read
Requests/sec: 8.08
Transfer/sec: 34.07 MB

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

А тепер підключимо пул потоків, для чого додамо директиву aio threads в блок location з сховищем:

location / {
root /storage;
aio threads;
}

і попросимо наш NGINX перезавантажити конфігурації.

Повторимо тест.

% ifstat-bi eth2
eth2
Kbps in out Kbps
60915.19 9.51 e+06
59978.89 9.51 e+06
60122.38 9.51 e+06
61179.06 9.51 e+06
61798.40 9.51 e+06
57072.97 9.50 e+06
56072.61 9.51 e+06
61279.63 9.51 e+06
61243.54 9.51 e+06
59632.50 9.50 e+06

Тепер наш сервер видає 9,5 Гбіт/с (проти ~1 Гбіт/с без пулів потоків)!

Ймовірно, він міг би віддавати і більше, але це є практичним межею для цього мережного інтерфейсу і NGINX впирається у пропускну здатність мережі. Робочі процеси більшу частину часу сплять в очікуванні подій (перебувають у стані
S
):

top - 10:43:17 up 11 days, 1:35, 1 user, load average: 172.71, 93.84, 77.90
Завдання: 376 total, 1 running, 375 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.2 us, 1.2 sy, 0.0 ni, 34.8 id, 61.5 wa, 0.0 hi, 2.3 si, 0.0 st
KiB Mem: 49453440 total, 49096836 used, 356604 free, 97236 buffers
KiB Swap: 10474236 total, 22860 used, 10451376 free, 46836580 cached Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4654 vbart 20 0 309708 28844 596 S 9.0 0.1 0:08.65 nginx
4660 vbart 20 0 309748 28920 596 S 6.6 0.1 0:14.82 nginx
4658 vbart 20 0 309452 28424 520 S 4.3 0.1 0:01.40 nginx
4663 vbart 20 0 309452 28476 572 S 4.3 0.1 0:01.32 nginx
4667 vbart 20 0 309584 28712 588 S 3.7 0.1 0:05.19 nginx
4656 vbart 20 0 309452 28476 572 S 3.3 0.1 0:01.84 nginx
4664 vbart 20 0 309452 28428 524 S 3.3 0.1 0:01.29 nginx
4652 vbart 20 0 309452 28476 572 S 3.0 0.1 0:01.46 nginx
4662 vbart 20 0 309552 28700 596 S 2.7 0.1 0:05.92 nginx
4661 vbart 20 0 309464 28636 596 S 2.3 0.1 0:01.59 nginx
4653 vbart 20 0 309452 28476 572 S 1.7 0.1 0:01.70 nginx
4666 vbart 20 0 309452 28428 524 S 1.3 0.1 0:01.63 nginx
4657 vbart 20 0 309584 28696 592 S 1.0 0.1 0:00.64 nginx
4655 vbart 20 0 30958 28476 572 S 0.7 0.1 0:02.81 nginx
4659 vbart 20 0 309452 28468 564 S 0.3 0.1 0:01.20 nginx
4665 vbart 20 0 309452 28476 572 S 0.3 0.1 0:00.71 nginx
5180 vbart 20 0 25232 1952 1156 R 0.0 0.0 0:00.45 top
4651 vbart 20 0 20032 752 252 S 0.0 0.0 0:00.00 nginx
25921 vbart 20 0 121956 2176 1000 S 0.0 0.0 0:01.98 sshd
25923 vbart 20 0 40304 3840 2208 S 0.0 0.0 0:00.54 zsh

І у нас ще є солідний запас ресурсів процесора.

Результати
wrk
з другої машини:

Running 1m test @ http://192.0.2.1:8000/1/1/1
12 threads and connections 50
Thread Stats Avg Stdev Max ± Stdev
Latency 226.32 ms 392.76 ms 1.72 s 93.48%
Req/Sec 20.02 10.84 59.00 65.91%
15045 requests in 1.00 m, 58.86 read GB
Requests/sec: 250.57
Transfer/sec: 0.98 GB

Середній час віддачі 4 Мб файлу скоротилася з 7.42 секунд до 226.32 мілісекунд, тобто ~33 рази, а кількість оброблюваних запитів в секунду зросла в 31 раз (250 проти 8)!

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

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

Правда в тому, що, на щастя, в більшості випадків операції з файлами не призводять до читання з повільного жорсткого диска. Якщо у вас вистачає оперативної пам'яті, то сучасні операційні системи досить розумні, щоб закешувати файли, до яких часто відбувається звернення в так званому кеші сторінок (page cache).

Кеш сторінок справляється досить добре і це завжди дозволяло NGINX демонструвати високу продуктивність в найпоширеніших ситуаціях. Читання з кешу сторінок відбувається дуже швидко і таку операцію не можна назвати блокує. У той же час, взаємодія з пулом потоків несе додаткові витрати на синхронізацію.

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

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

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

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

Справа в тому, що відсутня відповідна підтримка з боку ядра операційної системи. Перші спроби додати таку можливість в Linux у вигляді системного виклику fincore() відносяться до 2010 року, але «віз і нині там». Пізніше були спроби у вигляді системного виклику
preadv2()
та прапора
RWF_NONBLOCK
(подробиці можна дізнатися зі статей Non-blocking buffered file read операцій і Asynchronous buffered read операцій LWN.net але доля цих патчів і раніше під питанням. Сумно, що виною всьому цьому схоже є горезвісний байкшединг (суперечки про те, якого кольору фломастери краще пахнуть).

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

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

Конфігурація досить проста і разом з тим дуже гнучка. Для початку вам буде потрібно NGINX версії 1.7.11 або вище, зібраний з прапором
--with-threads
. У найпростішим випадку настройка виглядає елементарно. Все, що необхідно для включення вивантаження операцій читання і відправлення файлів в пул потоків, це директива
aio
на рівні
http
,
server
або
location
, встановлена в значення threads:

aio threads;

Це мінімально можливий варіант налаштування пулів потоків. Насправді він є скороченою версією такій конфігурації:

thread_pool default threads=32 max_queue=65536;
aio threads=default;

Вона задає пул потоків
default
, в якому працюватимуть 32 потоку і максимально допустимий розмір черги завдань складає 65536. Якщо черга завдань переповнюється, то NGINX відхиляє запит і логирует помилку:

thread pool "NAME" queue overflow: N tasks waiting

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

Як можна помітити, за допомогою директиви thread_pool у вас є можливість задавати кількість потоків, максимальний розмір черги, а також ім'я даного пулу потоків. Останнє передбачає можливість сконфігурувати кілька незалежних пулів і використовувати їх у різних частинах конфігурації для різних завдань:

http {
thread_pool one threads=128 max_queue=0;
thread_pool two threads=32;

server {
location /one {
aio threads=one;
}

location /two {
aio threads=two;
}
}
...
}

Якщо параметр
max_queue
не вказаний явно, як в пулі two, то використовується значення за замовчуванням, рівне 65536. Як видно з прикладу, можна задати нульовий розмір черги. У цьому випадку пул зможе одночасно приймати до обробки тільки таку кількість завдань, скільки у нього є вільних потоків і не буде очікують у черзі завдань.

А тепер уявіть, що у вас є сервер з трьома жорсткими дисками, який повинен виконувати роль кеширующего проксі для ваших бэкендов. При цьому передбачуваний розмір кешу багаторазово перевершує обсяг доступної оперативної пам'яті. По суті це щось на зразок кеш-ноди в вашої особистої мережі роздачі контенту (CDN). У цьому випадку основне навантаження по віддачі кешованих даних буде лягати на дискову підсистему. Зрозуміло ви хочете отримати максимум продуктивності з тих трьох дисків, що є в наявності.

Одним з рішень може стати організація RAID масиву. У такого підходу звичайно ж є свої плюси і мінуси. Але сьогодні NGINX готовий запропонувати вам інший підхід:

# В нашій системі кожний з жорстких дисків там в одну з наступних директорій:
# /mnt/disk1, /mnt/disk2 або /mnt/disk3 відповідно

proxy_cache_path /mnt/disk1 levels=1:2 keys_zone=cache_1:256m max_size=1024G use_temp_path=off;
proxy_cache_path /mnt/disk2 levels=1:2 keys_zone=cache_2:256m max_size=1024G use_temp_path=off;
proxy_cache_path /mnt/disk3 levels=1:2 keys_zone=cache_3:256m max_size=1024G use_temp_path=off;

thread_pool pool_1 threads=16;
thread_pool pool_2 threads=16;
thread_pool pool_3 threads=16;

split_clients $request_uri $disk {
33.3% 1;
33.3% 2;
* 3;
}

location / {
proxy_pass http://backend;
proxy_cache_key $request_uri;
proxy_cache cache_$disk;
aio threads=pool_$disk;
sendfile on;
}

У даній конфігурації використовується три незалежних кеша — по одному на кожен жорсткий диск, і три незалежних пулу потоків, також по одному на диск.

Для рівномірного розподілу навантаження між кэшами (а відповідно і жорсткими дисками) використовується модуль split_clients, який чудово для цього підходить.

Параметр
use_temp_path=off
у директиви proxy_cache_path інструктує NGINX зберігати тимчасові файли у тій же директорії, де знаходяться дані кеша. Це необхідно, щоб уникнути копіювання даних з одного диска на інший при збереженні відповіді в кеш.

Все це разом дозволяє вичавити максимум продуктивності даної дискової підсистеми, оскільки NGINX допомогою окремих пулів потоків взаємодіє з кожним диском паралельно і незалежно. Кожен диск обслуговують 16 незалежних потоків і для нього формується окрема черга завдань на читання і відправлення файлів.

Адже ваші клієнти люблять індивідуальних підхід? Будьте впевнені ваші жорсткі диски теж. ;)

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

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

Як вже згадувалося раніше, пули потоків можуть бути використані і для будь-яких інших операцій та роботи з бібліотеками, які не мають асинхронного інтерфейсу. Потенційно це відкриває нові можливості для реалізації модулів і функціональності, реалізація якої без шкоди для продуктивності раніше була нездійсненною в розумні терміни. Можна витратити багато зусиль і часу на написання асинхронного варіанти наявний бібліотеки або в спробах додати такий інтерфейс, але виникало питання: «чи варта гра свічок»? З пулами потоків дану задачу можна вирішити набагато простіше, створюючи модулі, які працюють з блокуючими викликами, і при цьому не заважаючи NGINX виконувати свою основну задачу обробляючи інші запити.

Таким чином, багато нового і цікавого чекає NGINX в майбутньому. Залишайтеся з нами!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.