MySQL обслуговує кожне клієнтське з'єднання окремим потоком. Кількість одночасних з'єднань обмежена max_connections - за замовчуванням 151. Коли ліміт вичерпано, нові підключення отримують ERROR 1040: Too many connections.
Резерв для адміністратора. MySQL дозволяє одне додаткове з'єднання понад ліміт для користувача з привілеєм CONNECTION_ADMIN, щоб у момент аварії можна було зайти й розібратися. Якщо застосунок підключається під root, він з'їсть і цей резерв. Ще надійніше - окремий адміністративний інтерфейс (admin_address, admin_port) зі своїм лімітом.
Як порахувати потребу. У PHP кожен процес, що обробляє запит, тримає власне з'єднання:
PHP-FPM: pm.max_children = 50 на сервер × 4 вебсервери = 200
воркери черг (Horizon): 30 процесів × 2 сервери = 60
планувальник, Pulse, разові команди ~ 10
─────
270 + запас
Для Octane/FrankenPHP у режимі воркерів - кількість воркерів на всіх інстансах: з'єднання живуть довго й тримаються постійно.
Чому не можна просто поставити 5000:
- кожне з'єднання займає пам'ять: буфер потоку плюс буфери сортування, з'єднань і читання, що виділяються під час запиту. Сотні з'єднань з великими запитами можуть вичерпати пам'ять і отримати OOM-killer;
- тисяча одночасно активних запитів не виконується швидше - вони конкурують за процесор, диск і блокування. Пропускна здатність падає, затримки ростуть.
Корисні дані - не скільки з'єднань відкрито, а скільки одночасно виконують запити:
SHOW GLOBAL STATUS WHERE variable_name IN
('Threads_connected', 'Threads_running', 'Max_used_connections',
'Max_used_connections_time', 'Aborted_connects', 'Connection_errors_max_connections');
Типові причини раптового вичерпання:
- повільні запити чи блокування: з'єднання не звільняються, а нові запити прибувають - лавина;
- витік з'єднань у воркерах (довгоживучі процеси, що відкривають нові підключення);
- різке масштабування вебсерверів чи воркерів без перегляду ліміту бази.
Рішення на рівні архітектури:
- пул з'єднань перед MySQL - ProxySQL чи MySQL Router: тисячі з'єднань від застосунку мультиплексуються в десятки до бази;
- пул потоків (thread pool) - у MySQL Enterprise, Percona Server і MariaDB: обмежує кількість одночасно виконуваних запитів незалежно від кількості з'єднань;
wait_timeout(за замовчуванням 8 годин) зменшити, щоб забуті неактивні з'єднання закривалися;- окремі ліміти на користувача:
ALTER USER ... WITH MAX_USER_CONNECTIONS 50- щоб аналітик чи воркер не забрав усі з'єднання в застосунку.