Контейнерні бази даних — це диво продуктивності, поки не настає день відновлення. Тоді ви виявляєте, що місяцями з любов’ю відправляли в об’єктне сховище gzipped-розчарування. У нього є ім’я файлу, контрольна сума, політика утримання — і повна нездатність відновити сервіс.
«Фейкові бекапи» — це резервні копії, які завершуються успішно, але не можуть відновити консистентний робочий стан бази даних у межах обіцяного бізнесу RPO/RTO (або вашого 2:00 ночі). У контейнерах режими відмов множаться: ефермерні файлові системи, маунти томів, про які ви забули, відсутні WAL/binlog, і снапшоти, зроблені в найгірший момент.
Як виглядає фейковий бекап (і чому контейнеризація ускладнює)
Фейковий бекап — це будь-що, що дає вам впевненість, але не дає можливості відновитися. Зазвичай підозрювані:
- Бекапи, що «вдалі», але відновлення не працює (помилки прав, відсутні ролі, відсутні розширення, пошкоджений архів, несумісні версії).
- Бекапи, що відновлюються, але дані неправильні (неконсистентний снапшот, частковий дамп, відсутні binlogs/WAL, сюрпризи з часовими зонами/кодуванням).
- Бекапи, що відновлюються, але потребують 12 годин, а ваш RTO був «менше години». Файл існує; ваша робота — ні.
- Бекапи, збережені в файловій системі контейнера, яка зникає під час переселення, дренування вузла або оновлення образу.
- Бекапи, що опускають єдине потрібне: права об’єкту, процедури, тригери, користувачі, GRANT-и, конфіг, або WAL/binlog-потік для відновлення до певного моменту.
Контейнери посилюють ці проблеми, бо вони роблять «де живуть дані» підступно неоднозначним. Ви можете запустити mysqldump або pg_dump з cron у контейнері, побачити файл у /backups і відчути задоволення. Але якщо /backups — не персистентний том, ви щойно створили мотиваційний плакат.
По-друге, платформи для контейнерів підштовхують до безстанового мислення. Добре для аплікейшен-серверів. Небезпечно для баз даних. Платформа з радістю перезапустить ваш под бази під час створення снапшоту файлової системи, якщо ви не встановите запобіжні заходи. Також вона може обертати секрети й порушити роботу бекап-джоба, при цьому все ще повідомляючи CronJob як «Completed».
Ваше правило: бекап вважається «реальним» тільки якщо ви можете його відновити, перевірити коректність і зробити це в обіцяний час за допомогою автоматизації, якій ви довіряєте.
Жарт #1: Бекап, який ніколи не відновлювався, — як парашут, куплений на розпродажі й ніколи не розгорнутий. Ви можете здогадатися, як проходитиме перший тест.
MySQL vs PostgreSQL: що насправді означає «консистентний бекап»
Консистентність — це контракт, а не файл
Консистентність стосується транзакційної правильності. І MySQL (InnoDB), і PostgreSQL можуть забезпечити консистентні бекапи, але роблять це по-різному і карають за різні помилки.
MySQL: InnoDB, redo logs і binlogs
У MySQL є два великі світи: логічні бекапи (mysqldump, mysqlpump) і фізичні бекапи (Percona XtraBackup, MySQL Enterprise Backup). Логічні бекапи портативні й читабельні; фізичні швидші й кращі для великих баз, але більш чутливі до несумісностей версій і конфігурацій.
Для відновлення до певного моменту (PITR) MySQL покладається на binary log (binlog). Повний бекап без binlogs часто дає відновлення «стан на вчора ввечері», а не «п’ять хвилин тому». Якщо бізнес очікує «п’ять хвилин тому», вам потрібно відправляти і зберігати binlogs з відомим відповідником до бекапу.
Поширена модель фейкових бекапів у MySQL-контейнерах: ви запускаєте mysqldump, але забуваєте --single-transaction для InnoDB, або дамп робиться з репліки з лагом, або ви не включаєте процедури/events/triggers, або не фіксуєте користувачів/grants. Він відновлюється, але аплікація падає.
PostgreSQL: MVCC, base backups і WAL
Світ PostgreSQL — це MVCC і write-ahead logging (WAL). Консистентний фізичний бекап — це base backup плюс ті WAL-сегменти, що потрібні, щоб привести його до консистентної точки (та за бажанням — рухатися вперед у часі). Логічні бекапи (pg_dump) консистентні на рівні бази даних, але вони повільніші для великих наборів даних і не захоплюють кластерні об’єкти, якщо не додати pg_dumpall для ролей і глобалів.
Поширена модель фейкових бекапів у PostgreSQL-контейнерах: зробили снапшот файлової системи каталогу даних без координації з PostgreSQL, не включили WAL і отримали бекап, який при старті жертвеє помилкою «invalid checkpoint record». Інший випадок: використали pg_dump, але забули ролі, розширення або припущення про search_path схем.
Операційний висновок
- Якщо вам потрібні швидкі відновлення і великі бази: віддавайте перевагу фізичним бекапам (XtraBackup /
pg_basebackup) плюс PITR журнали. - Якщо потрібна портативність і невеликі набори даних: логічні бекапи підходять, але ви мусите включити всі об’єкти й тестувати відновлення.
- У контейнерах: змонтуйте персистентні томи правильно, зберігайте журнали (WAL/binlog) поза ефермерними шарами і ставтеся до завдань бекапу як до production-систем.
Одна операційна цитата, яка з роками не старіє: «Hope is not a strategy.» — Gene Kranz
Цікаві факти й історичний контекст (бо минуле пояснює ваш пейджер)
- Лінія PostgreSQL веде свій початок від проєкту POSTGRES в UC Berkeley у 1980-х; WAL і MVCC дозріли в процесі перетворення на надійну СУБД.
- Рання популярність MySQL прийшла завдяки швидкості і простоті у веб-стеках, але транзакційна надійність для серйозних навантажень з’явилася, коли InnoDB став дефолтним рушієм.
- WAL (write-ahead logging) старший за обидві СУБД; принцип походить із досліджень баз даних задовго до того, як контейнери зробили все тимчасовим.
- Реплікація MySQL історично ґрунтувалася на statement-based binlogs; сьогодні row-based logging поширений, бо уникає тонких недетермінованих проблем при реплеї.
- PITR у PostgreSQL став мейнстримом з розвитком інструментів архівації WAL; без збережених WAL «бекап» означає «часова капсула», а не «відновлення».
- Файлові снапшоти стали практичним примітивом бекапу з розвитком copy-on-write файлових систем і масивів; вони потужні, але їх легко неправильно використати без координації з базою даних.
- Percona XtraBackup здобув популярність, бо дозволив гарячі фізичні бекапи для InnoDB без вимкнення MySQL, змінивши економіку бекапів великих датасетів.
- Оркестрація контейнерів нормалізувала незмінні образи і ефермерні вузли; невідповідність зі «становими на Kubernetes» породила десятиріччя важких уроків.
- Контрольні суми і «успішні джоби» завжди вводили в оману спрощенням: успіх бекапу — це I/O подія, успіх відновлення — подія коректності. Операції вчилися цьому гучно.
Типи бекапів, які працюють у контейнерах (логічні, фізичні, снапшоти, PITR)
Логічні бекапи: портативні, повільні, легко фейкові
MySQL: mysqldump підходить, якщо ним користуватися правильно і ваш набір не масивний. Потрібен --single-transaction для InnoDB-консистентності без блокувань таблиць, і, ймовірно, бажано --routines --triggers --events. Але логічні дампи все ще можуть бути фейковими: неповні, тихо обрізані або відновлені в схему, що не відповідає продакшену.
PostgreSQL: pg_dump генерує консистентні дампи завдяки MVCC, але він на рівні бази даних. Кластерні об’єкти (ролі, tablespaces, деякі налаштування) — окремо. Якщо ви відновите в чистий кластер без відтворення ролей або розширень, аплікація поламається винахідливими способами.
Фізичні бекапи: швидкі, операційно строгі
MySQL: фізичні бекапи з XtraBackup (або підприємницький еквівалент) — дорослий вибір для великих датасетів. Але треба обробляти redo logs, підготовлювати бекап і враховувати сумісність версій.
PostgreSQL: pg_basebackup плюс архівація WAL дають консистентні, відновлювані фізичні бекапи. Це просто, але ви маєте гарантувати архівацію WAL і її збереження, а також мати чисту процедуру відновлення (включаючи конфіг-відновлення або сучасні команди відновлення).
Снапшоти: «найшвидший» бекап, який любить зраджувати
Снапшоти томів (LVM, ZFS, Ceph, EBS, CSI snapshots) відмінні, коли ви можете гарантувати консистентність. Ключове слово — гарантувати. Crash-consistent снапшот може спрацювати… поки не перестане, і ви не дізнаєтеся, який саме бекап поганий, доки він не знадобиться.
Щоб робити снапшоти правильно, координуйтеся з базою даних: flush/lock або використовуйте режими бекапу, що забезпечують відновлюваність. PostgreSQL може відновитися після краху, але снапшот, зроблений під час запису без потрібного покриття WAL, усе одно може зламатися. InnoDB MySQL може відновитися після краху, але змішування снапшотів з відсутніми логами або дивними fsync-настройками швидко стає негарним.
PITR: різниця між «втратили день» і «втратили п’ять хвилин»
PITR не опціональний, якщо ваше RPO малий. Для MySQL: binlogs. Для PostgreSQL: архівація WAL. У контейнерах PITR ламається, коли каталоги логів не персистентні, архівація неправильно налаштована або політика утримання видаляє логи раніше, ніж ви помітите.
Жарт #2: PITR — як машина часу, тільки вона йде назад і зазвичай повертає вас прямо перед виконанням руйнівної міграції. Зручно.
Практичні завдання: 12+ команд для перевірки, що бекапи справжні
Це завдання, які ви можете виконати сьогодні. Кожне містить: команду, прикладний вивід, що це означає і яке рішення прийняти.
Завдання 1: Переконайтеся, що каталог даних бази реально на персистентному маунті (хост контейнера)
cr0x@server:~$ docker inspect mysql01 --format '{{ range .Mounts }}{{ .Source }} -> {{ .Destination }} ({{ .Type }}){{ "\n" }}{{ end }}'
/var/lib/docker/volumes/mysql01-data/_data -> /var/lib/mysql (volume)
/srv/backups/mysql01 -> /backups (bind)
Значення: /var/lib/mysql — Docker volume (персистентний), а бекапи пишуться на хост-бінд у /srv/backups/mysql01.
Рішення: Якщо ви не бачите персистентного маунту для каталогу даних і директорії бекапів — зупиніться. Виправте збереження перед обговоренням інструментів.
Завдання 2: Kubernetes: перевірте, що pod використовує PVC (не emptyDir)
cr0x@server:~$ kubectl get pod pg-0 -o jsonpath='{range .spec.volumes[*]}{.name}{"\t"}{.persistentVolumeClaim.claimName}{"\t"}{.emptyDir}{"\n"}{end}'
data pgdata-pg-0
tmp map[]
Значення: Тому data відповідає PVC. tmp — ефермерний (emptyDir).
Рішення: Якщо ваші дані БД на emptyDir, у вас не збереження; у вас vibes.
Завдання 3: MySQL логічний бекап правильно (та виявлення обрізаних дампів)
cr0x@server:~$ docker exec mysql01 sh -lc 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --routines --triggers --events --set-gtid-purged=OFF --databases appdb | gzip -1 > /backups/appdb.sql.gz && ls -lh /backups/appdb.sql.gz'
-rw-r--r-- 1 root root 412M Dec 31 02:10 /backups/appdb.sql.gz
Значення: Дамп існує й має правдоподібний розмір.
Рішення: Розмір — не доказ. Далі перевірте цілісність gzip і наявність потрібних об’єктів.
Завдання 4: Перевірка цілісності gzip (ловить часткові завантаження і відмови через заповнений диск)
cr0x@server:~$ gzip -t /srv/backups/mysql01/appdb.sql.gz && echo OK
OK
Значення: Файл — валідний gzip-потік (не обрізаний).
Рішення: Якщо це падає — вважайте бекап відсутнім. Розслідуйте дисковий простір, логи джоба й кроки завантаження.
Завдання 5: Швидка саніті-перевірка всередині дампа MySQL (є схема й дані?)
cr0x@server:~$ zcat /srv/backups/mysql01/appdb.sql.gz | head -n 25
-- MySQL dump 10.13 Distrib 8.0.36, for Linux (x86_64)
--
-- Host: localhost Database: appdb
-- ------------------------------------------------------
-- Server version 8.0.36
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
...
Значення: Схоже на реальний дамп з метаданими. Також підтверджує версію MySQL на момент бекапу.
Рішення: Якщо заголовок дампа відсутній або потік порожній, ваш бекап-джоб записав сторінку з помилкою, а не SQL.
Завдання 6: MySQL: підтвердити, що binlog увімкнено (PITR вимагає цього)
cr0x@server:~$ docker exec mysql01 sh -lc 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE \"log_bin\"; SHOW VARIABLES LIKE \"binlog_format\";"'
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
Значення: Binlog увімкнено, формат ROW (зазвичай безпечніший для реплікації й PITR).
Рішення: Якщо log_bin OFF — прийміть RPO «лише повні бекапи» або увімкніть його й впровадьте доставку binlog.
Завдання 7: MySQL: перевірте, що у вас є свіжі binlogs (не просто увімкнено)
cr0x@server:~$ docker exec mysql01 sh -lc 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW BINARY LOGS;" | tail -n 5'
| binlog.000231 | 10485760 |
| binlog.000232 | 8912451 |
Значення: Binlogs існують і ротація відбувається.
Рішення: Якщо binlogs відсутні або останні — кілька днів тому, PITR не реальний. Виправте утримання й доставку.
Завдання 8: PostgreSQL: зробіть логічний бекап і захопіть globals (ролі) окремо
cr0x@server:~$ kubectl exec -it pg-0 -- bash -lc 'pg_dump -U postgres -Fc -d appdb -f /backups/appdb.dump && pg_dumpall -U postgres --globals-only > /backups/globals.sql && ls -lh /backups/appdb.dump /backups/globals.sql'
-rw-r--r-- 1 root root 2.1G Dec 31 02:12 /backups/appdb.dump
-rw-r--r-- 1 root root 19K Dec 31 02:12 /backups/globals.sql
Значення: У вас є dump у custom-форматі плюс глобальні об’єкти.
Рішення: Якщо ви дампите лише базу й ігноруєте globals — очікуйте помилок прав під час відновлення й відсутність ролей.
Завдання 9: PostgreSQL: перевірте, що архівація WAL увімкнена (PITR залежить від цього)
cr0x@server:~$ kubectl exec -it pg-0 -- bash -lc 'psql -U postgres -d postgres -c "SHOW wal_level; SHOW archive_mode; SHOW archive_command;"'
wal_level
-----------
replica
(1 row)
archive_mode
--------------
on
(1 row)
archive_command
-------------------------------------------------
test ! -f /wal-archive/%f && cp %p /wal-archive/%f
(1 row)
Значення: WAL генерується на рівні replica (добре для PITR/реплікації). Архівація увімкнена.
Рішення: Якщо archive_mode вимкнено — у вас немає PITR. Або прийміть ризик, або виправте це до обіцянки RPO.
Завдання 10: PostgreSQL: підтвердити, що WAL реально архівується (не лише налаштовано)
cr0x@server:~$ kubectl exec -it pg-0 -- bash -lc 'psql -U postgres -d postgres -c "SELECT now(), last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time FROM pg_stat_archiver;"'
now | last_archived_wal | last_archived_time | failed_count | last_failed_wal | last_failed_time
-------------------------------+-------------------+----------------------------+--------------+-----------------+------------------
2025-12-31 02:14:01.12345+00 | 0000000100000000000000A7 | 2025-12-31 02:13:58+00 | 0 | |
(1 row)
Значення: Архівація вдається й недавня.
Рішення: Якщо failed_count росте або last_archived_time застарілий — ваш PITR-потік зламався. Викличте відповідального до того, як це знадобиться.
Завдання 11: Тест відновлення MySQL-дампа в тимчасовому контейнері (єдиний тест, що має значення)
cr0x@server:~$ docker run --rm --name mysql-restore -e MYSQL_ROOT_PASSWORD=restorepass -d mysql:8.0
...output...
cr0x@server:~$ sleep 15 && zcat /srv/backups/mysql01/appdb.sql.gz | docker exec -i mysql-restore mysql -uroot -prestorepass
...output...
cr0x@server:~$ docker exec mysql-restore mysql -uroot -prestorepass -e "SHOW DATABASES; USE appdb; SHOW TABLES;" | tail -n 10
Tables_in_appdb
users
orders
order_items
Значення: Дамп імпортується й схема існує.
Рішення: Якщо імпорт падає — бекап фейковий. Якщо імпорт пройшов, але запити аплікації падають — вам бракує об’єктів (рoutines, triggers) або ви покладаєтеся на середовищні налаштування.
Завдання 12: Тест відновлення PostgreSQL-дампа в тимчасовий інстанс
cr0x@server:~$ docker run --rm --name pg-restore -e POSTGRES_PASSWORD=restorepass -d postgres:16
...output...
cr0x@server:~$ sleep 10 && cat /srv/backups/pg/globals.sql | docker exec -i pg-restore psql -U postgres
...output...
cr0x@server:~$ docker exec -i pg-restore createdb -U postgres appdb
...output...
cr0x@server:~$ docker exec -i pg-restore pg_restore -U postgres -d appdb /srv/backups/pg/appdb.dump
pg_restore: connecting to database for restore
pg_restore: creating TABLE "public.users"
pg_restore: creating TABLE "public.orders"
...output...
Значення: Дамп відновлюється чисто в новому кластері й відтворює створення об’єктів.
Рішення: Якщо падає через ролі/права — ваша globals-захоплення неповне. Якщо через розширення — треба попередньо інсталювати їх у середовищі відновлення.
Завдання 13: Перевірка безпеки снапшоту: чи ви бекапите правильний шлях?
cr0x@server:~$ docker exec mysql01 sh -lc 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE \"datadir\";"'
+---------------+----------------+
| Variable_name | Value |
+---------------+----------------+
| datadir | /var/lib/mysql/ |
+---------------+----------------+
Значення: Підтверджує реальний каталог даних.
Рішення: Якщо ваш снапшот береться з іншого хост-шляху, ніж змонтований datadir, ви бекапите не те. Таке трапляється.
Завдання 14: Базова перевірка ємності: бекапи падають через тиск диска?
cr0x@server:~$ df -h /srv/backups
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 1.8T 1.7T 62G 97% /srv/backups
Значення: Ви на 97% заповнення. Це не стиль життя.
Рішення: Якщо ви регулярно перевищуєте ~85–90%, очікуйте часткові файли, невдалі fsync і «бекап успішний» у логах, що не відповідає реальності. Виправте політику утримання, компресію й розмір сховища.
Завдання 15: Підтвердити, що джоби бекапу не «успішні», коли фактично провалені (Kubernetes CronJob)
cr0x@server:~$ kubectl get job -n db -l app=pg-backup -o wide
NAME COMPLETIONS DURATION AGE CONTAINERS IMAGES SELECTOR
pg-backup-28911 1/1 9s 2h backup alpine:3.20 controller-uid=...
cr0x@server:~$ kubectl logs -n db job/pg-backup-28911 | tail -n 20
pg_dump: error: connection to server at "pg" (10.0.2.44), port 5432 failed: FATAL: password authentication failed for user "postgres"
Значення: Джоб завершився з Kubernetes-перспективи, але бекап провалився.
Рішення: Логи — джерело істини. Переконайтеся, що джоб виходить з ненульовим кодом у разі помилки (не «гасіть» помилки у шелл-скриптах).
Плейбук швидкої діагностики: знайти вузьке місце швидко
Коли бекапи повільні, відсутні або не відновлюються, у вас немає часу на філософію. Потрібен компактний цикл. Ось порядок, який ловить найбільше проблем найшвидше.
Перше: чи валідний артефакт бекапу?
- Перевірте цілісність: gzip test, tar listing або інспекція формату дампа.
- Перевірте тренд розміру: раптове падіння або стрибок зазвичай означає помилку або неконтрольований ріст даних.
- Перевірте час останнього успішного тесту відновлення: якщо його немає — ви нічого не знаєте.
Друге: чи включений у pipeline журнал для PITR?
- MySQL: binlog увімкнено, ротація, доставка, збереження.
- PostgreSQL: архівація WAL увімкнена,
pg_stat_archiverздоровий, утримання покриває бажане вікно.
Третє: вузьке місце — CPU, диск чи мережа?
- CPU-bound: занадто високий рівень компресії, шифрування, однопоточні дампи.
- Disk-bound: повільний PVC, насичений диск вузла, затримки копіювання снапшоту.
- Network-bound: пропускна здатність об’єктного сховища, тротлінг або ліміти між зонами.
Четверте: чи робите ви консистентні бекапи?
- MySQL:
--single-transactionдля дампів; фізичний бекап підготовлений; координація снапшоту. - PostgreSQL: base backup + WAL; або
pg_dumpплюс globals; координація снапшоту.
П’яте: чи відновлення вписується в RTO?
- Тестуйте час відновлення від початку до кінця: завантаження + декрипт + розпакування + імпорт + побудова індексів + перевірочні запити.
- Якщо RTO не витримується — змініть метод бекапу, паралелізуйте відновлення або змініть бізнес-обіцянку. Виберіть щось.
Три корпоративні міні-історії зі світу «в CI пройшло»
Інцидент: неправильне припущення (логічний дамп «включає все», правда?)
Середня SaaS-компанія запускала PostgreSQL у Kubernetes. Їхній бекап-джоб робив pg_dump -Fc щодня, відправляв файл в об’єктне сховище й крутив політику утримання. Зелені дашборди. Ніяких алертів. Така інфраструктура, що змушує менеджерів задоволено кивати.
Потім стався інцидент: міграція схеми пройшла не так і потрібне було відновлення на «вчора». Вони підняли новий кластер і відновили дамп. Технічно воно працювало. База стартувала. Таблиці були. Але аплікація не змогла аутентифікувати користувачів і почала видавати помилки прав як метроном.
Банальна деталь: ролі й гранти не були включені. У продакшені кілька ролей створили вручну під час розборок на виклику місяці тому. Дамп захопив об’єкти, але не модель ідентичності й привілеїв на рівні кластера. Їхній «бекап» не знав, хто має які права.
Виправлення було негайним: додали pg_dumpall --globals-only до pipeline, задокументували створення ролей через міграції/ІаС і додали тест відновлення, що виконує мінімальний логін-дроу для аплікації проти відновленої БД. Вони припинили вважати, що «бекап бази» — це «відновлення аплікації».
Оптимізація, що повернулася бумерангом (компресія і хитрощі проти часу)
Е-комерс платформа перейшла з фізичних на логічні бекапи, щоб «спростити». База — кілька сотень ГБ. Дампи сильно компресували, щоб зекономити на об’єктному сховищі. Бекапи робилися в робочий час, бо «контейнери масштабуються». Джоб робив nice-значення і все здавалося цивілізовано.
Що сталося — класика: CPU підскочив через важку компресію, що викликало затримки запитів. Потім бекап йшов довше й перекривав пікові навантаження. Це ще більше посилило конкуренцію ресурсів і робило дамп ще довшим. База не впала, але була гірша — повільна, непередбачувана і зла.
Коли вони протестували час відновлення — було жорстоко: завантаження й декомпресія тривали вічність, потім імпорт і відновлення індексів. Команда оптимізувала неправильну метрику (вартість зберігання) і проігнорувала ту, що важить під час інциденту (час до відновлення).
Вони перейшли на фізичні бекапи для MySQL, зменшили компресію до розумного рівня й впровадили доставку binlog для PITR. Вартість зберігання зросла, але шлях відновлення став таким, що можна було оголосити в статусі.
Нудна, але правильна практика, що виручила (репетиції відновлення й фіксація версій)
Команда фінансових послуг запускала PostgreSQL з архівацією WAL і тижневими base backup. Нічого модного: консистентна конфігурація, суворе утримання і щомісячна репетиція відновлення в ізольованому середовищі. Вони фіксували мінорні версії Postgres для середовищ відновлення й мали матрицю «бекап взято на версії X, відновлено на X/Y», яку вони реально тестували.
Одного кварталу інцидент зберігання пошкодив первинний том і база впала. Реплікація не врятувала: репліка вже відтворила корупцію. Єдиний шлях — відновлення.
План реагування був болісно детальний. Отримати base backup, отримати WAL з архіву, відновити в новий PV, відтворити до потрібного часу, виконати перевірочні запити, переключити трафік. Вони зробили це крок за кроком з тією самою спокійною енергією, що й люди, які репетирували нудну річ.
Вони відновилися у вікно, яке обіцяли. Без героїзму. Без археології у Slack. Пост-інцидентний розбір був короткий, бо система зробила те, для чого її спроектували. Це стандарт, який варто копіювати.
Поширені помилки: симптом → корінна причина → виправлення
1) «Джоб бекапу каже success, але файл крихітний»
Симптом: бекапи раптово впали з сотень MB/GB до кількох KB/MB.
Корінна причина: джоб записав повідомлення про помилку у вихідний потік, автентифікація не пройшла, або диск заповнився під час запису; pipeline все одно повернув exit code 0 через недбалі скрипти.
Виправлення: увімкніть set -euo pipefail у скриптах; перевіряйте цілісність артефакту (gzip test); алертіть на аномалії розміру; робіть джоб невдалим за stderr-шаблонами або ненульовими кодами виходу.
2) «Postgres відновлення падає: invalid checkpoint / відсутній WAL»
Симптом: відновлений каталог даних не стартує, скаржиться на checkpoint record або відсутні WAL.
Корінна причина: краш-неконсистентний снапшот або base backup без відповідних WAL-сегментів.
Виправлення: використовуйте pg_basebackup з архівацією WAL; забезпечте утримання WAL; якщо використовуєте снапшоти — координуйте їх із PostgreSQL і включайте WAL, потрібні для консистентності.
3) «MySQL відновлюється, але відсутні routines/events»
Симптом: після відновлення аплікація помиляється; відсутні збережені процедури або заплановані події.
Корінна причина: mysqldump без --routines --events --triggers.
Виправлення: додайте ці прапори; додайте перевірочні запити відновлення, що перевіряють наявність цих об’єктів.
4) «Бекапи є, але PITR неможливий»
Симптом: ви можете відновити лише на час бекапу, а не на певний момент.
Корінна причина: binlogs/WAL не увімкнено, не архівуються або не утримуються достатньо довго; шлях логів ефермерний у контейнері.
Виправлення: увімкніть і шипіть binlogs/WAL у надійне сховище; моніторьте здоров’я архівації; вирівняйте утримання з бізнес-RPO.
5) «Kubernetes переселив pod і ви втратили бекапи»
Симптом: бекапи зникають після обслуговування вузла або перезапуску pod.
Корінна причина: бекапи писалися в файлову систему контейнера або в emptyDir.
Виправлення: пишіть бекапи на PVC або стрімте безпосередньо в об’єктне сховище; перевіряйте маунти через манифести і runtime-інспекцію.
6) «Відновлення надто повільне для RTO»
Симптом: відновлення триває години/дні; реакція на інцидент утримується швидкістю імпорту.
Корінна причина: логічні дампи для дуже великих датасетів, надмірна компресія, однопоточний шлях відновлення, повільний клас зберігання.
Виправлення: перейдіть на фізичні бекапи; налаштуйте компресію; тестуйте час відновлення; розгляньте репліки для швидшого переключення; використовуйте швидші PVC/шари зберігання для середовищ відновлення.
7) «Бекап з репліки відновлює старі дані»
Симптом: відновлення консистентне, але бракує недавніх записів.
Корінна причина: бекап зроблено з відстаючої репліки без перевірки лагу, або реплікація була поламана.
Виправлення: вимагайте перевірки лага перед бекапом; алертіть про здоров’я реплікації; для PITR покладайтеся на логи з відомим порядком і утриманням.
8) «Все відновлено, але аплікація все одно падає»
Симптом: БД запущена; аплікація видає помилки аутентифікації, відсутні розширення, проблеми з кодуванням, дивні часові зсуви.
Корінна причина: ви бекапили дані, але не контракт середовища: ролі, розширення, колації, конфігураційні припущення.
Виправлення: закодьте bootstrap БД (ролі/розширення) як код; додайте smoke-тести, що виконують реальні запити аплікації; зберігайте образ/версію середовища відновлення разом із бекапами.
Чеклісти / покроковий план (цілком навмисно нудно)
Крок 1: Визначте цілі відновлення (RPO/RTO) і зробіть їх виконуваними
- Визначте RPO (максимально допустима втрата даних) і RTO (максимально допустиме простоювання).
- Перекладіть їх у реалізацію: частота повних бекапів + утримання логів PITR + бюджет часу на відновлення.
- Якщо ви не можете дозволити собі складність PITR — визнайте, що RPO = «останній бекап». Не імпровізуйте пізніше.
Крок 2: Виберіть правильний метод бекапу для бази й розміру
- MySQL small/medium:
mysqldumpз правильними прапорами + доставка binlog, якщо потрібен PITR. - MySQL large: фізичні бекапи (XtraBackup/enterprise) + доставка binlog.
- PostgreSQL small/medium:
pg_dump -Fc+pg_dumpall --globals-only. - PostgreSQL large:
pg_basebackup+ архівація WAL, плюс періодичні тести відновлення.
Крок 3: Зробіть явним розміщення сховища у манифестах контейнерів
- Каталог даних на PVC/томі з відомим класом продуктивності.
- Каталог стагінгу бекапів на PVC (або стрім у об’єктне сховище).
- WAL/binlog на надійному сховищі, якщо використовується локальна архівація.
Крок 4: Вбудуйте валідацію в pipeline
- Перевірки цілісності артефактів: gzip/tar валідація, інспекція формату дампа.
- Захоплення метаданих: версія БД, хеш схеми, мітка часу бекапу, версія інструменту.
- Автоматизація тесту відновлення: відновлення в тимчасове середовище і запуск перевірочних запитів.
Крок 5: Моніторьте систему бекапів як продакшен
- Алерти на відсутні бекапи, аномалії розміру і застарілу архівацію WAL/binlog.
- Відстежуйте тренди часу відновлення (завантаження + декрипт + розпакування + застосування + перевірка).
- Робіть провали бекапів «page-worthy», якщо ви обіцяєте RPO/RTO. Тихі провали породжують фейкові бекапи.
Крок 6: Практикуйте відновлення (і контроль дрейфу версій)
- Мінімум щомісячні репетиції відновлення. Щотижнево для критичних баз.
- Фіксуйте версії інструментів відновлення (mysqldump/mysql client, pg_restore тощо).
- Тримайте відомий добрий образ середовища відновлення для кожної основної версії БД.
ЧаПи
1) Що саме робить бекап «фейковим»?
Усе, що не може відновити консистентний робочий стан у потрібному часовому вікні. «Файл існує» і «джоб успішний» не рахуються.
2) Чи логічні бекапи (mysqldump/pg_dump) за своєю суттю небезпечні?
Ні. Їх легко неправильно налаштувати і вони повільні в масштабі. Логічні бекапи підходять, якщо ви тестуєте відновлення і ваш RTO дозволяє час імпорту.
3) У MySQL, чи --single-transaction завжди потрібен?
Для консистентних InnoDB-дампів без блокувань таблиць — так. Якщо у вас є нетранзакційні таблиці (наприклад, MyISAM), консистентність ускладнюється і варто переглянути рушій зберігання або метод бекапу.
4) У PostgreSQL, чому потрібен pg_dumpall --globals-only?
pg_dump працює на рівні однієї бази даних. Ролі й інші глобальні об’єкти живуть на рівні кластера. Без них відновлення часто падає через проблеми з правами.
5) Чи можна покладатися тільки на снапшоти томів як єдиний бекап?
Лише якщо ви можете гарантувати консистентність і протестували відновлення зі снапшотів. Снапшоти відмінні як компонент, але не як догма. Більшість команд комбінують снапшоти (швидкі) з PITR-логами (точкове відновлення).
6) Який найпростіший патерн бекапу, безпечний для контейнерів?
Стріми бекап безпосередньо в надійне сховище (об’єктне сховище або backup-сервер) і мінімізуйте локальний стейджинг. Не записуйте критичні артефакти в ефермерні шари контейнера.
7) Як дізнатися, що PITR реально працює?
MySQL: підтвердіть, що binlog увімкнено, binlogs присутні й недавні, і ви можете відтворити до цільового таймстемпу в тестовому відновленні. PostgreSQL: pg_stat_archiver показує недавню успішну архівацію і ви можете відновити до цільового часу за допомогою архівованих WAL.
8) Звідки робити бекап — з primary чи replica?
Бекапи з репліки зменшують навантаження на primary, але треба примусово перевіряти здоров’я реплікації і пороги лага. Інакше ви отримаєте чисті, консистентні, але «неправильного часу» бекапи.
9) Чому відновлення падає лише в продакшеноподібних середовищах?
Бо продакшен має брудні частини: ролі, гранти, розширення і поведінку аплікації, що тестують ваші припущення. Ваші тести відновлення мають включати мінімальний smoke-тест аплікації, а не лише «БД запустилася».
10) Який мінімальний тест відновлення я маю автоматизувати?
Підніміть тимчасовий інстанс БД, відновіть останній бекап, виконайте кілька репрезентативних запитів (включно з критичною для аутентифікації логікою) і зафіксуйте загальний час. Якщо падає — гучно повідомляйте.
Висновок: кроки, що реально зменшують ризик
Якщо більше нічого не встигаєте — зробіть ці три речі цього тижня:
- Протестуйте відновлення останнього бекапу MySQL і/або PostgreSQL у тимчасовому середовищі. Заміруйте час. Задокументуйте.
- Перевірте передумови PITR: binlogs для MySQL або архівація WAL для PostgreSQL з утриманням, що відповідає RPO.
- Зробіть сховище явним: переконайтеся, що дані, бекапи й логи живуть на персистентних томах або в надійному віддаленому сховищі, а не в ефермерних шарах контейнера.
Далі зробіть дорослий крок: інтегруйте ці перевірки в автоматизацію й моніторинг, щоб припинити покладатися на пам’ять, надію і ту одну людину, що «знає бекапи». Мета — не файл бекапу. Мета — відпрацьоване відновлення.