Як працює традиційний PHP (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:
- Один офіціант швидко перемикається між столами
- Один PHP-потік перемикається між завданнями
- Завдання чергуються, але не виконуються одночасно
- Немає гонок даних – в кожний момент часу працює лише одна корутина
Паралелізм – це багатопотоковість (Go):
- Кілька офіціантів працюють одночасно
- Кілька потоків виконуються на різних ядрах CPU
- Завдання виконуються справді одночасно
- Потрібні м’ютекси, блокування, всі ці складнощі
Що далі?
Тепер ви розумієте суть. Можете заглибитись:
- Ефективність – скільки корутин потрібно для максимальної продуктивності
- Доказова база – виміри, бенчмарки та дослідження, що підтверджують ефективність корутин
- Swoole на практиці – реальні виміри: Appwrite +91%, IdleMMO 35М запитів/день, бенчмарки з БД
- Python asyncio на практиці – Duolingo +40%, Super.com -90% витрат, Instagram, бенчмарки uvloop
- Корутини – як вони працюють під капотом
- Scope – як керувати групами корутин
- Планувальник – хто вирішує, яку корутину запускати