Локальна розробка — милий обманщик. Ваш ноутбук має одного користувача, швидкий NVMe, теплий кеш файлової системи й стек Docker Desktop, який «корисно» згладжує гострі кути. Продакшн не має нічого з цього. У продакшні є реальна конкурентність, реальна затримка вводу-виводу, реальні оновлення і ті види відмов, які з’являються о 03:12 з календарним запрошенням «SEV-1».
Якщо ви запускаєте MySQL або MariaDB у контейнерах Docker, розрив між «він стартує» і «він переживає навантаження» — саме там більшість команд втрачає кров. Двигуни схожі — поки не перестають бути. Образи виглядають взаємозамінними — поки не зміниться мінорна версія й не змінить значення за замовчуванням. Сховище — «просто том» — поки fsync не стане вашою новою релігією.
Що ламається в продакшн (і чому)
Коли хтось каже «MySQL і MariaDB практично те саме», зазвичай має на увазі: «мій додаток виконав кілька запитів і не впав». Продакшн вимагає жорсткіших гарантій: передбачуваного запуску, стабільних значень за замовчуванням, безпечних оновлень, послідовної продуктивності під навантаженням і відновлюваності, коли щось іде не так.
Великі категорії помилок «працювало локально»
- Аутентифікація та сумісність клієнтів: MySQL 8 змінив значення за замовчуванням, які старі клієнти не люблять. MariaDB пішла своїм шляхом.
- Семантика стійкого зберігання: bind mount, іменовані томи та мережеві томи поводяться по-різному щодо fsync, дозволів і затримок.
- Обмеження ресурсів: ваш ноутбук робить overcommit пам’яті і CPU. У продакшні контейнери підпадають під cgroups і примусове обмеження.
- Порядок запуску: Compose «depends_on» — це не готовність. Ваш додаток влучає в БД, поки вона ще виконує відновлення після краху.
- Дрейф конфігурацій: локально використовуються значення за замовчуванням; у проді — файл конфігурації; у стейджингу — змінна середовища. Сюрприз: у вас запускаються три різні бази даних.
- Шляхи оновлення: невеликий апдейт образу може змінити значення за замовчуванням, формат redo-логів або поведінку реплікації.
- Режим SQL/колації: тонкі відмінності проявляються як «чому продакшн відхиляє цей INSERT?»
Тут основна думка: MySQL і MariaDB — це не «просто контейнери». Це системи зі станом, з гострими межами щодо надійності й продуктивності. Docker полегшує їхній старт; він не робить їх легкими в експлуатації.
Короткий жарт (1/2): Запустити базу даних у контейнері — як поставити піаніно на скейтборд: можливо, але краще добре піклуватися про підлогу.
Визначте позицію: що ви повинні зробити
Якщо ви відправляєте в продакшн:
- Фіксуйте версії. Явні теги. Ніколи
latest. Ніколи. - Обирайте рушій свідомо. Якщо вам потрібні фічі Oracle MySQL або сувора сумісність з поведінкою MySQL 8 — використовуйте MySQL. Якщо потрібні специфічні можливості MariaDB або операційні причини (наприклад, певні дистрибутиви), використовуйте MariaDB. Не «поменяйте потім».
- Проєктуйте зберігання в першу чергу. Визначте, де живуть дані, як їх бекапити й як відновлювати перед тим, як відправите перший запит.
- Вимірюйте латентність fsync. Не маркетингові IOPS. Реальна поведінка fsync на вашому реальному шляху зберігання.
Цікаві факти та історичний контекст (які вам справді знадобляться)
Це не факти для вікторини. Вони пояснюють, чому значення за замовчуванням різняться, чому сумісність не гарантована і чому «drop-in replacement» має термін придатності.
- MariaDB була створена як форк MySQL після поглинань Sun/Oracle, щоб зберегти спільнотно керований шлях розвитку. Така історія пояснює, чому назви подібні, а розходження — політичні й технічні.
- MySQL 8 змінив плагін аутентифікації за замовчуванням на
caching_sha2_password, що ламає старі клієнти й інструменти, які очікувалиmysql_native_password. - MySQL 8 прибрав query cache (це була рушійна пастка під конкуренцією). Якщо ви покладалися на нього в MySQL 5.7 — ви покладалися на пастку.
- Оптимізатор і екосистема движків MariaDB відходили в бік з часом (наприклад, Aria, MyRocks у певних контекстах і різна історія InnoDB/XtraDB). Результат: «той самий запит» не означає «той самий план виконання».
- GTID-реплікація існує в обох світах, але деталі реалізації відрізняються настільки, що крос-рушійна реплікація/міграція потребує планування, а не вдавань.
- Офіційні Docker-образи — це упакований продукт. Скрипти entry-point, користувачі за замовчуванням, дозволи й логіка ініціалізації — частина продукту, який ви запускаєте.
- Linux-файлові системи і драйвери мають значення. Overlay-файлові системи, мережеві файлові системи та шари copy-on-write впливають на вартість fsync і поведінку метаданих.
- Надійність конфігурована. Налаштування на кшталт
innodb_flush_log_at_trx_commitміняють безпеку даних заради швидкості. Багато «швидких» контейнерних гідів тихо ставлять надійність у режим «опціонально».
MySQL проти MariaDB у контейнерах: відмінності, що болять
1) Поведінка образу — частина вашої БД
Коли ви запускаєте mysql:8.0 або mariadb:11, ви обираєте не лише двигун бази даних. Ви обираєте:
- Як працює ініціалізація (
/docker-entrypoint-initdb.dповедінка, значення charset за замовчуванням, робота з root-паролем). - Який вигляд має конфіг за замовчуванням і звідки він завантажується.
- Якими UID/GID запускається сервіс, що впливає на дозволи томів.
У локальній розробці ви безжально видаляєте томи. У продакшні ваші entrypoint-скрипти не повинні бути єдиним бар’єром між «перезапуск» і «каталог даних непрацездатний».
2) Аутентифікація і TLS: це не просто «пароль»
Класична помилка: додаток використовує старіший конектор. Локально ви запускали MariaDB або MySQL 5.7. У проді хтось вибрав MySQL 8 «бо новіший». Додаток починає падати на логінах з помилками про плагіни аутентифікації або RSA-публічні ключі.
Прийміть рішення: або оновіть клієнтські бібліотеки, щоб підтримувати MySQL 8 за замовчуванням, або явно налаштуйте MySQL 8 для використання mysql_native_password для конкретного користувача (і усвідомте наслідки для безпеки). Для MariaDB історія аутентифікації досить відмінна — перевіряйте сумісність конекторів на ранньому етапі.
3) SQL-моди та колації: «працює», поки хтось не вставить реальні дані
Локальні набори даних чисті. Продакшн-дані — це смітник з бізнес-цінністю. Відмінності в:
sql_mode(строгості щодо недійсних дат, обрізаних рядків і неявних конверсій)- кодировці за замовчуванням (наприклад,
utf8mb4проти старішогоutf8) - значеннях колації за замовчуванням (правила сортування й порівняння)
…можуть перетворити тиху поведінку локально на гучний продакшн-брак.
4) Продуктивність: той самий запит, інший план, інший біль
MySQL і MariaDB ділять багато ДНК, але можуть обирати різні плани виконання під навантаженням. Docker додає свій податок на продуктивність: поведінка файлових систем у overlay, драйвери зберігання і обмежений I/O, що не проявляється на ноутбуці розробника.
Якщо ви серйозні: розглядайте кожен двигун + версію як окрему платформу. Бенчмаркуйте реальне навантаження, а не синтетичний однолайнер.
5) Реплікація та міграція: «достатньо сумісні» — це не стратегія
Колись команда захоче мігрувати MariaDB на MySQL або навпаки через контракти підтримки, запропоновані керовані сервіси або потреби у фічах. Контейнеризація цього не полегшує; вона робить простішим виконати це випадково й тому небезпечно.
Логічні дампи (mysqldump) повільні, але переносні. Фізичні копії каталогів даних швидкі, але крихкі між версіями і рушіями. Реплікація може бути елегантною, але вимагає дисципліни сумісності.
Docker storage: де продуктивність і надійність сперечаються
Бази даних весь день роблять дві речі: читають і пишуть. Docker storage визначає, чи ці записи швидкі, надійні й відновлювані — або «швидкі, поки не стануть такими», що ввічливо описує корупцію даних і тривалі простої.
Іменовані томи проти bind mount: обирайте свідомо
- Іменований том: шлях, керований Docker, зазвичай стабільніші дозволи, менше сюрпризів. Операційно приємніше для багатьох налаштувань.
- Bind mount: ви контролюєте точний шлях на хості. Чудово для інтеграції з бекапами та дебагу, але легко помилитися з власністю, SELinux/AppArmor контекстами та вибором файлової системи.
Якщо ви примонтуєте директорію з хостової файлової системи з незвичною семантикою (мережева FS, деякі розподілені FS, неправильно налаштований NFS), ви можете отримати затримки при fsync або ризик корупції.
Латентність fsync — ваш податок «він працював локально»
У локальній розробці fsync швидкий і кеш теплий. У продакшні fsync може бути повільним через:
- мережеве сховище
- політику кешу RAID-контролера
- поведінку блокового сховища в хмарі
- накладні витрати copy-on-write
- контенцію на хості
Коли fsync повільний, скидання логів InnoDB повільне. Потім коміти повільні. Потім ваш додаток таймаутить. Потім хтось «виправляє» це, вимикаючи надійність. І тепер ви робите ставку на свою зарплату.
Одна парафразована ідея: все ламається постійно — проектуйте на це, а не надійтеся, що не станеться
.
Обмеження пам’яті контейнера і поведінка InnoDB
InnoDB потребує пам’яті для buffer pool, лог-буферів і кешів. Контейнери мають ліміти. Якщо ви не підганяєте innodb_buffer_pool_size та інші налаштування під cgroups, kernel OOM killer рано чи пізно прийме рішення за вас. Це не буде бажане рішення.
Health checks проти готовності
Контейнер «запущений» ще не означає «готовий». Після краху чи жорсткого рестарту MySQL/MariaDB може виконувати відновлення після краху. Процес приймає TCP-з’єднання рано, але запити можуть блокуватися або падати, поки відновлення не завершиться. Саме тут наївна оркестрація перетворює транзитний інцидент на каскадну відмову.
Практичні завдання: команди, виводи та рішення
Нижче — завдання продакшн-класу, які ви можете виконати сьогодні. Кожне містить: команду, реалістичний зразок виводу, що це означає, і рішення, яке треба прийняти. Зробіть це перед тим, як оголосити перемогу.
Завдання 1: Підтвердити, який двигун і версію ви фактично запускаєте
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT @@version, @@version_comment;"
+-----------+----------------------------------+
| @@version | @@version_comment |
+-----------+----------------------------------+
| 8.0.36 | MySQL Community Server - GPL |
+-----------+----------------------------------+
Що це означає: Це Oracle MySQL 8.0.36, а не MariaDB. Коментар версії має значення; MariaDB виведе свій брендинг.
Рішення: Використовуйте документацію та нотатки про міграцію для конкретного двигуна. Не застосовуйте налаштування MariaDB до MySQL 8.
Завдання 2: Перевірити тег образу та digest (pinning)
cr0x@server:~$ docker inspect --format '{{.Config.Image}} {{index .RepoDigests 0}}' db
mysql:8.0 mysql@sha256:3a5f2c8e7f2c2c6f2d2f4c1c0b0b7c6d9e8f1a2b3c4d5e6f7a8b9c0d1e2f3a4
Що це означає: Ви запускаєте тег, і є конкретний digest. Теги рухаються; digests — ні.
Рішення: В продакшні зафіксуйте digest (або хоча б зафіксуйте мінорну версію і контролюйте оновлення).
Завдання 3: Дізнатися, чи використовується іменований том або bind mount для даних
cr0x@server:~$ docker inspect db --format '{{json .Mounts}}'
[{"Type":"volume","Name":"prod-mysql-data","Source":"/var/lib/docker/volumes/prod-mysql-data/_data","Destination":"/var/lib/mysql","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
Що це означає: Для /var/lib/mysql використовується іменований том Docker.
Рішення: Переконайтеся, що ваші процедури бекапу й відновлення знають, де цей том знаходиться, і що хостове сховище підходить (файлова система, затримки, моніторинг).
Завдання 4: Перевірити тип файлової системи і опції монтування під директорією даних
cr0x@server:~$ docker exec -it db bash -lc "df -T /var/lib/mysql && mount | grep ' /var/lib/mysql ' -n || true"
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 ext4 96143228 31287124 59921844 35% /var/lib/mysql
Що це означає: Дані на ext4 — добрий бенчмарк. Якщо тут ви бачите overlayfs або NFS-монтування, зупиніться і перегляньте рішення.
Рішення: Уникайте розміщення даних InnoDB на мережевих файлових системах, якщо ви повністю не розумієте семантику надійності та блокувань.
Завдання 5: Підтвердити каталог даних і критичні налаштування надійності InnoDB
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES WHERE Variable_name IN ('datadir','innodb_flush_log_at_trx_commit','sync_binlog');"
+------------------------------+----------------+
| Variable_name | Value |
+------------------------------+----------------+
| datadir | /var/lib/mysql/|
| innodb_flush_log_at_trx_commit | 1 |
| sync_binlog | 1 |
+------------------------------+----------------+
Що це означає: Повна надійність: скидання логів при кожному коміті, синхронізація binlog при кожному коміті.
Рішення: Залишайте це для продакшну, якщо ви не підписалися на можливість втрати даних при краху. Якщо продуктивність погана — спочатку виправте сховище і конфігурацію, а не знижуйте надійність.
Завдання 6: Перевірити плагіни аутентифікації та конфігурацію користувачів (класична проблема MySQL 8)
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT user, host, plugin FROM mysql.user WHERE user IN ('app','root');"
+------+-----------+-----------------------+
| user | host | plugin |
+------+-----------+-----------------------+
| app | % | caching_sha2_password |
| root | localhost | caching_sha2_password |
+------+-----------+-----------------------+
Що це означає: Користувач app використовує caching_sha2_password. Старі клієнти можуть не працювати.
Рішення: Оновіть клієнтський конектор або змініть плагін на користувача (і протестуйте). Не «виправляйте» це відкатом сервера без плану.
Завдання 7: Перевірити sql_mode і виявити дрейф локального/продакшн
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT @@sql_mode\G"
*************************** 1. row ***************************
@@sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
Що це означає: Включені строгі режими. Додатки, що покладалися на лояльну поведінку, можуть ламатися.
Рішення: Краще виправити додаток і схему, аніж послаблювати sql_mode глобально. Якщо потрібно, змініть його явно й задокументуйте.
Завдання 8: Перевірити кодування та колацію за замовчуванням (цілісність даних, не косметика)
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'character_set_server'; SHOW VARIABLES LIKE 'collation_server';"
+----------------------+---------+
| Variable_name | Value |
+----------------------+---------+
| character_set_server | utf8mb4 |
+----------------------+---------+
+------------------+--------------------+
| Variable_name | Value |
+------------------+--------------------+
| collation_server | utf8mb4_0900_ai_ci |
+------------------+--------------------+
Що це означає: MySQL 8 використовує нові колації за замовчуванням. MariaDB відрізняється. Сортування/порівняння можуть змінюватися між середовищами.
Рішення: Стандартизуйте charset/collation у міграціях схеми. Не покладайтеся на значення сервера за замовчуванням.
Завдання 9: Інспектувати логи контейнера на предмет відновлення після краху, проблем з правами та невдалих плагінів
cr0x@server:~$ docker logs --tail=80 db
2025-12-31T00:11:21.443915Z 0 [System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.0.36) initializing of server in progress as process 1
2025-12-31T00:11:22.119203Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2025-12-31T00:11:27.991130Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2025-12-31T00:11:28.250984Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections.
Що це означає: Чистий старт. Якщо ви бачите «permission denied», «upgrade after crash» або багаторазові перезапуски, розглядайте це як проблему з конфігурацією/сховищем, а не кодом додатка.
Рішення: Зробіть перевірки готовності такими, що відображають реальну придатність (наприклад, простий запит) і дослідіть повільне відновлення, якщо воно повторюється.
Завдання 10: Підтвердити обмеження ресурсів (cgroups), щоб уникнути загадкових OOM
cr0x@server:~$ docker inspect db --format 'Memory={{.HostConfig.Memory}} NanoCpus={{.HostConfig.NanoCpus}}'
Memory=2147483648 NanoCpus=2000000000
Що це означає: Встановлено ліміт пам’яті 2 GiB і квоту CPU, еквівалентну 2 ядрам.
Рішення: Налаштуйте buffer pool і обмеження з’єднань під ці параметри. Якщо ви запускаєте MySQL з дефолтними параметрами в контейнері на 2 GiB — OOM killer влаштує вам вечірку-сюрприз.
Завдання 11: Спостерігати за пам’яттю в рантаймі і перевірити історію OOM
cr0x@server:~$ docker stats --no-stream db
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a1b2c3d4e5f6 db 185.23% 1.92GiB / 2.00GiB 96.00% 1.2GB / 1.1GB 9.8GB / 14GB 61
Що це означає: Ви близькі до ліміту пам’яті. Саме так відбуваються OOM-пригоди під час піків.
Рішення: Зменшіть max_connections, правильно підберіть buffer pool або збільшіть пам’ять. Також оцініть запити, що створюють піки.
Завдання 12: Перевірити розмір InnoDB buffer pool відносно пам’яті контейнера
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
+-------------------------+-----------+
| Variable_name | Value |
+-------------------------+-----------+
| innodb_buffer_pool_size | 1073741824|
+-------------------------+-----------+
Що це означає: Buffer pool — 1 GiB. На контейнері з 2 GiB це може бути прийнятно — якщо інші витрати пам’яті (з’єднання, тимчасові таблиці, сорти) під контролем.
Рішення: Залиште запас. Якщо ви регулярно на 90% пам’яті, зменшіть buffer pool або оптимізуйте пам’ять на з’єднання.
Завдання 13: Виміряти повільні запити і чи ви обмежені CPU або I/O
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW GLOBAL STATUS LIKE 'Slow_queries'; SHOW VARIABLES LIKE 'slow_query_log';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries | 1842 |
+---------------+-------+
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| slow_query_log | ON |
+----------------+-------+
Що це означає: Кількість повільних запитів росте. Логування увімкнене, тож можна аналізувати та виправляти.
Рішення: Витягніть логи повільних запитів, виправте індекси/запити та перевірте, що в проді використовуються ті ж плани, що і в стейджингу.
Завдання 14: Перевірити бінарне логування і формат (реплікація та відновлення до точки часу)
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'binlog_format';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
Що це означає: Бінлоги увімкнені у форматі ROW — загалом безпечніший формат для коректності реплікації.
Рішення: Якщо ви хочете відновлення до точки часу, зберігайте бінлоги і подавайте їх. Якщо ні — будьте чесні: ваш план відновлення — «відновити останній бекап і втратити дані».
Завдання 15: Переконатися, що додаток випадково не використовує припущення про localhost/socket
cr0x@server:~$ docker exec -it app bash -lc "getent hosts db && nc -vz db 3306"
172.20.0.3 db
Connection to db 3306 port [tcp/mysql] succeeded!
Що це означає: Контейнер додатка може резолвити й дістатися БД за іменем сервісу по TCP.
Рішення: Стандартизуйтесь на TCP для зв’язку контейнер-контейнер, якщо у вас немає свідомої причини шарити UNIX-сокет через том (рідко і крихко).
Завдання 16: Перевірити на корупцію таблиць або попередження відновлення після краху (здоров’я InnoDB)
cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,80p'
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
2025-12-31 00:22:10 0x7f1a2c1ff700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 44 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 102 srv_active, 0 srv_shutdown, 188 srv_idle
srv_master_thread log flush and writes: 290
Що це означає: Ви можете шукати «LATEST DETECTED DEADLOCK» і затримки I/O. Відсутність явних помилок — добре; наявність довгих очікувань вказує на I/O або конкуренцію.
Рішення: Якщо ви бачите високі «log i/o» очікування або fsync-стали — спочатку дослідіть затримки сховища.
Швидкий план діагностики
У вас інцидент. Додаток повільний або впав. Люди питають «є оновлення?» в чаті, ніби це навантажувальний тест. Ось порядок дій, що швидко знаходить вузьке місце.
Перше: чи база даних справді здорова і готова?
- Перевірте перезапуски контейнера і логи на предмет циклів відновлення після краху, помилок дозволів і повідомлень про оновлення.
- Запустіть тривіальний запит зсередини контейнера:
SELECT 1;Якщо він блокується — сервіс не «готовий». - Підтвердьте вільне місце на диску і навантаження inode на шляху хоста, що підкріплює том.
Друге: це латентність I/O (fsync) чи конкуренція CPU?
- Якщо коміти повільні і висока конкурентність — підозрюйте латентність fsync і сховище.
- Якщо CPU завантажений і запити повільні — підозрюйте відсутність індексів, погані плани або занадто багато потоків.
- Якщо пам’ять біля ліміту — підозрюйте свопінг (на хості) або OOM kills (в контейнері).
Третє: це дрейф конфігурацій або регресія сумісності?
- Порівняйте
sql_mode, charset/collation і плагіни аутентифікації між локалом/стейджингом/продом. - Підтвердьте тег образу й digest. Чи була «невинна» повторна розгортка?
- Перевірте версії клієнтських конекторів і вимоги TLS.
Четверте: чи це мережевий шлях і резолюція імен?
- Валідовуйте сервіс-дискавері в Docker-мережі.
- Підтвердьте прокидання портів і правила фаєрволу при перетині хостів.
- Шукайте епізодичні штормові підключення (погані налаштування пулу).
Типові помилки: симптом → корінна причина → виправлення
1) Симптом: Додаток не може залогінитися після переходу на MySQL 8
Корінна причина: Несумісність плагіну аутентифікації (caching_sha2_password проти очікувань старих клієнтів).
Виправлення: Оновіть клієнтський конектор; або встановіть плагін користувачу mysql_native_password і перевипустіть пароль; потім заплануйте коректне оновлення.
2) Симптом: Випадкове «permission denied» при старті після деплойу
Корінна причина: Власність тому/кути SELinux не відповідають UID/GID рантайму контейнера; bind mount шлях створений root на хості.
Виправлення: Стандартизуйте створення томів; застосуйте правильну власність на хостовому шляху; використовуйте послідовний безпековий контекст; уникайте ad-hoc ручного створення директорій.
3) Симптом: Записи повільні тільки в продакшні, читання — нормальні
Корінна причина: Латентність fsync на продакшн-шляху зберігання (мережевий том, повільний диск або контенція хоста). У деві ваш диск швидкий і простає.
Виправлення: Виміряйте поведінку fsync; перенесіть дані на локальний SSD/NVMe або правильно профілюйте блокове сховище; уникайте overlay для datadir; налаштуйте I/O scheduler і хост.
4) Симптом: «Забагато з’єднань» під час піків трафіку
Корінна причина: Неправильні налаштування пулу з’єднань, автоскейл контейнерів без планування ємності бази, або низьке max_connections щодо навантаження.
Виправлення: Виправте налаштування пулу першочергово (таймаути, max open connections). Потім підбирайте max_connections з урахуванням пам’яті (буфери на з’єднання).
5) Симптом: Запити поводяться по-різному між MariaDB і MySQL
Корінна причина: Інші вибори оптимізатора, різні колації або відмінності sql_mode.
Виправлення: Зафіксуйте двигун і версію у всіх середовищах; запускайте EXPLAIN у продаподібних умовах; стандартизуйте дефолти схеми для charset/collation і sql_mode.
6) Симптом: Контейнер «healthy», але додаток отримує таймаути відразу після перезапуску
Корінна причина: Health check лише перевіряє, що процес запущено; БД все ще виконує відновлення після краху або розігріває кеші.
Виправлення: Health check має виконувати запит (і за потреби перевіряти стан реплікації). Завантаження додатка має починатися після готовності, а не після «контейнер стартував».
7) Симптом: «Диск заповнений» в контейнері, але на хості є вільне місце
Корінна причина: Ви записуєте у шар контейнера (overlay) замість тому; або том розташований на іншій файловій системі, яка заповнена.
Виправлення: Переконайтеся, що datadir вказує в змонтований том; перевірте монтування; очистіть бінлоги і tmpdir за політикою збереження.
8) Симптом: Реплікація ламається після «мінорного» оновлення
Корінна причина: Порушення очікувань сумісності binlog/GTID; відмінності в дефолтах або застарілі поведінки між версіями/рушіями.
Виправлення: Оновлюйте за етапним планом; перевіряйте реплікацію у pre-prod з трафіком, схожим на продакшн; фіксуйте версії поки не будете впевнені.
Три корпоративні міні-історії з передової
Міні-історія 1: Інцидент, спричинений неправильною припущенням
Команда мала акуратний Docker Compose файл: сервіс MySQL, сервіс додатку і том. У деві все було добре. Стейджинг теж вдався. Продакшн запустили і він тихо помер: періодичні помилки логіну і сплеск 500, які виглядали як регресія додатка.
Припущення було тонким: «MySQL — це MySQL». Локально вони запускали MariaDB, бо вона стартувала швидше і була знайомішою. У проді платформний інженер замінив образ на mysql:8 для стандартизації. Без злого умислу — просто добре задуманого прибирання.
Додаток використовував застарілий MySQL-конектор у базовому образі, який давно не оновлювався. При легкому навантаженні він іноді працював (завдяки повторному використанню з’єднань і лояльним тестовим шляхам). При реальному навантаженні нові з’єднання різко зросли й аутентифікація почала падати з помилками плагіну.
Виправлення було нескладним: оновити конектор і стандартизувати очікування аутентифікації. Втратили час, бо всі годину дебажили «мережу». Урок чіткий: вибір движка — це частина контракту вашого додатка, а не інфраструктурна деталь, яку можна змінити в п’ятницю.
Міні-історія 2: Оптимізація, що дала зворотний ефект
Інша організація мала проблему продуктивності: записи були повільні. Хтось помітив очевидні підозри — innodb_flush_log_at_trx_commit=1 і sync_binlog=1. Їх змінили на 2 і 0 відповідно. Затримки зменшились. Інцидентний канал заспокоївся. Всі хлопали руки.
Два тижні потому відбувся ребут хоста. Не драматичний. Після рестарту додаток виявив відсутність останніх транзакцій. Не багато. Але достатньо, щоби боліти, бо відсутні рядки були клієнтоорієнтовані і термінові.
Тепер команда мала дві проблеми: виправити дані і відновити довіру. Довелося звірятися з зовнішніми системами, повторно обробляти події і пояснювати, чому надійність була знижена без письмового рішення. Корінь проблеми — не самі налаштування, а використання їх як пластиря замість виправлення латентності сховища і I/O-контенції.
Кінцеве рішення було нудним: перенести том БД на правильно Provisioned блокове сховище, обмежити штормові підключення і оптимізувати повільні запити. Налаштування надійності повернули до строгих значень, бо «швидко, але неправильно» — це не функція.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Інша команда запускала MariaDB у контейнерах для внутрішньої платформи. Вони не були гламурні, але дисципліновані. Кожне розгортання фіксувало digest образу. Кожна зміна конфігурації пройшла рев’ю. Бекапи робилися щовночі з тестовим відновленням кожен спринт.
Одного ранку після планового обслуговування нода не змогла запустити базу. Логи показували симптоми корупції у файлі таблиць InnoDB. Ніхто не веселився, але й ніхто не панікував. Вони виконали runbook: зупинили записи, сфотографували стан, відновили останній відомо-робочий бекап, перемотали бінлоги до межі інциденту.
Вони повернулися в роботу з обмеженими втратами даних, бо їхня політика зберігання і відправки бінлогів була розроблена заздалегідь. Корінна причина виявилась у шарі зберігання ноди, а не в MariaDB. Але інцидент не став катастрофою, бо вони практикували відновлення як звичайну річ, а не як разовий вогняний дрил.
Короткий жарт (2/2): Бекапи як парашути — якщо ви перевірятимете їх лише коли вони потрібні, ви вже зробили вибір.
Чеклісти / покроковий план
Покроково: перехід від «локальна перемога» до «виживання в продакшні»
- Обирайте двигун і версію свідомо. Документуйте, чому ви використовуєте MySQL чи MariaDB, і фіксуйте точну версію.
- Уніфікуйте введення конфігурацій. Віддавайте перевагу змонтованому файлу конфігурації або контрольованому механізму; уникайте мішанини env vars і багатьох фрагментів конфігурації без політики.
- Визначте зберігання. Вирішіть: іменований том чи bind mount; файлова система хоста; storage class; вимоги IOPS/надійності.
- Усвідомлено задайте надійність. Тримайте
innodb_flush_log_at_trx_commit=1іsync_binlog=1, якщо ви не погодилися на втрату даних. - Задайте ліміти пам’яті і натюньте під них. Обмежте
max_connections, розрахуйте buffer pool і перевірте поведінку OOM в контейнері. - Реалізуйте перевірки готовності. Health check, який виконує запит, кращий за «порт відкритий».
- Логи і метрики. Лог повільних запитів, логи помилок і базові метрики MySQL/MariaDB (connections, buffer pool hit ratio, redo log waits).
- Бекапи з тестом відновлення. Плануйте відновлення в disposable середовищі; перевіряйте сумісність схеми і додатка.
- Репетиція оновлень. Програвайте оновлення версій на даних, подібних до продакшн, і під навантаженням. Вимірюйте час відновлення.
- Runbooks для інцидентів. Мати план для: високої латентності, відставання реплікації, засмічення диска і проблем зі стартом.
Чекліст перед деплоєм (роздрукуйте перед відправленням)
- Тег образу зафіксований і переглянутий; бажано зафіксований digest.
- Каталог даних на томі (не в шарі контейнера).
- Файлова система і опції монтування перевірені на придатність для БД.
- Ліміти пам’яті/CPU встановлені; MySQL/MariaDB налаштований відповідно.
- Сумісність плагіну аутентифікації/клієнта перевірена.
- Кодування/колація стандартизовані у схемі, не покладатися на дефолти.
- Налаштування надійності усвідомлено прийняті і задокументовані.
- Бекапи налаштовані; відновлення протестоване.
- Моніторинг і алерти налаштовані для диска, CPU, пам’яті і латентності запитів.
- Перевірки готовності/health перевіряють реальний успішний запит.
FAQ
1) Чи можу я перемкнутися з MariaDB на MySQL просто змінивши Docker-образ?
Можете спробувати, і іноді воно навіть стартує. Але формати каталогів даних, системні таблиці і дефолти різняться між версіями. Плануйте міграцію: логічний дамп/відновлення або cutover на основі реплікації.
2) Чому MySQL 8 ламає мій додаток, коли MariaDB працювала?
Найчастіше: відмінності плагіну аутентифікації, строгість sql_mode і дефолти колації. Перевірте SELECT @@version_comment, плагіни користувачів і @@sql_mode.
3) Чи безпечніші іменовані томи, ніж bind mounts?
Операційно іменовані томи зменшують пастки з дозволами і випадковими записами в шар контейнера. Bind mounts підходять, коли ви контролюєте хост і маєте хороші практики щодо власності, меток і бекапів.
4) Чому база повільніша в Docker ніж на bare metal?
Якщо datadir дійсно на томі, оверхед може бути невеликим. Великі уповільнення зазвичай пов’язані з підкладним сховищем (мережеві томи, повільні диски, контенція) і поведінкою fsync — не з самим Docker.
5) Чи варто відключати налаштування fsync заради продуктивності?
Тільки якщо ви готові втратити підтверджені транзакції при краху. Для продакшн-систем, яким користувачі довіряють, спочатку виправте латентність сховища і шаблони запитів. Зміна налаштувань надійності — це бізнес-рішення, не просто тюнінг.
6) Який найшвидший спосіб підтвердити, що I/O — вузьке місце?
Шукайте в InnoDB status повідомлення про log waits, корелюйте з повільними підтвердженнями і дивіться метрики диска на хості. У інциденті патерн «записи повільні, читання нормальні» — гучна підказка.
7) Чому Compose «depends_on» не вирішує проблеми запуску?
Воно контролює порядок запуску, а не готовність. MySQL/MariaDB може приймати з’єднання до того, як зможе надійно обслуговувати запити (відновлення після краху, ініціалізація, міграції).
8) Чи можу я використовувати ту саму стратегію бекапу для MySQL і MariaDB контейнерів?
Концептуально так — логічні дампи і фізичні бекапи доступні для обох — але інструменти й деталі сумісності відрізняються. Головне правило: тестуйте відновлення з тим самим двигуном/версією, які ви будете запускати.
9) Який найнадійніший спосіб виконувати міграції схеми в контейнерах?
Запускайте міграції як явну задачу з механізмом блокування/лідерства і відкладайте розгортання додатка до успішного завершення міграції. Не дозволяйте кільком реплікам змагатися за міграцію при старті.
10) Як запобігти випадковим оновленням при перебудові образів?
Фіксуйте базові образи, фіксуйте теги/дiges образу БД і ставте зміни версій під управління змін з планом відкату.
Висновок: кроки, які запобігають повторному навчанню
«В мене працювало локально» зазвичай правда. Просто вона нерелевантна. Продакшн — інша машина з іншими режимами відмов, а бази даних підсилюють кожну маленьку брехню вашого оточення.
Зробіть ці кроки, у вказаному порядку:
- Виберіть двигун і зафіксуйте версію (і перестаньте вдавати, що MySQL і MariaDB взаємозамінні під час виконання).
- Аудитуйте шлях зберігання для datadir: тип файлової системи, опції монтування і реальна латентність fsync під навантаженням.
- Уніфікуйте дефолти: sql_mode, charset/collation, очікування аутентифікації — зробіть їх явними.
- Реалізуйте перевірки готовності та вправи з відновлення: база, яку неможливо відновити, — це чутка, а не система.
- Виконайте практичні завдання вище і зберігайте їхні виводи в runbook, щоб мати базу для порівнянь до і після змін.
Якщо ви нічого не зробите більше: перестаньте використовувати «latest», перестаньте ставитися до каталогу даних як до випадкової папки і перестаньте дозволяти дефолтам вирішувати коректність. Продакшн винагороджує нудьгу. Заробіть її.