Моніторинг MySQL і Percona Server: знаходьте критичні запити без здогадок

Було корисно?

У продуктивному середовищі MySQL рідко «падає» з драмою. Він виходить із ладу нудно: p95 латентності тихо подвоюється, CPU виглядає «нормально», і продуктова команда клянеться: «ми нічого не деплоїли». Тим часом база даних горить — ввічливо.

Різниця між двогодинним інцидентом і десятихвилинним фіксом майже ніколи не в сировинних навичках. Вона в тому, чи показує ваш моніторинг який запит є винуватцем, чому він повільний (CPU, I/O, блокування або реплікація) і що змінилося.

Що ви насправді намагаєтеся зробити (і чому здогадки коштують дорого)

«Знайти критичні запити» звучить як одна задача, але в експлуатації це три окремі роботи:

  1. Виявлення: ідентифікувати кілька операторів, які домінують у латентності, CPU, I/O або часі блокувань.
  2. Атрибуція: пов’язати ці оператори з дією в застосунку, деплоєм, feature-flag, cron-завданням, аналітичною записною книжкою або неправильно налаштованим ORM.
  3. Практична застосовність: вибрати правильне виправлення — індекс, перепис запиту, стабільність плану, ліміти конкурентності, буферизація або зміни схеми — не зламавши все інше.

Пастка «здогадок» виникає, коли ваш моніторинг показує лише метрики хоста (CPU, диск, мережа). Вони потрібні, але недостатні. Метрики хоста кажуть вам що база незадоволена. Вони не кажуть, хто її розлютив.

Вам потрібні три рівні видимості:

  • SQL-рівень: нормалізовані відбитки запитів, розподіл латентності, порівняння rows examined vs returned, тимчасові таблиці, злиття сортування, помилки.
  • Рівень очікувань (wait): час, витрачений на блокування, I/O, metadata locks, buffer pool, redo log і внутрішні mutex.
  • Системний рівень: насичення CPU, довжина черги виконання, латентність I/O, час fsync, тиск на кеш сторінок, повтори в мережі.

MySQL може це робити. Percona Server теж. Практична різниця — скільки ви можете зробити до інциденту, наскільки коштовна інструментальна база і наскільки легко ви зшиваєте докази в часову лінію.

MySQL vs Percona Server: що змінюється для моніторингу

Це родичі, а не чужі

Percona Server для MySQL — це downstream-збірка, яка слідкує за upstream MySQL близько, з додатковою інструментацією та операційними можливостями. У сучасних парках ви побачите:

  • Oracle MySQL (community або enterprise): базові можливості, Performance Schema, sys schema, slow log, EXPLAIN, optimizer trace тощо.
  • Percona Server: сумісний з upstream, плюс операційні поліпшення та додаткові налаштування (деякі вже не такі «унікальні», як раніше, але все ще важливі в продакшені).
  • Percona Monitoring and Management (PMM): не форк сервера, а стек моніторингу, який робить аналітику запитів зручною для людей.

Різниці в моніторингу, що мають значення на практиці

Головне питання не «який швидший?», а «який робить root cause очевидним під стресом?» Оцінка така:

  • Досвід аналізу запитів: з ванільним MySQL ви можете цього досягти за допомогою slow logs + Performance Schema + кастомних дашбордів. З Percona Server плюс PMM ви зазвичай дістаєтеся до «топ-10 найгірших відбитків запитів» швидше й з меншим об’ємом власної інфраструктури.
  • Контроль витрат інструментації: обидва можна налаштувати, щоб уникати великого оверхеду, але екосистема Percona частіше робить «низький оверхед, завжди увімкнено» культурною звичкою, а не лише презентацією в слайді.
  • Додаткові метрики та налаштування: Percona Server історично постачав додаткові змінні статусу та можливості для інструментації й діагностики. Навіть коли upstream наздоганяє, Percona зберігає операційний нахил: відкриває гострі краєчки, не ховає їх.

Суб’єктивна порада: якщо ви запускаєте MySQL у помірному масштабі і вже маєте хорошу дисципліну спостереження, upstream MySQL підходить. Якщо ви працюєте в режимі «чому це завжди терміново» і хочете аналітику запитів, яку on-call може використати о 3 ранку, Percona Server + PMM важко переоцінити.

Суха правда: більшість інцидентів спричинені не «поганими запитами» самі по собі. Вони спричинені поганими запитами при поганій конкурентності, або адекватним запитом, що раптом отримав інший план, або маленьким блокуванням, яке перетворюється на глобальний затор трафіку.

Жарт #1: Запит не є «повільним», поки його не запустили в циклі хтось, хто щойно відкрив аналітику.

План швидкої діагностики (першочергове/друге/третє)

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

Перше: класифікуйте вузьке місце (60–120 секунд)

  • CPU-загальне? Високий user CPU, висока черга виконання, стабільна латентність диска.
  • I/O-залежність? Помірний CPU, але підвищена read/write латентність, високий час fsync, пропуски buffer pool.
  • Блокування? Активних потоків мало, підключень багато, багато сесій «Waiting for…»; стрибки в очікуванні блокувань.
  • Реплікація? Зростає відставання реплік, relay logs ростуть, SQL-потік чекає; або міський первинний вузол OK, але репліки повільні через об’єм запитів.

Друге: ідентифікуйте головних правопорушників (2–5 хвилин)

  • Використовуйте slow query log (якщо увімкнено) для отримання відбитків і таймінгів.
  • Використовуйте Performance Schema, щоб перелічити топ-операторів за загальним часом, середнім часом і rows examined.
  • Перевірте активні сесії: що виконується зараз і на що вони чекають?

Третє: підтвердіть режим помилки (5–15 хвилин)

  • Для CPU-проблем: перевірте регресії плану, відсутні індекси, погані предикати, некоректні оцінки кардинальностей.
  • Для I/O-проблем: перевірте проксі-метрики попадань у buffer pool, read-ahead, виливання temp table, тиск redo log, латентність fsync.
  • Для блокувань: ідентифікуйте блокуючу сесію, тип блокування (рядок, metadata, gap/next-key) і чому воно тримається.
  • Для реплікації: порівняйте склад команд primary vs replica; знайдіть довгу транзакцію або DDL; перевірте конфігурацію паралельної реплікації.

Не «оптимізуйте навмання». Спочатку діагностуйте, потім змініть одну річ, потім виміряйте. Ви керуєте системою, не займаєтесь імпровізацією.

Цікаві факти та історичний контекст (бо сучасні налаштування мають історію)

  • Факт 1: MySQL slow query log передував сучасним стеком спостереження; це старомодне текстове логування, але одне з найкорисніших під час інцидентів, бо фіксує реальні запити з таймінгами.
  • Факт 2: Performance Schema починався як контроверсійна функція, бо ранні версії могли бути важкими, якщо їх увімкнути без розбору. Сучасний MySQL зробив її значно практичнішою як «завжди ввімкнене» джерело істини — якщо її налаштовувати з розумом.
  • Факт 3: sys schema створили, щоб зробити Performance Schema придатним без того, щоб кожен писав свої монструозні SQL-запити. Це фактично «представи для людей».
  • Факт 4: Percona популяризувала діджестинг запитів у світі MySQL з інструментами на кшталт pt-query-digest, який нормалізував запити у відбитки задовго до того, як «query analytics» стало пунктом у дашборді.
  • Факт 5: MySQL metadata locks (MDL) часто дивують: безневинний ALTER TABLE може блокувати читання так, що здається проблемою мережі, поки ви не переглянете MDL waits.
  • Факт 6: Дизайн InnoDB дуже орієнтований на «buffer pool перш за все». Якщо ваш робочий набір не вміщується і ви робите випадкові читання, ви відчуєте це навіть на преміальних дисках — бо фізика бере плату мілісекундами.
  • Факт 7: Відставання реплікації часто не є «мережевою затримкою». Частіше це «SQL-потік репліки не встигає», бо репліка виконує занадто багато роботи на транзакцію через різні I/O або умови кешування.
  • Факт 8: Переход від файлових тимчасових таблиць до більш «в пам’яті» поведінки тимчасових таблиць у різних версіях зменшив певні болі, але «тимчасова таблиця в пам’яті» не безкоштовна; вона може спричинити тиск на пам’ять, який стає свопінгом і приводить до інциденту.

Практичні завдання: команди, виводи та рішення (12+)

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

Завдання 1: Перевірте, на якому сервері ви знаходитесь (і припиніть сперечатися у Slack)

cr0x@server:~$ mysql -NBe "SELECT VERSION(), @@version_comment;"
8.0.36-28 Percona Server (GPL), Release 28, Revision 1234567

Що це означає: Рядок версії і коментар показують, чи це Oracle MySQL, Percona Server, MariaDB тощо. Це важливо, бо можливості й значення за замовчуванням відрізняються.

Рішення: Виберіть правильний плейбук. Не застосовуйте tunning-флаг, якого немає в цій збірці.

Завдання 2: Перевірте, чи ввімкнено Performance Schema

cr0x@server:~$ mysql -NBe "SHOW VARIABLES LIKE 'performance_schema';"
performance_schema	ON

Що це означає: Якщо OFF, ви щойно втратили одне з найкращих джерел «що сервер робить прямо зараз?»

Рішення: Якщо у production OFF, заплануйте maintenance-вікно для увімкнення і тримайте її ввімкненою з обережними налаштуваннями інструментації.

Завдання 3: Подивіться топ-операторів за загальною латентністю (Performance Schema)

cr0x@server:~$ mysql -t -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, ROUND(AVG_TIMER_WAIT/1e9,2) AS avg_ms, SUM_ROWS_EXAMINED, SUM_ROWS_SENT FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 48291
total_s: 912.44
avg_ms: 18.89
SUM_ROWS_EXAMINED: 987654321
SUM_ROWS_SENT: 48291

Що це означає: Цей запит домінує за загальним часом і перевіряє абсурдну кількість рядків на результат. Це ваш провідний слід.

Рішення: Дослідіть індексування і план. Зокрема перевірте наявність композитного індексу на (user_id, created_at) і підтвердіть, що він використовується.

Завдання 4: Знайдіть «що виконується прямо зараз» з повними запитами

cr0x@server:~$ mysql -t -e "SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, LEFT(INFO,120) AS query FROM information_schema.PROCESSLIST WHERE COMMAND != 'Sleep' ORDER BY TIME DESC LIMIT 10;"
+------+---------+-------------------+------+---------+------+----------------------------+----------------------------------------------------------+
| ID   | USER    | HOST              | DB   | COMMAND | TIME | STATE                      | query                                                    |
+------+---------+-------------------+------+---------+------+----------------------------+----------------------------------------------------------+
| 8123 | appuser | 10.2.3.4:53210    | prod | Query   |  98  | Waiting for table metadata | ALTER TABLE orders ADD COLUMN foo INT                   |
| 8221 | appuser | 10.2.3.9:49211    | prod | Query   |  97  | Sending data               | SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 50 |
+------+---------+-------------------+------+---------+------+----------------------------+----------------------------------------------------------+

Що це означає: DDL чекає на metadata lock, у той час як запити продовжують виконуватися, або навпаки. Наявність «Waiting for table metadata» — це великий вказівник на MDL-конкуренцію.

Рішення: Якщо DDL блокує критичний трафік — зупиніть його (або перенесіть на інструменти онлайн-зміни схеми). Якщо DDL заблоковано — знайдіть сесію, що утримує MDL.

Завдання 5: Ідентифікуйте блокуючі metadata locks (MDL)

cr0x@server:~$ mysql -t -e "SELECT * FROM sys.schema_table_lock_waits WHERE object_schema='prod' AND object_name='orders'\G"
*************************** 1. row ***************************
object_schema: prod
object_name: orders
waiting_query: ALTER TABLE orders ADD COLUMN foo INT
waiting_pid: 8123
blocking_pid: 7991
blocking_query: SELECT COUNT(*) FROM orders WHERE created_at > NOW() - INTERVAL 7 DAY
sql_kill_blocking_query: KILL QUERY 7991
sql_kill_blocking_connection: KILL 7991

Що це означає: Один довгий SELECT утримує metadata lock досить довго, щоб блокувати DDL (або навпаки). sys schema робить це читабельним.

Рішення: Якщо безпечно — вбийте блокуючий запит (не підключення, якщо не потрібно). Потім виправте шаблон у застосунку: довгі транзакції та «SELECT … FOR UPDATE» тримають MDL довше, ніж думають люди.

Завдання 6: Перевірте, чи увімкнено slow query log і чи має розумні налаштування

cr0x@server:~$ mysql -NBe "SHOW VARIABLES WHERE Variable_name IN ('slow_query_log','slow_query_log_file','long_query_time','log_queries_not_using_indexes');"
slow_query_log	ON
slow_query_log_file	/var/log/mysql/mysql-slow.log
long_query_time	0.200000
log_queries_not_using_indexes	OFF

Що це означає: Логуєте запити повільніші за 200ms. Це достатньо агресивно, щоб впіймати «смерть тисячі порізів», але не настільки, щоб ви розплавили диск.

Рішення: Якщо OFF — увімкніть. Якщо long_query_time = 10 секунд, ви не бачите поширених вбивць.

Завдання 7: Зробіть діджест slow log у ранжований список (Percona Toolkit)

cr0x@server:~$ pt-query-digest /var/log/mysql/mysql-slow.log | head -n 30
# 330ms user time, 20ms system time, 25.00M rss, 80.00M vsz
# Current date: Mon Dec 30 01:12:14 2025
# Overall: 12.34k total, 45 unique, 0.12 QPS, 0.02x concurrency ________
# Time range: 2025-12-30T00:10:00 to 2025-12-30T01:10:00
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time        8200s    5ms     38s    663ms     2s      1s     120ms
# Rows examine     9.10G     0     12M   737k     3.2M   1.1M     55k
# Profile
# Rank Query ID           Response time Calls R/Call V/M   Item
# ==== ================== ============= ===== ====== ===== ============
#    1 0xA1B2C3D4E5F6A7B8  4100.0000 50.0%  48291 0.0850 0.10  SELECT orders

Що це означає: Ви отримуєте ранжований список за часом реагування і нормалізований ID запиту. «Rows examine 9.10G» — це місце події.

Рішення: Зосередьтесь на топ-1–3 відбитках. Не ганяйтеся за хвостом розподілу під час інциденту.

Завдання 8: Перевірте тиск на InnoDB buffer pool (швидкі проксі-метрики)

cr0x@server:~$ mysql -t -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name                         | Value     |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests      | 987654321 |
| Innodb_buffer_pool_reads              | 12345678  |
+---------------------------------------+-----------+

Що це означає: Innodb_buffer_pool_reads — це читання з диска; read_requests — логічні читання. Якщо дискові читання стрибають відносно запитів, ваш робочий набір не в кеші.

Рішення: Якщо раптово читаєте з диска, ідентифікуйте, що змінилося: новий патерн запитів, втрата кеша після рестарту або зростання датасету. Розгляньте збільшення buffer pool (якщо безпечно), але також виправте запит, що сканує.

Завдання 9: Ідентифікуйте очікування блокувань на рівні двигуна

cr0x@server:~$ mysql -t -e "SELECT * FROM sys.innodb_lock_waits ORDER BY wait_age DESC LIMIT 5\G"
*************************** 1. row ***************************
wait_started: 2025-12-30 01:09:01
wait_age: 00:00:47
locked_table: `prod`.`orders`
locked_index: PRIMARY
waiting_query: UPDATE orders SET status='shipped' WHERE id=?
blocking_query: UPDATE orders SET status='paid' WHERE id=?
blocking_pid: 7442
waiting_pid: 7551

Що це означає: У вас транзакційна конкуренція. Два оновлення б’ються за ті самі рядки або «гарячі» діапазони індексів.

Рішення: Знайдіть патерн. Чи це таблиця-черга? «Гаряча» остання рядок? Застосуйте батчинг, переставте операції, зменшіть область транзакції або перерахуйте точку конкуренції.

Завдання 10: Підтвердіть, чи тимчасові таблиці ллються на диск

cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';"
Created_tmp_disk_tables	184223
Created_tmp_tables	210001
Created_tmp_files	1123

Що це означає: Високе співвідношення дискових тимчасових таблиць свідчить про дорогі сортування/group-by або недостатні налаштування пам’яті для тимчасових таблиць.

Рішення: Ідентифікуйте, які запити створюють тимчасові таблиці (Performance Schema допоможе), потім виправте форму запитів і індекси, перш ніж бездумно збільшувати ліміти пам’яті.

Завдання 11: Подивіться топ подій очікування (Performance Schema)

cr0x@server:~$ mysql -t -e "SELECT event_name, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, COUNT_STAR FROM performance_schema.events_waits_summary_global_by_event_name ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;"
+------------------------------------------+---------+------------+
| event_name                               | total_s | COUNT_STAR |
+------------------------------------------+---------+------------+
| wait/io/file/innodb/innodb_data_file     |  820.11 |   91234567 |
| wait/synch/mutex/innodb/buf_pool_mutex   |  210.44 |   61234567 |
| wait/lock/metadata/sql/mdl               |   98.22 |     120345 |
+------------------------------------------+---------+------------+

Що це означає: Ваш час іде в файловий I/O і конкуренцію mutex buffer pool, з боковою часткою metadata locks. Це корисний наратив.

Рішення: Якщо домінує I/O — шукайте скани й виливання. Якщо mutex домінує — перевірте конкурентність, кількість buffer pool instances і «гарячі» сторінки. Якщо MDL домінує — виправте планування DDL і довгі транзакції.

Завдання 12: Виявлення відставання реплікації і хто в цьому винен

cr0x@server:~$ mysql -t -e "SHOW REPLICA STATUS\G" | egrep -i "Seconds_Behind_Source|Replica_SQL_Running_State|Last_SQL_Error|Retrieved_Gtid_Set|Executed_Gtid_Set"
Seconds_Behind_Source: 187
Replica_SQL_Running_State: Waiting for dependent transaction to commit
Last_SQL_Error:
Retrieved_Gtid_Set: 1-1000023
Executed_Gtid_Set: 1-999842

Що це означає: Репліка відстає. SQL-потік чекає на commit (часто через затримку коміту/флаш або залежності транзакцій).

Рішення: Перевірте латентність коміту і довгі транзакції. Розгляньте налаштування надійності (durability) лише з явним прийняттям ризику. Частіше: виправте патерн транзакцій, що створив відставання.

Завдання 13: Корелюйте час запиту з rows examined (знайдіть «виглядає швидким», але не є)

cr0x@server:~$ mysql -t -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(AVG_TIMER_WAIT/1e9,2) AS avg_ms, ROUND(SUM_ROWS_EXAMINED/COUNT_STAR,0) AS rows_examined_per_call FROM performance_schema.events_statements_summary_by_digest WHERE COUNT_STAR > 1000 ORDER BY rows_examined_per_call DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT id FROM sessions WHERE token = ?
COUNT_STAR: 450000
avg_ms: 1.20
rows_examined_per_call: 12000

Що це означає: Середня латентність низька, але за виклик перевіряється 12k рядків. Під навантаженням це стає шліфуванням CPU і buffer pool.

Рішення: Додайте або виправте індекс на token. Це профілактична робота, яка запобігає майбутньому інциденту.

Завдання 14: Підтвердіть вибір плану з EXPLAIN (і не довіряйте інтуїції)

cr0x@server:~$ mysql -t -e "EXPLAIN SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 50;"
+----+-------------+--------+------------+------+--------------------------+--------------------------+---------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys            | key                      | key_len | rows | filtered | Extra       |
+----+-------------+--------+------------+------+--------------------------+--------------------------+---------+------+----------+-------------+
|  1 | SIMPLE      | orders | NULL       | ref  | idx_user_created,idx_user| idx_user_created         | 8       |  50  |   100.00 | Using index |
+----+-------------+--------+------------+------+--------------------------+--------------------------+---------+------+----------+-------------+

Що це означає: Використовує композитний індекс і читає ~50 рядків. Якщо ваші попередні метрики показували мільйони examined, ви або дивитесь інший шаблон запиту, або має місце розкид параметрів чи нестабільність плану.

Рішення: Порівняйте точний текст запиту і шаблони параметрів. Дослідіть гістограми/статистику і чи є запити sargable.

Завдання 15: Перевірте латентність файлового I/O в моменті (бо «диск в порядку» — це брехня до перевірки)

cr0x@server:~$ iostat -x 1 3
Linux 6.5.0 (db01) 	12/30/2025 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          32.10    0.00    6.20   18.40    0.00   43.30

Device            r/s     w/s   rMB/s   wMB/s  avgrq-sz  avgqu-sz   await  r_await  w_await  svctm  %util
nvme0n1         820.0  1200.0   38.2    95.1     92.1      8.20   12.40    9.10    14.60   0.35  72.0

Що це означає: 12ms await на NVMe підозріло, з iowait 18%. Це історія I/O, а не «лише CPU».

Рішення: Підніміться по стеку: які запити спричиняють read amplification або виливання тимчасових таблиць? Якщо це спільне сховище, перевірте «шумних сусідів».

Завдання 16: Якщо у вас є PMM: підтвердьте, що QAN інжестить і покажіть топ-запити

cr0x@server:~$ pmm-admin list
pmm-admin 2.41.0

Service type  Service name  Address and Port  Service ID
MySQL         db01          127.0.0.1:3306    /service_id/abcd1234

Agent type   Status     Agent ID
pmm-agent    Running    /agent_id/efgh5678

Що це означає: PMM бачить MySQL-сервіс. Якщо Query Analytics не показує даних, зазвичай це проблема колектора/конфігу, а не «немає повільних запитів».

Рішення: Використовуйте PMM QAN, щоб pivote від відбитка → прикладів → хостів → проміжку часу. Це найкоротший шлях від болю до винуватця.

Відбитки критичних запитів: як розпізнати клас проблеми

1) «Цунамі rows examined»

Підпис: Високий SUM_ROWS_EXAMINED, помірна кількість повернутих рядків, висока загальна латентність. CPU піднімається, кількість читань buffer pool зростає, слідом можуть піти дискові читання.

Причини: Відсутній композитний індекс, non-sargable предикати, неузгоджені типи, неявні конверсії типів, початковий wildcard у LIKE '%foo' або ORM переписав запит у форму, яку оптимізатор не любить.

Виправлення: Створіть правильний індекс, перепишіть предикат у sargable вигляд, вирівняйте типи і підтвердіть через EXPLAIN. Потім стежте за регресіями планів після зміни статистики.

2) «Конвой блокувань»

Підпис: Багато сесій у станах «Waiting for…», низький CPU, пропускна здатність падає. БД виглядає бездіяльною, поки всі чекають.

Причини: Гарячі рядки, послідовні оновлення, довгі транзакції, gap locks у REPEATABLE READ або DDL, що конфліктує з трафіком через MDL.

Виправлення: Зменшіть область транзакцій, переставте операції, додайте індекси, щоб зменшити блоковані діапазони, використовуйте онлайн-інструменти зміни схеми і плануйте DDL як зміни в продакшені (бо це так і є).

3) «Фабрика виливання тимчасових таблиць»

Підпис: Зростання Created_tmp_disk_tables, підвищення латентності I/O, запити з GROUP BY/ORDER BY уповільнюються, диски стають галасливішими.

Причини: Відсутні індекси для сортування/групування, вибір надто багатьох колонок, великі результати або недостатні пам’ятні налаштування для тимчасових таблиць.

Виправлення: Індексувати для задоволення ORDER BY/GROUP BY, зменшити кількість полів у SELECT, попередньо агрегувати або рефакторити. Збільшення пам’яті лише після зупинки витоків.

4) «Відставання реплікації, яке маскується під уповільнення застосунку»

Підпис: Читання, спрямовані на репліки, повільні або застарілі; або фейловери ризикові через велике відставання.

Причини: Одна велика транзакція, тиск на коміт, залежності транзакцій, або репліка з іншим I/O/кеш-профілем, ніж первинний вузол.

Виправлення: Розбийте великі записи, налаштуйте паралелізм реплікації відповідно, і забезпечте репліки зі схожою продуктивністю зберігання (або відрегулюйте маршрутизацію).

5) «План змінився і ніхто не помітив»

Підпис: Той самий відбиток запиту, але раптом гірше. EXPLAIN показує інший індекс або порядок з’єднання, ніж тиждень тому.

Причини: Змінились статистики, змінилася розподільність даних або апгрейд версії змінив поведінку оптимізатора.

Виправлення: Оновіть статистику свідомо, розгляньте гістограми, стабілізуйте з підказками (hints) лише при необхідності, і відстежуйте дрейф планів для критичних запитів.

Жарт #2: Оптимізатор як кіт: іноді геніальний, іноді спить на клавіатурі, і він не пояснить себе.

Три короткі корпоративні історії з польових боїв

Коротка історія 1: Інцидент через хибне припущення

Компанія мала чітке розділення: записи на primary, читання на репліки. Припущення було простим і заспокійливим: «читання не можуть нашкодити записам». Це стало політикою.

Потім чек-аут почав таймаутитись. CPU primary не був завантажений, але латентність коміту зросла. Репліки були в порядку. Мережа була в порядку. Команда застосунку клялася, що це «тільки кілька додаткових читань».

Performance Schema показала істину: новий «read-only» endpoint виконував multi-table join з відсутнім предикатом, скануючи великі діапазони. Він виконувався на primary у fallback-шляху, коли пул реплік був виснажений. Сам по собі запит не був дуже повільним на один виклик — але частота його виконання витісняла гарячі сторінки і створювала турбулентність у buffer pool.

Неправильне припущення було не про «читання», а про де читання відбуваються під частковою відмовою. Вичерпання пула підключень, відставання реплік, DNS-флапи — будь-що може націлити читання на primary. Запит став проблемою write-path, руйнуючи локальність кеша і підвищуючи I/O.

Вони виправили це, додавши відсутній предикат і індекс, а також реалізувавши circuit breaker: якщо репліки недоступні, деградувати фічу замість мовчазного тиску на primary. Моніторинг оновили, щоб сигналізувати про «читальні відбитки на primary» як критичний ризик.

Коротка історія 2: Оптимізація, що зіграла проти

Команда хотіла знизити p99 для пошукового endpoint. Вони додали індекс, який здавався очевидно правильним, і він покращив тести. Деплой пройшов, святкування.

Два дні потому write-навантаження почало періодично застрягати. CPU бази ріс, потім падав, але пропускна здатність стрибала. Slow query log не показував «поганих запитів». Це підказка: біль не в часі виконання запиту, а в write amplification.

Новий індекс помножив вартість оновлень. Фонове завдання, що зачіпало багато рядків, тепер повинно було підтримувати ще одну структуру з поганою локальністю. Buffer pool почав «стряскатися» сторінками індексу. Performance Schema показав збільшену конкуренцію mutex навколо структур buffer pool, а iostat показав підвищену write латентність під час спалахів.

Виправлення не було «видалити індекси». Це було: зробити задачу менш руйнівною (батчинг, нижча конкурентність) і переробити індекс під реальні предикати. Вони замінили один широкий індекс на вузький композитний, що задовольнив шлях читання без значних штрафів для записів.

Урок: «оптимізація» — це зміна компромісів. Якщо ви не вимірюєте шлях записів, ви не оптимізуєте — ви граєте у рулетку.

Коротка історія 3: Скучно, але правильна практика, що врятувала день

Інша організація мала ритуал, про який ніхто не хвалився: вони тримали slow query logging увімкненим на низькому порозі, правильно ротували логи і щотижня аналізували результати. Ніяких героїзмів, лише гігієна.

Одного п’ятничного пополудня p95 латентності підскочив. On-call відкрив PMM Query Analytics і за хвилину побачив новий відбиток на вершині. Це був запит, доданий у мажорному релізі: lookup по колонці token, але token зберігається в іншому типі, ніж параметр, що спричинило неявну конверсію і вбило використання індексу.

Оскільки slow logs і діджести були нормою, у них були базові лінії. Вони довели, що «цього запиту не було вчора» і зв’язали його з деплоєм. Відкат був миттєвим. Потім команда додала правильний індекс і вирівняла типи у наступному патчі.

Ніякої війни в кімнаті. Ніяких загальнокорпоративних зборів. Виправлення майже нудне. Ось у цьому і суть: нудна спостережливість запобігає гучним аваріям.

Парафразована ідея від Gene Kranz: тверді й компетентні — зберігайте спокій, дійте за процедурою і довіряйте даним, а не відчуттям.

Поширені помилки: симптом → корінь проблеми → виправлення

1) Симптом: CPU високий, але slow query log тихий

Корінь: Багато середньо-повільних запитів під порогом або оверхед у парсингу/виконанні, який не потрапляє через велике long_query_time.

Виправлення: Тимчасово знизьте long_query_time до 200–500ms або увімкніть агрегування Performance Schema digest і ранжуйте за загальним часом.

2) Симптом: Threads connected високо, Threads running низько, латентність жахлива

Корінь: Очікування блокувань або ресурсів (I/O, metadata locks). Усі стоять у черзі.

Виправлення: Використовуйте sys views для lock waits і MDL waits; знайдіть блокера; зменшіть довжину транзакцій; плануйте DDL правильно.

3) Симптом: Запити «раптом» повільні після рестарту

Корінь: Холодний buffer pool; робочий набір не прогрітий; раптові дискові читання вибухають.

Виправлення: Очікуйте це і плануйте (стратегії прогріву, достатній buffer pool, резерв потужності). Також перевірте, чи ви не втратили локальність кешу через нові скани.

4) Симптом: Відставання реплік під час пакетної роботи

Корінь: Великі транзакції або високий rate комітів насичують застосування/флаш репліки, або залежності обмежують паралелізм.

Виправлення: Розбийте записи на менші транзакції, налаштуйте конкурентність завдання і забезпечте, щоб диск/настройки репліки відповідали навантаженню.

5) Симптом: Під час деплоїв з’являється «Waiting for table metadata lock»

Корінь: DDL конфліктує з довгими запитами/транзакціями; MDL утримується довше, ніж очікувалося.

Виправлення: Усуньте довгі транзакції, робіть зміни схеми з онлайн-інструментами і впровадьте вікна/троттлінг для DDL.

6) Симптом: p99 погіршується, середні значення в нормі

Корінь: Конкуренція, черги, розкид параметрів або періодичні зміни плану. Середнє брешуть.

Виправлення: Використовуйте перцентилі в QAN/моніторингу, перевіряйте топ-оператори за max latency і 95-м перцентилем, і інспектуйте конкретні приклади запитів.

7) Симптом: Завантаження диска не 100%, але латентність висока

Корінь: Глибина черги і латентність можуть зростати задовго до досягнення %util (особливо на спільних сховищах). Малі випадкові I/O — тихий вбивця.

Виправлення: Довіряйте await і глибині черги. Зменшіть read amplification (індекси, форма запитів), зменшіть тимчасові виливи і перевірте стан сховища.

8) Симптом: Ви «виправили» запит індексом, але загальна пропускна здатність впала

Корінь: Оверхед обслуговування індексу при записах, особливо для таблиць з високим churn.

Виправлення: Перегляньте дизайн індексу, розгляньте вужчі/композитні індекси, зменшіть churn записів і бенчмаркуйте обидва шляхи — читання і запис.

Чеклісти / покроковий план

Чекліст A: Базовий моніторинг, який потрібно мати до наступного інциденту

  1. Увімкніть Performance Schema і тримайте її ввімкненою.
  2. Увімкніть slow query log з порогом, що ловить реальний біль (часто 200–500ms, налаштовуйте під навантаження).
  3. Ротируйте і зберігайте slow logs; переконайтесь, що диск не заповниться.
  4. Запускайте щоденний/щотижневий digest report і відстежуйте топ-відбитки з часом.
  5. Збирайте метрики wait events і майте хоча б один дашборд, що ранжує їх.
  6. Відстежуйте відставання реплікації і сигналізуйте по тренду, а не лише по абсолютному значенню.
  7. Копіюйте приклади запитів для топ-диджестів (PMM допомагає, але ви можете зберігати зразки самостійно).

Чекліст B: Кроки реагування на інцидент (15-хвилинна версія)

  1. Класифікуйте: CPU vs I/O vs блокування vs реплікація.
  2. Захопіть топ-диджести за загальним часом і за середнім часом.
  3. Перевірте активні сесії і lock waits; знайдіть блокерів.
  4. Якщо I/O: перевірте тимчасові таблиці на диску і скани (rows examined).
  5. Підтвердьте план для топ-відбитка через EXPLAIN.
  6. Виберіть одну дію: вбити блокуючий запит, додати індекс (тільки якщо безпечно й протестовано), обмежити завдання або відкотити деплой.
  7. Знову виміряйте. Якщо метрика не змінилась — ваше виправлення не вирішило проблему.

Чекліст C: Зміцнення, що знижує ризик майбутніх «killer query»

  1. Додайте запобіжні механізми в аплікацію: timeouts, circuit breakers і ліміти конкурентності запитів.
  2. Віддавайте перевагу prepared statements і стабільним формам запитів (допомагає діджестингу і зменшує хаос планів).
  3. Стандартизуйтe практику зміни схеми (онлайн-підхід, усвідомлення MDL).
  4. Зробіть «rows examined per call» відстежуваним SLO для критичних endpoint-ів.
  5. Пишіть план відкату для кожної зміни, що впливає на запити (індекси теж враховуються).

ЧаПи

1) Чи «кращий» Percona Server за MySQL для моніторингу?

Він кращий у тому сенсі, в якому хороший набір інструментів кращий за один ключ. MySQL має примітиви; екосистема Percona частіше робить їх оперативно придатними швидше, особливо з PMM і культурою інструментів.

2) Чи покладатися на slow query log або Performance Schema?

Використовуйте обидва. Slow log дає точні запити і таймінги у надійному файлі. Performance Schema дає живу агреговану правду і контекст очікувань. Коли вони погоджуються — діяйте швидко.

3) Який long_query_time слід використовувати?

Почніть близько 200–500ms для OLTP-служб і налаштовуйте залежно від обсягу та сховища. Якщо ваша ціль p95 = 50ms, поріг 10 секунд — це спостережницька халатність.

4) Чи шкодить увімкнення Performance Schema продуктивності?

Може, якщо увімкнути всі інструменти й консьюмари. Із розумними дефолтами і селективною інструментацією вона загалом прийнятна в production. Виміряйте оверхед у staging, якщо хвилюєтесь; не вимикайте її через суєвір’я.

5) Як знайти «killer queries», коли кожен окремо виглядає швидким?

Ранжуйте за загальним часом і за кількістю викликів, а не лише за середньою латентністю. Запит 5ms, який виконується 50 000 разів на хвилину, може споживати весь ресурс CPU.

6) Який найшвидший спосіб діагнозу проблем з блокуваннями?

Використовуйте sys views: sys.innodb_lock_waits і sys.schema_table_lock_waits. Знайдіть PID блокера, подивіться на його запит і вік транзакції, і вирішіть, вбивати чи виправляти навантаження.

7) Чому бачу відставання репліки, хоча primary виглядає здоровим?

Apply на репліці — інше навантаження: інше кешування, інший I/O, інколи інший набір запитів. Відставання часто спричинене однією великою транзакцією або тиском на commit, а не проблемами primary.

8) Чи може PMM замінити мій існуючий моніторинг?

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

9) Чи варто вбивати «killer queries» під час інцидентів?

Іноді так. Якщо запит блокує інших або спричиняє конвой блокувань, його вбивання може швидко відновити сервіс. Але рішення має бути явним: убити запит чи з’єднання, і розуміти можливі retry-шторми в застосунку.

10) Що якщо «killer query» — це насправді багато схожих запитів?

Саме тому діджести важливі. Виправляйте патерн: додайте правильний індекс, змініть форму запитів або додайте кешування. Не вирізуйте окремі запити як кроти.

Висновок: наступні кроки, які ви можете зробити цього тижня

Якщо ви хочете припинити здогадки, ваша мета проста: змусити базу даних пояснювати себе швидко.

  1. Увімкніть лог повільних запитів з реалістичним порогом і ротуванням.
  2. Тримайте Performance Schema ввімкненою, і використовуйте sys schema views, щоб уникати написання археологічного SQL.
  3. Запровадьте робочий процес діджесту запитів: щоденний/щотижневий рейтинг, відстеження топ-відбитків і розгляд «rows examined per call» як запах проблеми.
  4. Виберіть один «швидкий діагноз» дашборд: топ-диджести, топ-waits, активні сесії, відставання реплікації. Якщо це не там — цього немає під час інциденту.
  5. Визначте свою позицію щодо Percona: якщо потрібна швидша операційна видимість з меншим власним glue-кодом — Percona Server + PMM практичний вибір. Якщо ви віддані upstream MySQL — застосуйте ту саму дисципліну з вашими інструментами.

Більшість критичних запитів не вигадливі. Це звичайні запити, опинившись в екстраординарних умовах. Ваша робота — зробити ці умови видимими і зробити вбивцю болісно легким для ідентифікації.

← Попередня
OpenVPN «route addition failed» у Windows: виправлення маршрутизації, що дійсно працюють
Наступна →
Бібліотека медіа WordPress виглядає порожньою: перевірте шляхи БД та URL

Залишити коментар