Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel

В екосистемі PHP на даний момент існує два конектори для роботи з сервером Tarantool - це офіційне розширення PECL tarantool/tarantool-php, написане на С, та tarantool-php/clientнаписаний на PHP. Я автор останнього.

У цій статті я хотів би поділитися результатами тестування продуктивності обох бібліотек і показати, як за допомогою мінімальних змін у коді можна досягти 3-5 приросту продуктивності (на синтетичних тестах!).

Що тестуватимемо?

Тестуватимемо згадані вище синхронні конектори, запущені асинхронно, паралельно та асинхронно-паралельно. 🙂 Також ми не хочемо чіпати код самих конекторів. На даний момент доступно кілька розширень, що дозволяють досягти бажаного:

  • Свул ― високопродуктивний асинхронний фреймворк для PHP. Використовується такими інтернет-гігантами як Alibaba та Baidu. З версії 4.1.0 з'явився чарівний метод SwooleRuntime::enableCoroutine(), що дозволяє «одним рядком коду перетворити синхронні мережеві бібліотеки PHP на асинхронні».
  • Async - донедавна досить перспективне розширення для асинхронної роботи в PHP. Чому до недавнього? На жаль, з невідомої причини, автор видалив репозиторій і подальша доля проекту туманна. Доведеться скористатися одним із форків. Як і Swoole, це розширення дозволяє легким рухом руки перетворити штани включити асинхронність заміною стандартної реалізації TCP та TLS потоків їх асинхронними версіями. Робиться це через опцію «async.tcp = 1".
  • Паралельні ― досить нове розширення від відомого Joe Watkins, автора таких бібліотек як phpdbg, apcu, pthreads, pcov, uopz. Розширення надає API для багатопотокової роботи в PHP і позиціонується як заміна pthreads. Істотним обмеженням бібліотеки є те, що вона працює тільки із ZTS (Zend Thread Safe) версією PHP.

Як тестуватимемо?

Запустимо екземпляр Tarantool'а з відключеним журналом попереджувального запису (wal_mode = none) та збільшеним мережевим буфером (readahead = 1*1024*1024). Перша опція виключить роботу з диском, друга — дасть змогу вичитувати більше запитів із буфера операційної системи та тим самим мінімізувати кількість системних викликів.

Для бенчмарків, які працюють з даними (вставка, видалення, читання і т.д.) перед стартом бенчмарку (пере) створюватиметься memtx-спейс, в якому значення первинного індексу створюються генератором упорядкованих значень цілих чисел (sequence).
DDL Спейс виглядає так:

space = box.schema.space.create(config.space_name, {id = config.space_id, temporary = true})
space:create_index('primary', {type = 'tree', parts = {1, 'unsigned'}, sequence = true})
space:format({{name = 'id', type = 'unsigned'}, {name = 'name', type = 'string', is_nullable = false}})

За потреби, перед запуском бенчмарку, спейс заповнюється 10,000 кортежами виду

{id, "tuplе_<id>"}

Доступ до кортежів здійснюється за рандомним значенням ключа.

Сам бенчмарк є одиничний запит до сервера, який виконується 10,000 разів (революцій), які, у свою чергу, виконуються в ітераціях. Ітерації повторюються до тих пір, поки всі відхилення між 5 ітераціями не виявляться в межах допустимої похибки в 3 %*. Після цього береться усереднений результат. Між ітераціями пауза за 1 секунду, щоб не дати процесору піти в throttling. Складальник сміття Lua відключається перед кожною ітерацією та примусово запускається після її завершення. PHP-процес запускається тільки з необхідними для бенчмарку розширеннями, з увімкненою буферизацією виводу та вимкненим збирачем сміття.

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

Тестове оточення

Опубліковані нижче результати були зроблені на MacBookPro (2015), операційна система Fedora 30 (версія ядра 5.3.8-200.fc30.x86_64). Tarantool запускався в докері з параметром «--network host".

Версії пакетів:

Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.3, build a872fc2f86
PHP: 7.3.11 (cli) (built: Oct 22 2019 08:11:04)
tarantool/client: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ патч для 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3

* На жаль, офіційний конектор не працює з версією PHP > 7.2. Щоб скомпілювати та запустити розширення на PHP 7.3, довелося скористатися патчем.

Результати

Синхронний режим

Протокол Tarantool'а використовує бінарний формат Пакет повідомлень для серіалізації повідомлень. У конекторі PECL серіалізація прихована глибоко в надрах бібліотеки та вплине на процес кодування з userland-коду не уявляється можливим. Конектор на чистому PHP, навпаки, надає можливість кастомізації процесу кодування розширенням стандартного кодувальника або можливістю використання реалізації. З коробки доступно два кодувальники, один заснований на msgpack/msgpack-php (Офіційне розширення MessagePack PECL), інший - на rybakit/msgpack (На чистому PHP).

Перед порівнянням конекторів виміряємо продуктивність MessagePack кодувальників для PHP-конектора і в подальших тестах будемо використовувати той, який покаже кращий результат:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Хоча PHP версія (Pure) і поступається розширенню PECL у швидкості, в реальних проектах я б все ж таки рекомендував використовувати саме rybakit/msgpack, тому що в офіційному розширенні MessagePack специфікація формату реалізована лише частково (наприклад, немає підтримки для користувача типів даних, без якої ви не зможете використовувати Decimal — новий тип даних, представлений у Tarantool 2.3) і має ряд інших проблем (включаючи проблеми сумісності з PHP 7.4). Ну і загалом, проект виглядає покинутим.

Отже, виміряємо продуктивність конекторів у синхронному режимі:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Як видно з графіка, конектор PECL (Tarantool) показує кращу продуктивність порівняно з конектором на PHP (Client). Але це й не дивно, враховуючи, що останній, крім того, що реалізований повільнішою мовою, виконує, по суті, більше роботи: при кожному виклику створюється новий об'єкт Запит и відповідь (у разі Select ― ще й Критерії, а у випадку Update/Upsert ― операції), окремі сутності Connection, Пакувальник и Handler теж додають оверхед. Очевидно, що за гнучкість доводиться платити. Однак, загалом, PHP-інтерпретатор показує хорошу продуктивність, хоч різниця і є, але вона незначна і, можливо, буде ще меншою при використанні preloading у PHP 7.4, не кажучи вже про JIT у PHP 8.

Рухаємось далі. У Tarantool 2.0 з'явилася підтримка SQL. Спробуємо виконати операції Select, Insert, Update та Delete використовуючи SQL-протокол та порівняти результати з noSQL (бінарними) еквівалентами:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Результати SQL не дуже вражають (нагадаю, що ми все ще тестуємо синхронний режим). Однак, я не став би засмучуватися з цього приводу раніше часу, підтримка SQL все ще знаходиться в активній розробці (щодо нещодавно, наприклад, додалася підтримка підготовлені заяви) і, судячи зі списку питання, двигун SQL надалі чекає ряд оптимізацій.

Асинхронізація

Ну що ж, побачимо тепер, як розширення Async зможе допомогти нам покращити результати вище. Для написання асинхронних програм розширення надає API на основі корутин (coroutines), ним і скористаємося. Досвідченим шляхом з'ясовуємо, що оптимальна кількість корутин для нашого оточення дорівнює 25:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
«Розмазуємо» 10,000 операцій з 25 корутин і дивимося, що вийшло:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Кількість операцій на секунду зросла більш ніж у 3 рази на tarantool-php/client!

Сумно, але конектор PECL не запустився з ext-async.

А що з SQL?

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Як бачите, в асинхронному режимі різниця між бінарним протоколом та SQL стала в межах похибки.

Свул

Знову з'ясовуємо оптимальну кількість корутин, тепер уже для Swoole:
Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Зупинимося на 25. Повторимо той самий трюк, що і з розширенням Async - розподілимо 10,000 операцій між 25 корутинами. Крім цього, додамо ще тест, в якому розділимо всю роботу на 2 два процеси (тобто кожен процес виконуватиме 5,000 операцій у 25 корутинах). Процеси будуть створюватися за допомогою SwooleProcess.

Результати:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Swole показує трохи нижчий результат у порівнянні з Async при запуску в одному процесі, але з 2 процесами картина змінюється кардинально (число 2 обрано не випадково, на моїй машині саме 2 процеси показали найкращий результат).

До речі, у розширенні Async також є API для роботи з процесами, проте там я не помітив якоїсь різниці від запуску бенчмарків в одному або кількох процесах (не виключено, що я десь накосячив).

SQL vs бінарний протокол:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Так само як і з Async, різниця між бінарними та SQL-операціями нівелюється в асинхронному режимі.

Паралельні

Так як розширення Parallel не про корутини, але про потоки, виміряємо оптимальну кількість паралельних потоків:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Воно дорівнює 16 на моїй машині. Запустимо бенчмарки конекторів на 16 паралельних потоках:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Як бачите, результат навіть краще, ніж з асинхронними розширеннями (крім Swoole запущеному на 2 процесах). Зауважте, що для конектора PECL на місці Update та Upsert операцій порожньо. Пов'язано це з тим, що ці операції вилетіли з помилкою - не знаю, з вини ext-parallel, ext-tarantool або обох.

Тепер порівняємо продуктивність SQL:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel
Помітили подібність із графіком для конекторів, запущених синхронно?

Всі разом

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

* Запустити корутини Swoole з Parallel не вдалося, схоже ці розширення несумісні.

Отже, фінальні результати:

Прискорюємо PHP-конектори для Tarantool за допомогою Async, Swoole та Parallel

Замість висновку

На мою думку, результати вийшли дуже гідні, і я чомусь впевнений, що це ще не межа! Чи потрібно це вам у реальному проекті вирішувати виключно вам самим, скажу лише, що для мене це був цікавий експеримент, що дає змогу оцінити, скільки можна «вичавити» із синхронного TCP-конектора з мінімальними зусиллями. Якщо у вас є ідеї щодо покращення бенчмарків – я з радістю розгляну ваш пул реквест. Весь код з інструкціями щодо запуску та результатами опубліковано в окремому репозиторії.

Джерело: habr.com

Додати коментар або відгук