Як працює традиційний PHP (FPM)

Модель FPM

Якби серверний PHP-застосунок був рестораном, його, напевно, вважали б елітним закладом, де кожний стіл обслуговує окремий офіціант.

Кожний новий запит до сервера обробляється окремою PHP VM, процесом або потоком, після чого стан знищується. Це еквівалентно тому, що офіціант обслуговує один стіл, а потім його звільняють або стирають пам’ять.

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

Останніми роками PHP-спільнота намагається впровадити stateful-модель, де одна PHP VM може обслуговувати кілька запитів, зберігаючи стан між ними. Наприклад, проєкт Laravel Octane, який використовує Swoole або RoadRunner, досягає кращої продуктивності завдяки збереженню стану між запитами. Але це далеко не межа можливого.

Звільняти офіціанта після кожного замовлення – занадто дорого. Оскільки страви готуються на кухні повільно, офіціант більшість часу чекає. Те саме відбувається з PHP-FPM: PHP VM простоює. Більше перемикань контексту, більше накладних витрат на створення та знищення процесів або потоків, більше споживання ресурсів.

// Традиційний PHP-FPM
$user = file_get_contents('https://api/user/123');     // стоїмо і чекаємо 300мс
$orders = $db->query('SELECT * FROM orders');          // стоїмо і чекаємо 150мс
$balance = file_get_contents('https://api/balance');   // стоїмо і чекаємо 200мс

// Витрачено: 650мс чистого очікування
// CPU простоює. Пам'ять простоює. Все чекає.

Конкурентність

Модель конкурентності

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

Ця схема може працювати досить гнучко: Стіл 1 замовив три страви. Стіл 2 замовив дві страви. Офіціант приносить першу страву столу 1, потім першу страву столу 2. А може, він встиг принести дві страви першому столу і одну другому. Або навпаки!

Це конкурентність: спільне використання одного ресурсу (CPU) між різними логічними потоками виконання, які називаються корутинами.

use function Async\spawn;
use function Async\await;

// Запускаємо всі три запити "конкурентно"
$userTask = spawn(file_get_contents(...), 'https://api/user/123');
$ordersTask = spawn($db->query(...), 'SELECT * FROM orders');
$balanceTask = spawn(file_get_contents(...), 'https://api/balance');

// Поки один запит чекає відповіді, ми виконуємо інші!
$user = await($userTask);
$orders = await($ordersTask);
$balance = await($balanceTask);

// Витрачено: 300мс (час найповільнішого запиту)

Конкурентність – це не паралелізм

Важливо розуміти різницю.

Конкурентність – як у True Async, JavaScript, Python:

Паралелізм – це багатопотоковість (Go):

Що далі?

Тепер ви розумієте суть. Можете заглибитись: