Оновлення MariaDB та Percona Server: як уникнути брехні «Працює в тестовому середовищі»

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

Ви оновили в тестовому середовищі. Тести пройшли. Панелі показників були зеленими. Потім продакшн атакує о 2:00 ранку загадкою: відставання реплікації зростає, p95 латентності подвоюється,
і лог повільних запитів читається як трилер, написаний нервовим ORM.

Брехня «працює в тестовому середовищі» рідко має злі наміри. Зазвичай це фізика: інша форма даних, інша конкуренція, інша поведінка сховища, інші SQL-країні випадки, інші плагіни, інший TLS і те, що ніхто не хоче визнати — інша людська терміновість.

MariaDB vs Percona Server: що фактично змінюється під час оновлення

«MariaDB vs Percona Server» часто подається як філософська суперечка. У продакшні це більше схоже на переїзд квартири: меблі здебільшого підходять, але
дверні прорізи інші, тиск води змінюється, і господар має власну думку про те, що вважається «стандартним».

Percona Server — це форк MySQL з додатковою інструментацією, функціями продуктивності та операційними налаштуваннями. MariaDB — окремий форк зі своїм шляхом оптимізатора,
виборами реплікації, рушіями зберігання й траєкторією версій. Обидва можуть працювати з InnoDB (InnoDB у MariaDB у старіші епохи — це XtraDB і з часом відгалужувався),
обидва «переважно говорять MySQL», і обидва з радістю приймуть SQL вашого застосунку до того дня, коли вони цього не робитимуть.

Сумісність — це не галочка; це матриця

Коли люди кажуть «сумісний», вони зазвичай мають на увазі «клієнти підключаються і базові CRUD-операції працюють». Це не той рівень. Ваш рівень — це:
те саме коректність, той самий діапазон продуктивності, та та сама поведінка при відмовах під реальним навантаженням та на реальних даних.

Складні частини при оновленні між MariaDB і Percona Server (або при оновленні всередині будь-якої з родин) зазвичай групуються у п’яти місцях:

  • Семантика SQL та плани оптимізатора: один і той же запит — інший план; один і той же план — інші оцінки рядків; різні значення за замовчуванням для SQL mode.
  • Поведінка реплікації: флейвор GTID, формати binlog за замовчуванням, надійність після збою, налаштування паралельної реплікації.
  • InnoDB/redo/undo/логи: розмір redo логу, поведінка flush за замовчуванням, зміни doublewrite, значення контрольних сум.
  • Аутентифікація/TLS/плагіни: плагіни auth, версії/шифри TLS за замовчуванням, наявність плагінів і їх назви.
  • Операційні припущення інструментів: очікування Percona Toolkit, системні таблиці, відмінності performance_schema/information_schema.

Якщо ви мігруєте через сімейства (MariaDB ↔ Percona Server), найбільша помилка — думати, що історія оновлення така сама, як
«оновлення незначної версії MySQL». Це не так. Ви перетинаєте межу відгалуження, а там тестове середовище бреше найголосніше.

Одну перефразовану ідею від Gene Kim (автор з надійності/операцій): Покращення результатів походить від поліпшення системи, а не від вимагання від людей більшої напруги.
Ставтеся до оновлення як до проєктування системи, а не як до героїчної роботи.

Історичний контекст, корисний для постмортему

Історія не виправляє аварії, але пояснює, чому ваш «простий апдейт» має гострі краї. Ось факти, які варто пам’ятати під час планування:

  1. MariaDB почалася як форк після придбання MySQL Oracle (епоха 2009–2010), що сформувало її позицію «відкритого управління» та темп відгалуження.
  2. Percona Server виріс із культури налаштування під продакшн: форк віддавав пріоритет інструментації продуктивності та операційних контролів для завантажених систем.
  3. GTID — це не універсальна річ: реалізація GTID у MariaDB відрізняється від Oracle MySQL/Percona, що важливо при крос-сімейній реплікації та інструментах відновлення.
  4. Performance Schema значно розвинувся у MySQL 5.6 → 5.7 → 8.0, і Percona зазвичай слідує моделі MySQL; історія інструментації та sys schema у MariaDB інша.
  5. MariaDB ввела фічі без еквівалента в MySQL (наприклад, деякі стратегії оптимізатора, системно-версійовані таблиці), що може змінювати виконання.
  6. Percona Toolkit став де-факто операційним стандартом у багатьох компаніях; він найкраще працює, коли сервер поводиться за очікуваннями MySQL/Percona.
  7. За замовчуванням InnoDB змінювався між поколіннями (розміри redo логу, евристика flush, обробка метаданих). Оновлення часто змінюють значення за замовчуванням, навіть якщо в конфігу не чіпали.
  8. Плагіни аутентифікації еволюціонували (caching_sha2_password у світі MySQL 8.0; різні значення за замовчуванням в інших місцях), через що «клієнт працює в тесті» залежить від конкретної збірки конектора.
  9. UTF8 став політичним питанням: різниця між utf8 (3-байт) і utf8mb4 (4-байт) і як за замовчуванням вибираються колації спричинила безліч «тест пройшов» сюрпризів.

Чому тестове середовище бреше: основні режими відмов

1) Розподіл даних у тесті — фейковий

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

Класична брехня: у тесті розподіл user_id рівномірний; у продакшні один «корпоративний орендар» володіє половиною рядків. Ваш «безпечний» запит перетворюється на
вузьке місце-замок під навантаженням.

2) Конкуренція у тесті — уявна

Більшість навантажувальних тестів у тесті запускаються з ввічливим рівнем конкуренції і теплим кешем. Продакшн — це натовп. Контенція блокувань, purge lag, тиск на redo і
вартість fsync — нелінійні. Не можна екстраполювати з «працює при 50 QPS» до «працює при 5 000 QPS», тільки тому що запит той самий.

3) Змінені значення за замовчуванням, але ви не помітили

Оновлення може змінити значення за замовчуванням для формату binlog, режимів SQL, евристики InnoDB flush, обробки тимчасових таблиць, версій TLS і більше. Якщо в тесті використовується
вручну налаштований my.cnf, а в продакшні є дрейф конфігів (або навпаки), у вас не тест оновлення — у вас порівняння двох різних всесвітів.

4) Стек зберігання не однаковий

«Такий же тип інстанса» — не те саме, що «така ж I/O». Ваш том у тесті може бути тихим, у продакшні — зайнятим. У тесті може бути локальний NVMe,
а в продакшні — мережеве сховище. Параметри монтування файлової системи можуть відрізнятися. І так, політика кешу RAID-контролера має значення.

Жарт №1: Сховище — місце, куди йде «але в нас SSD» помирати, зазвичай під час єдиного вікна обслуговування, який не можна продовжити.

5) Спостережуваність відрізняється, тож ви помічаєте проблеми пізніше

У тесті часто більше debug-логування, менше трафіку і менше обмежень відповідності. У продакшні жорсткіше зберігання логів, менше метрик і більше речей, що «кричать» на CPU. Коли ви оновлюєте,
ви також змінюєте те, що вимірюється, і те, що дорого вимірювати.

6) Поведінка застосунку змінюється під продакшн-фічами

У тесті рідко працюють ті самі feature flags, таймаути, логіка повторних спроб, circuit breakers, глибина черг і розміри пакетів. Оновлення бази може виявити дрібну зміну у латентності запиту,
що спричиняє шторм повторних спроб, який стає подією посилення записів, що перетворюється на ваш уїк-енд.

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

Коли оновлення пішло не за планом, нема часу милуватися графіками. Потрібно швидко знайти вузьке місце і обрати безпечну міру пом’якшення. Ось швидкий план, який працює з MariaDB та Percona Server.

Перший: база CPU-заблокована, I/O-заблокована чи блокується через локи?

  • CPU-заблокована: високий mysqld CPU, високий час «executing», багато handler reads, плани змінилися, відсутні індекси або інші рішення оптимізатора.
  • I/O-заблокована: високий iowait, низький hit rate буфера-пулу, тиск на redo/fsync, тимчасові таблиці зливаються на диск, проблеми з віком чекпойнта.
  • Блокується локами: потоки очікують рядкові блокування, metadata locks або внутрішні лatches; SQL-потік реплікації застряг; DDL блокує роботу.

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

Шукайте один або два домінантних «відбитки»: запит, який змінив план, фонова нитка, що насичує I/O, або вузьке місце застосування реплікації. Якщо головний винуватець пояснює більше ніж ~30–40% часу, вважайте його причиною інциденту, поки не доведено протилежне.

Третій: це коректність, продуктивність чи стабільність?

  • Коректність: невідповідності даних, мовчазні обрізання, зміни колацій, відмінності SQL mode, зсуви часових поясів.
  • Продуктивність: регресія латентності, падіння пропускної здатності, відставання реплікації, хвилі підключень.
  • Стабільність: падіння, OOM kills, диск повний, пошкоджені логи, відмови плагінів, помилки TLS-переговорів.

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

Практичні перевірки (команди, виводи, рішення)

Ці завдання відділяють «ми тестували» від «ми довели». Кожне завдання має: команду, приклад виводу, що це означає і яке рішення прийняти.
Запускайте їх у тесті та в продакшні до і після оновлення. Порівнюйте результати. Якщо не порівнюєте — ви фактично довіряєте відчуттям.

Завдання 1: Підтвердити ідентичність сервера та лінію версій

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

Значення: VERSION() та version_comment показують, чи ви на MariaDB, Percona чи на іншому сервері в маскараді.

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

Завдання 2: Здампити й порівняти змінні часу виконання, що впливають на поведінку

cr0x@server:~$ mysql -NBe "SHOW VARIABLES WHERE Variable_name IN ('sql_mode','binlog_format','transaction_isolation','innodb_flush_log_at_trx_commit','sync_binlog','character_set_server','collation_server','log_bin','gtid_mode','enforce_gtid_consistency');"
sql_mode	ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
binlog_format	ROW
transaction_isolation	REPEATABLE-READ
innodb_flush_log_at_trx_commit	1
sync_binlog	1
character_set_server	utf8mb4
collation_server	utf8mb4_0900_ai_ci
log_bin	ON
gtid_mode	ON
enforce_gtid_consistency	ON

Значення: Ці змінні формують коректність, безпеку реплікації та продуктивність. Вони також змінюються між основними версіями і між форками.

Рішення: Якщо тест і продакшн не співпадають — виправте дрейф перед тим, як довіряти результатам тесту. Якщо оновлення змінило значення за замовчуванням — зафіксуйте їх явно.

Завдання 3: Перевірити розмір innodb buffer pool і тиск

cr0x@server:~$ mysql -NBe "SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
innodb_buffer_pool_size	34359738368
Innodb_buffer_pool_reads	918273
Innodb_buffer_pool_read_requests	5544332211

Значення: Відношення reads до read_requests наближено показує промахи кешу. Стрибок buffer_pool_reads після оновлення часто означає новий робочий набір,
зміну плану або фонове «чистення».

Рішення: Якщо коефіцієнт промахів значно погіршився, ймовірно ви I/O-заблоковані. Збільшіть buffer pool (якщо безпечно), виправте плани запитів або зменште фонову активність.

Завдання 4: Перевірити тиск на redo лог і чекпойнти (InnoDB log waits)

cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS LIKE 'Innodb_log_waits'; SHOW VARIABLES LIKE 'innodb_redo_log_capacity';"
Innodb_log_waits	742
innodb_redo_log_capacity	4294967296

Значення: Innodb_log_waits > 0 означає, що foreground-транзакції чекали на redo. Це класичне «redo занадто малий або flush занадто повільний».

Рішення: Якщо waits росте під навантаженням — збільшіть ємність redo (де підтримується), перегляньте налаштування flush і перевірте затримку fsync сховища.

Завдання 5: Перевірити затримку диска і насичення на рівні ОС

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

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          22.11    0.00    6.08   18.47    0.00   53.34

Device            r/s     w/s   rkB/s   wkB/s  aqu-sz  await  svctm  %util
nvme0n1         320.0  980.0  20480.0  65536.0    9.40   8.90   0.55  72.00

Значення: Високий await і великий util вказують на тиск на сховище. iowait > ~10–20% під час інциденту — гучний натяк.

Рішення: Якщо await зривається після оновлення — підозрюйте redo/fsync, тимчасові таблиці на диску або зміну фонового флешу. Пом’якшуйте зниженням швидкості записів,
збільшенням пам’яті або переміщенням на швидші томи.

Завдання 6: Перевірити здоров’я реплікації та координати

cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep 'Replica_IO_Running|Replica_SQL_Running|Seconds_Behind_Source|Last_SQL_Error|Retrieved_Gtid_Set|Executed_Gtid_Set'
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 84
Last_SQL_Error:
Retrieved_Gtid_Set: 3b2c1d10-aaaa-bbbb-cccc-111111111111:1-982733
Executed_Gtid_Set: 3b2c1d10-aaaa-bbbb-cccc-111111111111:1-982649

Значення: Зростання Seconds_Behind_Source + розширення розриву GTID вказує, що apply не встигає.

Рішення: Якщо відставання з’явилось тільки після оновлення — перевірте налаштування паралельної реплікації, формат binlog і тиск fsync на репліці.

Завдання 7: Вловити регресії плану через EXPLAIN та optimizer trace (цільово)

cr0x@server:~$ mysql -e "EXPLAIN SELECT o.id FROM orders o JOIN customers c ON c.id=o.customer_id WHERE c.email='x@example.com' AND o.status='OPEN'\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: c
type: ref
possible_keys: idx_email
key: idx_email
rows: 1
filtered: 100.00
Extra: Using index
*************************** 2. row ***************************
table: o
type: ref
possible_keys: idx_customer_status
key: idx_customer_status
rows: 12
filtered: 10.00
Extra: Using where

Значення: Ви шукаєте зміни у порядку з’єднань, типі доступу та оцінках рядків. «type: ALL» там, де раніше був «ref», — червоний прапор.

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

Завдання 8: Порівняти свіжість статистик таблиць

cr0x@server:~$ mysql -NBe "SELECT table_schema, table_name, update_time FROM information_schema.tables WHERE table_schema='app' ORDER BY update_time DESC LIMIT 5;"
app	orders	2025-12-30 01:12:44
app	customers	2025-12-30 01:10:02
app	order_items	2025-12-29 23:45:11
app	invoices	2025-12-29 21:03:19
app	payments	2025-12-29 19:58:07

Значення: update_time може вводити в оману залежно від двигуна та налаштувань, але це швидка підказка, чи запускалось обслуговування і чи таблиці «свіжі».

Рішення: Якщо таблиці виглядають застарілими після міграції/імпорту — виконайте ANALYZE TABLE для критичних таблиць і повторно перевірте EXPLAIN для ключових запитів.

Завдання 9: Перевірити тимчасові таблиці на диску та тиск на сортування

cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS LIKE 'Created_tmp%'; SHOW VARIABLES LIKE 'tmp_table_size'; SHOW VARIABLES LIKE 'max_heap_table_size';"
Created_tmp_tables	182773
Created_tmp_disk_tables	44291
Created_tmp_files	9312
tmp_table_size	134217728
max_heap_table_size	134217728

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

Рішення: Якщо зливи на диск зросли після оновлення — обережно збільшіть tmp_table_size/max_heap_table_size і виправте проблемні запити та індекси.

Завдання 10: Ідентифікувати топ-шаблони запитів за дайджестом (performance schema)

cr0x@server:~$ mysql -NBe "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, ROUND(AVG_TIMER_WAIT/1e9,2) AS avg_ms FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 3;"
SELECT * FROM orders WHERE customer_id = ? AND status = ? ORDER BY created_at DESC LIMIT ?	882771	912.22	1.03
UPDATE inventory SET qty = qty - ? WHERE sku = ? AND qty >= ?	221009	644.11	2.91
SELECT id FROM customers WHERE email = ?	775002	312.88	0.40

Значення: Якщо один дайджест домінує по загальному часу — у вас є хірургічна ціль. Якщо все стало повільніше рівномірно — підозрюйте I/O або глобальну контенцію.

Рішення: Виправляйте топ-дайджести першочергово. Якщо головний дайджест з’явився після оновлення — підозрюйте регресію плану або зміну SQL від застосунку через поведінку конектора.

Завдання 11: Перевірити накопичення metadata locks (тихий вбивця)

cr0x@server:~$ mysql -e "SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, COUNT(*) cnt FROM performance_schema.metadata_locks GROUP BY 1,2,3,4 ORDER BY cnt DESC LIMIT 5;"
+--------------+-------------+-----------+-------------+-----+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_STATUS | cnt |
+--------------+-------------+-----------+-------------+-----+
| app          | orders      | SHARED_READ | GRANTED   | 132 |
| app          | orders      | EXCLUSIVE   | PENDING   | 1   |
+--------------+-------------+-----------+-------------+-----+

Значення: Один PENDING EXCLUSIVE lock плюс купа GRANTED SHARED — класичний випадок «хтось запустив DDL і тепер всі чекають».

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

Завдання 12: Перевірити error log на регресії плагінів/аутентифікації/TLS

cr0x@server:~$ sudo tail -n 30 /var/log/mysql/error.log
2025-12-30T01:20:11.123456Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
2025-12-30T01:20:15.765432Z 12 [ERROR] [MY-010054] [Server] Slave SQL: Error 'Unknown collation: 'utf8mb4_0900_ai_ci'' on query. Default database: 'app'. Query: 'CREATE TABLE ...', Error_code: 1273

Значення: Невідповідність колації та TLS-попередження — часті тригери при переходах між версіями/форками. «Unknown collation» — проблема межі міграції.

Рішення: Якщо колація не підтримується на цілі — потрібно конвертувати схему/стовпці або вибрати сумісну колацію перед реплікацією/імпортом.

Завдання 13: Перевірити дрейф схеми між тестом і продом (DDL diff через mysqldump)

cr0x@server:~$ mysqldump --no-data --routines --triggers --databases app | sha256sum
c0a5f4c2f6d7f6bb38a39b9c1d9e2c11caa8d9b7a0f1b2c3d4e5f6a7b8c9d0e1  -

Значення: Хешування дампу схеми — грубо, але ефективно. Якщо хеші відрізняються — ваші середовища не еквівалентні.

Рішення: Якщо схема відрізняється — припиніть стверджувати, що тест валідував продакшн. Відтворіть схему продакшну в тесті (і, бажано, з маскованими даними).

Завдання 14: Перевірити коректність бекапу/відновлення перед торканням продакшну

cr0x@server:~$ xtrabackup --prepare --target-dir=/backups/xb_2025-12-30_0100
xtrabackup: This target seems to be prepared.

Значення: Бекап, який ви не підготували/не відновлювали — це теорія, а не бекап. Підготовка підтверджує здатність бекапу стати консистентним.

Рішення: Якщо prepare не вдається або займає надто багато часу після оновлення — розглядайте це як блокер релізу: ваш шлях відновлення під загрозою.

Завдання 15: Запустити мікро-бенчмарк у формі продакшну (не для показухи)

cr0x@server:~$ sysbench oltp_read_write --mysql-host=127.0.0.1 --mysql-user=sb --mysql-password=sb --mysql-db=sbtest --tables=16 --table-size=500000 --threads=64 --time=120 --report-interval=10 run
SQL statistics:
    queries performed:
        read:                            1456720
        write:                           416206
        other:                           208103
    transactions:                        104051 (867.03 per sec.)
Latency (ms):
         min:                                    2.31
         avg:                                   73.81
         max:                                 512.24

Значення: Це не про абсолютний TPS; це про порівняння до/після на тому самому обладнанні та з тією самою конфігурацією. Слідкуйте за латентністю і піками max.

Рішення: Якщо max латентність вибухнула після оновлення — ймовірно у вас fsync/redo проблеми, контенція м’ютексів або тимчасові зливи. Не випускайте в продакшн.

Завдання 16: Підтвердити помилки підключення та поведінку рукопотискання

cr0x@server:~$ mysqladmin -uroot -p extended-status | egrep 'Aborted_connects|Connection_errors_internal|Connection_errors_max_connections|Threads_connected'
Aborted_connects	17
Connection_errors_internal	0
Connection_errors_max_connections	4
Threads_connected	412

Значення: Оновлення можуть змінити плагіни аутентифікації, вимоги TLS та таймаути. Стрибки aborted_connects можуть бути через несумісність клієнта або навантаження.

Рішення: Якщо aborted connects зросли тільки після оновлення — перевірте версії конекторів, налаштування TLS та max_connections/thread_cache.

Жарт №2: Єдина річ більш впевнена, ніж ORM — це ORM після оновлення бази під ним.

Три корпоративні міні-історії (болісні, реальні, корисні)

Міні-історія 1: Інцидент через неправильне припущення (GTID «це GTID»)

Середня SaaS-компанія запланувала «просту» міграцію: замінити застарілий кластер MariaDB на Percona Server, бо їх новий стек спостережуваності краще «розуміє» MySQL/Percona. У тесті все пройшло добре. Реплікація працювала. Вправи з відмови виглядали достатньо чистими, щоб підписати.

Неправильне припущення було тонким: вони вважали, що поведінка GTID переносима як «порт 3306 переносимий». У тесті використовували логічні дампи і короткоживучі репліки. У продакшні вони покладалися на автоматизацію, що базувалася на GTID, і очікували, що після cutover усе залишиться працездатним.

Під час cutover у продакшні промотована репліка піднялась, але автоматизація відмовилась приєднати відстаючий вузол. Потім друга репліка застосувала транзакції в порядку, що не відповідав їхнім очікуванням. Дані не загубились, але вони витратили час, а час — це баг доступності.

Постмортем виявив справжню причину: план міграції трактував GTID MariaDB і GTID MySQL/Percona як достатньо взаємозамінні, не перевіряючи припущення інструменту автоматизації. У тесті не включили рівень автоматизації або довгоживучу топологію реплікації.

Виправлення не було героїчним. Вони перебудували план: трактувати флейвор GTID як жорстку межу, перевіряти процедури відновлення від початку до кінця та включити інструмент автоматизації в тести з довгоживучими репліками і реалістичною ін’єкцією лагу. Наступний cutover пройшов нудно, а це найвища похвала для операцій.

Міні-історія 2: Оптимізація, що зіграла злий жарт (більші redo логи, гірша хвостова латентність)

Команда фінтеху оновила Percona Server в рамках тієї ж основної серії і помітила періодичні паузи записів у продакшні. Хтось помітив зростання Innodb_log_waits і ухвалив розумне рішення: збільшити ємність redo. Більший redo означає менше чекпойнтів, менше пауз. Так кажуть легенди.

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

Справжня проблема була не в розмірі redo; це була поведінка I/O під імпульсним навантаженням у поєднанні зі специфікою їхнього сховища. Більший redo змінив час виконання флашу і чекпойнта. Замість дрібної, частої фон-роботи вони створили більші, менш передбачувані фази запису, які невдало співпали з варіабельністю пропускної здатності їхнього тому.

Виправлення полягало в тому, щоб припинити розглядати redo як єдиний регулятор. Вони інструментували затримки fsync, відрегулювали флешинг, перевірили налаштування doublewrite і забезпечили реплікам достатній I/O резерв. Розмір redo залишився більшим, ніж раніше — але не «максимально великим», і не без перевірки поведінки хвостів.

Урок: налаштування продуктивності — не безкоштовні цукерки. Будь-який регулятор, що змінює, коли робота виконується, може створити хвостову латентність. Якщо ви дивитесь лише середні значення, ви випустите регресії з посмішкою.

Міні-історія 3: Нудна, але правильна практика, що врятувала день (хеш схеми + заморожування конфігу)

Команда з ентерпрайзу мусила перейти з MariaDB на Percona Server через обмеження постачальника платформи. Вони не були в захваті. Вони були дисципліновані, а це дорослий варіант захоплення.

Вони почали з двох безкомпромісних правил: заморожування конфігурації і ворота хешу схеми. Чотири тижні перед cutover зміни production my.cnf були заборонені, якщо тільки вони не були прив’язані до інциденту. Будь-яка зміна вимагала перегляду дифа і відтворення в тесті. Тим часом тестове середовище щовечора перебудовували з продакшн-схеми, і дамп схеми хешували і порівнювали в CI.

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

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

Ніч cutover пройшла без пригод. Кращі історії про оновлення не потрапляють до корпоративного фольклору, бо ніхто не любить згадувати про речі, що пройшли за планом.

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

1) Симптом: реплікація ламається з помилкою «Unknown collation» або «Unknown character set»

Коренева причина: Схема джерела використовує колацію/charset, які не підтримуються на цілі (поширено між MariaDB ↔ MySQL/Percona та між MySQL 5.7 → 8.0).

Виправлення: Конвертуйте схему перед міграцією. Стандартизуйтесь на utf8mb4 і вибирайте колації, підтримувані обома сторонами. Перевірте, відновивши дамп тільки схеми на цілі.

2) Симптом: запити стали повільнішими, але CPU той самий, а I/O вищий

Коренева причина: Зросли промахи buffer pool через зміну планів або різницю у поведінці тимчасових таблиць; у тесті дані вміщувались у RAM, у продакшні — ні.

Виправлення: Порівняйте EXPLAIN-плани, оновіть статистику (ANALYZE), додайте/скоригуйте індекси і перевірте tmp_table_size та відмінності внутрішнього движка тимчасових таблиць.

3) Симптом: p99 латентності запису зросла після оновлення

Коренева причина: Тиск на redo/fsync, зміна евристики флешингу або варіабельність стеку зберігання. Іноді спричинено «допоміжними» змінами конфігурації під час оновлення.

Виправлення: Виміряйте затримку fsync (ОС + БД), перегляньте innodb_flush_log_at_trx_commit і sync_binlog, відповідально налаштуйте ємність redo і забезпечте запас I/O на томах.

4) Симптом: «занадто багато підключень» або хвиля підключень після cutover

Коренева причина: Несумісність handshake/auth плагінів, помилки TLS- переговорів або повторні спроби застосунку, що підсилюють невеликі регресії латентності.

Виправлення: Зафіксуйте плагін аутентифікації і налаштування TLS, перевірте версії конекторів у тестах, налаштуйте таймаути і ввести обмеження на пулювання підключень.

5) Симптом: DDL викликає системні паузи під час вікна оновлення

Коренева причина: metadata locks; припущення про онлайн DDL не працюють для тієї операції/версії; довгі транзакції утримують локи.

Виправлення: Використовуйте інструменти онлайн-зміни схеми (або рідний онлайн DDL, де безпечно), вбивайте/уникати довгих транзакцій, встановлюйте lock wait timeouts і плануйте DDL раніше.

6) Симптом: репліки відстають тільки після оновлення

Коренева причина: паралельна реплікація не налаштована або змінені значення за замовчуванням; формат binlog змінився; порядок комітів відрізняється; I/O репліки слабший за primary.

Виправлення: Переконайтеся, що binlog_format=ROW, налаштуйте паралелізм репліки, збільшіть I/O потужність репліки і впевніться, що конфіг репліки відповідає навантаженню після оновлення.

7) Симптом: кількість deadlocks зросла і кількість помилок у застосунку підскочила

Коренева причина: змінилися плани виконання або використання індексів, що призводить до іншого порядку захоплення локів; підвищена конкуренція; інші значення isolation/SQL mode за замовчуванням.

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

8) Симптом: диск несподівано заповнюється під час міграції або незабаром після

Коренева причина: більші binlog, тимчасові таблиці на диску, ввімкнений детальний лог, артефакти бекапу, що не обертаються, або зростання redo/undo.

Виправлення: Попередньо виділіть бюджет простору, забезпечте ротацію логів, обмежте зберігання, моніторте /var/lib/mysql та tmpdir, і перевірте, що скрипти міграції прибирають за собою.

Чеклісти / покроковий план (спеціально нудно)

Фаза 0: Визначте, який тип «оновлення» ви насправді робите

  • Оновлення в родині (Percona → Percona, MariaDB → MariaDB): все ще ризиковано, але тулінг і семантика ближчі.
  • Міграція між родинами (MariaDB ↔ Percona): трактуйте як міграцію, а не апгрейд. Різний GTID, різні колації, різний оптимізатор, різні системні таблиці.
  • Стрибок основної версії: припускайте зміну поведінки, якщо не доведено протилежне.

Фаза 1: Змусьте тестове середовище припинити брехати

  • Щовечірньо відтворюйте схему тесту з продакшну. Хешуйте дамп схеми. Блокуйте реліз при невідповідності.
  • Завантажуйте тест реалістичною формою даних: маскований снапшот продакшну або згенерований набір з подібним перекосом і кардинальністю.
  • Погодьте конфіг: той самий my.cnf, ті ж kernel/sysctl, ті самі опції монтування файлової системи, той самий клас сховища.
  • Перегравайте шаблони трафіку продакшну (включно зі сплесками). Не запускайте лише unit-тести і не вважайте це достатнім.
  • Включіть шар автоматизації: інструменти failover, бекапи, моніторинг, система міграції схеми.

Фаза 2: Preflight gates сумісності (блокери)

  • Перевірка сумісності колацій/charset по всій схемі.
  • SQL mode і строгість: впевніться у очікуваній поведінці для вставок, group by і неявних перетворень.
  • Auth плагіни і TLS: впевніться, що кожна бібліотека клієнта може підключитися під новими значеннями за замовчуванням.
  • План реплікації: вирішіть стратегію GTID; уникайте «розберемося під час cutover».
  • Репетиція бекапу/відновлення: доведіть, що ви можете відновити і промотувати в межах RTO.

Фаза 3: Гейти продуктивності (доведіть, що ви не заховали вузьке місце у темряві)

  • Бенчмаркуйте з продакшн-конкурентністю і сплесками; зафіксуйте p95/p99 і max латентності.
  • Порівняйте топ-дайджести до/після; помітьте нові топ-винуватці і регресії планів.
  • Перевірте I/O запас і поведінку redo під хвилями записів.
  • Підтвердіть відсутність тимчасових зливів і тиску на сортування.

Фаза 4: План cutover, що враховує реалії відкату

  • Визначте тригери для відкату: помилка коректності, нестабільність реплікації, стійка регресія латентності, зростання помилок.
  • Заморозьте зміни схеми під час вікна cutover.
  • Зменшіть радіус ураження: канарьте частину трафіку або орендарів, якщо архітектура дозволяє.
  • Проводьте ті самі перевірки під час cutover, що й на репетиції. Ті самі команди, ті самі дифи, ті самі гейти.
  • Не імпровізуйте «швидкі оптимізації» під час інциденту, якщо не можете виміряти їх і безпечно відкотити.

Питання й відповіді

1) Чи є Percona Server «просто MySQL», а MariaDB «просто MySQL»?

Не в контексті, який має значення для вашого плану оновлення. Percona тісно слідує MySQL, додаючи фічі й збірки. MariaDB з часом відгалузилася значніше. Трактуйте переходи між родинами як міграції з валідацією сумісності, а не як випадкові апгрейди.

2) Чи можливо реплікувати з MariaDB на Percona Server (або навпаки) під час міграції?

Іноді так, але крайні випадки — там, де ви втрачаєте кров: несумісність флейворів GTID, відмінності колацій і припущення про statement/row формат. Якщо робите таке, доведіть це довгою репетицією реплікації, а не 30-хвилинним тестом.

3) Яка найбільш поширена причина «працює в тесті»?

Форма даних. Датасети тесту рідко відтворюють продакшн-перекоси, фрагментацію і гарячі ключі. Оптимізатори і кеші поводяться інакше, коли реальність складна.

4) Чи треба зафіксувати всі змінні конфігурації перед оновленням?

Зафіксуйте ті, що змінюють семантику або надійність: sql_mode, binlog_format, transaction isolation, flush і sync_binlog, charset/collation і політику аутентифікації/TLS. Не фіксуйте випадкові регулятори, які не розумієте; ви закам’янієте старі помилки.

5) Як вловити регресії планів запитів раніше?

Захоплюйте топ-дайджести запитів, запускайте EXPLAIN для репрезентативних запитів, порівнюйте до/після і валідуйте з продакшн-подібною статистикою (ANALYZE). Також слідкуйте за тимчасовими зливами і схемою handler reads, а не тільки за часом запиту.

6) Чому після оновлення зросло відставання реплік, коли записи не змінились?

Застосування на репліці може бути обмежене однонитковим apply, обмеженнями порядку комітів, тиском fsync або іншими значеннями за замовчуванням паралельної реплікації. Первинний вузол може «витримувати», але репліка не встигає застосувати в межах свого I/O бюджету.

7) Безпечно змінювати налаштування redo/flush під час інциденту?

Лише якщо ви можете швидко виміряти результат і розумієте компроміс цілісності. Деякі налаштування зменшують витрати на fsync за рахунок надійності після збою. Якщо ваш інцидент стосується коректності, не жертвуйте цілісністю заради швидкості без погодження на рівні керівництва.

8) Як зрозуміти, що тестове середовище «достатньо продакшн-подібне»?

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

9) Чи використовувати логічні дампи або фізичні бекапи для міграції?

Логічні дампи портативні, але повільні і можуть змінити дрібниці (definers, колації, charset). Фізичні бекапи швидкі, але вимагають сумісності движка/версії. Для крос-сімейних рухів багато команд комбінують методи: фізичні всередині сім’ї, логічні через межі, завжди з репетиціями.

10) Який розумний критерій успішності оновлення?

Коректність верифікована, реплікація стабільна, продуктивність у межах визначеного бюджету регресії (наприклад, відсутність стійкої регресії p95 і відсутність вибуху p99).
Плюс: бекап/відновлення працюють і моніторинг показує правду.

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

Якщо ви плануєте рух MariaDB ↔ Percona Server (або навіть «легке» оновлення в родині), не починайте з обговорення фіч. Почніть з того, щоб позбутися брехні тестового середовища.

  1. Збудуйте звіт про дифи: запустіть Завдання 1–3 і 13 в обох середовищах і виправте дрейф, поки вони не співпадуть.
  2. Виберіть 10 критичних запитів: запустіть EXPLAIN до/після і зафіксуйте плани як артефакти.
  3. Зробіть одну реалістичну репетицію навантаження: sysbench підійде як базис, але також відтворіть шматок продакшн-трафіку, якщо можете.
  4. Репетируйте відновлення: готуйте і відновлюйте бекапи, доведіть промоут і перевірте, що можете відбудувати репліку в межах RTO.
  5. Напишіть тригери для відкату: зробіть їх вимірюваними (лаг, рівень помилок, p99) і узгодьте до вікна обслуговування.

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

← Попередня
Відмови пошти: як читати коди відмов як професіонал і усунути корінну причину
Наступна →
ZFS zpool split: Клонування дзеркального пулу для міграції або DR

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