Горизонтальне масштабування - запустити кілька однакових контейнерів і розподіляти між ними запити. Працює, лише якщо контейнер без стану (stateless): будь-який запит може обробити будь-яка копія.
Що заважає масштабуванню і куди це винести:
| Стан у контейнері | Куди винести |
|---|---|
| сесії у файлах | Redis чи база даних (SESSION_DRIVER=redis) |
| кеш у файлах | Redis, Memcached |
завантажені файли в storage/app |
об'єктне сховище (S3, R2) чи спільний том |
черги в синхронному режимі чи database на SQLite |
Redis чи база даних, окремі воркери |
| планувальник у кожному контейнері | окремий контейнер чи onOneServer() |
| блокування у файлах | блокування через Redis/базу |
Типові наслідки, якщо цього не зробити:
- користувача «розлогінює» при кожному запиті - сесія на іншій копії;
- завантажений аватар то є, то немає;
- запланована задача виконується стільки разів, скільки контейнерів (листи - вчетверо);
- кеш скидається лише на одній копії.
Масштабування в різних середовищах:
docker compose up -d --scale app=4 # кілька копій на одному сервері
docker service scale myapp_app=4 # Swarm
kubectl scale deployment app --replicas=4 # Kubernetes
На одному сервері кілька копій мають сенс, лише якщо одна копія не використовує всі ядра; справжня користь - копії на різних серверах за балансувальником.
Що ще враховувати:
- з'єднання з базою: кожна копія тримає свій пул - 10 копій по 20 процесів PHP-FPM = 200 з'єднань. База може не витримати раніше, ніж закінчаться ресурси застосунку;
- база - не масштабується так само: вузьке місце переміщується туди - репліки для читання, кеш, оптимізація запитів;
- довірені проксі - за балансувальником застосунок має бачити справжні IP і протокол;
- одноразові задачі при старті (міграції, прогрів кешу) не повинні виконуватися кожною копією.
Для Laravel контейнер з php-fpm чи FrankenPHP, сесіями й кешем у Redis, файлами в S3 і окремими контейнерами для воркерів і планувальника - класична конфігурація, що масштабується.