Pager дзвонить, Slack заповнюється повідомленнями, і хтось вимовляє фразу, яка перетворює вашу каву на механізм виживання:
«Я запустив delete у проді».
Тут вмирає міф про «ми маємо реплікацію». Реплікація чудова для того, щоб залишатися онлайн під час відмов обладнання.
Вона ж погано рятує від людей, бо реплікує людей з вражаючою точністю. Те, що рятує — це
здатність перемотати час назад: відновлення до точки в часі (PITR) плюс операційна дисципліна, щоб це працювало насправді.
Реплікація проти PITR: дві різні обіцянки
Обіцянка реплікації: залишатися онлайн, коли щось ламається
Реплікація — про доступність і масштабування читань. Вона відповідає на питання «що, якщо вузол помре?» і інколи «що, якщо зона помре?»
Вона не відповідає на питання «що, якщо ми записали непотрібні дані?» Якщо ви реплікуєте записи, ви реплікуєте помилки, видалення схем і невдалі деплои.
Болюча фраза — «лаг реплікації». Люди ставляться до неї як до метрики продуктивності; під час інциденту вона стає інструментом відновлення.
Лаг може дати вам вікно, щоб зупинити радіус ураження. Але це ненадійний ремінь безпеки: іноді він є, іноді — ні,
і він не був призначений для того краху, що щойно трапився.
Обіцянка PITR: перемотати до відомого доброго часу
PITR — це стратегія бекапів, не реплікації. Концепція проста: зробити базовий бекап (повний знімок) і далі
зберігати журнали передзапису (WAL у PostgreSQL; бінлоги у MariaDB). Для відновлення ви відновлюєте базовий бекап і програєте логи
до моменту трохи перед помилкою.
PITR — те, що потрібно, коли проблема в тому, що «ми змінили дані», а не «втратили сервер». Це також потрібно, коли
«статичний» звіт вирішив бути творчим і виконав UPDATE без WHERE.
Одна перефразована ідея від John Allspaw (операції та стійкість): «Надійність приходить від проєктування під відмови, а не від припущення, що їх не буде.»
Реплікація тримає сервіс онлайн. PITR тримає вашу кар’єру онлайн.
Режими людської помилки: що насправді відбувається
Нудна таксономія катастроф (яка повторюється)
- Випадкове видалення/оновлення: відсутній WHERE, неправильний tenant ID або «швидкий фікс» в консолі.
- Невдалі зміни схеми: видалення стовпця/таблиці, неправильний порядок міграцій, додавання NOT NULL до бекфілла.
- Помилки імпорту даних: CSV з невірним відображенням, ETL запускається в неправильному середовищі, дубльовані ключі.
- Помилки привілеїв: надання зайвих прав, відкликання доступу або виконання обслуговування як суперкористувач.
- Баги в застосунку: «працює в staging», а потім новий код видаляє не ті рядки в масштабі.
Чому реплікація тут підводить
Більшість реплікації в MariaDB і PostgreSQL розроблена для реплікації зафіксованих змін. Вона достатньо детермінована,
щоб зберігати копії консистентними. Це чудово, поки зміна, яку ви зафіксували, саме те, що потрібно відмінити.
Перша пастка — психологічна: команди бачать кілька нод і припускають «ми завжди можемо розвернутись». Failover не відміняє записи.
Він просто перемикає вас на іншу копію тієї самої помилки.
Жарт №1: Реплікація як груповий чат — усі отримують повідомлення, включно з тим ганебним, яке ви б хотіли відкликати.
Чого PITR також не зробить (щоб ви не романтизували його)
PITR не відтворить історію, яку ви не зберегли. Якщо ви не архівуєте WAL/binlog надійно або обрізуєте їх занадто агресивно,
ваша кнопка «перемотати» буде декоративною. PITR також не виправить корупцію на рівні застосунку, якщо ви не можете ідентифікувати безпечну точку в часі.
Вам все одно потрібна судова експертиза: коли почався поганий запис і коли він закінчився?
Механіка відновлення MariaDB: binlog, GTID, репліки
Базова реплікація: що ви фактично маєте в проді
Реплікація MariaDB (і родоводу MySQL) зазвичай використовує бінарні логи з primary, які застосовують репліки. Ви зустрінете
реплікацію за файлом/позицією та реплікацію на основі GTID. GTID загалом легше експлуатувати, але він не чудодійний засіб від людських помилок;
він просто робить failover і зміни топології менш схильними до помилок.
Якщо ви запускаєте semi-sync, ви зменшуєте шанс втратити останні транзакції під час failover. Це ручка для доступності/втрати даних,
а не для людської помилки.
PITR в MariaDB: базовий бекап + відтворення binlog
PITR в MariaDB зазвичай: зробити повний бекап (логічний з mariadb-dump або фізичний з інструментами як mariabackup),
потім зберігати бінлоги і відтворювати їх до моменту одразу перед шкідливою командою. Операційна перевага в тому, що відтворення binlog можна
фільтрувати і таргетувати, якщо ви використовуєте логування на рівні рядків і маєте хороші таймстемпи/GTID.
Бінлоги у форматі statement можуть бути підводним каменем під час відновлення, бо відтворення може бути недетермінованим залежно від функцій, часу
або недетермінованих операторів. Логування на рівні рядків важче по розміру, але передбачуваніше для відновлення.
Патерн «затримана» репліка (корисно, але не план)
Деякі команди тримають відкладену репліку (усвідомлений лаг), щоб випадкові видалення на primary не застосовувалися негайно.
Це може врятувати, якщо помилку помітили швидко і вікно затримки покриває момент.
Це все ще не повний план відновлення. Затримані репліки обриваються на практиці: хтось перезапускає їх і вони «наздоганяють»,
або SQL-потік зупиняється і ви цього не помічаєте до тих пір, поки він не стане марним, або затримка занадто мала для повільної корупції.
Механіка відновлення PostgreSQL: WAL, таймлайни, стендаби
Базова реплікація: потокова реплікація та її форма
Streaming replication у PostgreSQL відсилає WAL від primary до стендабів. Стендаби можуть бути синхронними або асинхронними.
Як і в MariaDB, реплікація — про те, щоб мати копію поруч для takeover.
Операційна особливість: у PostgreSQL є таймлайни. Коли ви піднімаєте стендаб (promote), створюється новий таймлайн. Це нормально,
але важливо для PITR, бо ваш архів WAL має включати файли історії таймлайну, і ви повинні розуміти, що саме відновлюєте.
PITR у PostgreSQL: базовий бекап + архівація WAL
PITR у PostgreSQL — першокласна концепція: базовий бекап плюс безперервна архівація сегментів WAL. Відновлення налаштовується шляхом
відновлення базового бекапу, потім використання recovery-сигналу плюс цільового часу/LSN і програвання WAL з архіву.
Велика перевага: PostgreSQL має сильні інструменти навколо програвання WAL і чітке розмежування між потоковою реплікацією та архівацією WAL.
Але вам усе одно треба побудувати конвеєр: надійність archive_command, ретенція, валідація і тести відновлення.
Жарт №2: WAL — чорна скринька вашої бази даних — хіба що вона допомагає, лише якщо ви не зберегли її на тому ж літаку.
Логічна реплікація — не ваш парашут
Логічна реплікація PostgreSQL (і подібні CDC-системи) може допомогти селективно реплікувати таблиці і уникнути деяких проблем зі змінами схеми.
Вона не замінює PITR. Логічна реплікація також стрімить зміни, включно з помилками, і часто не має засобів для перемотування назад без окремої стратегії знімків.
Що вас рятує після людської помилки: матриця рішень
Перші принципи: визначте питання, перш ніж обирати інструмент
Після людської помилки вам потрібні відповіді на три питання:
- Радіус ураження: які дані змінилися і в якому обсязі?
- Часові межі: коли почалася шкідлива зміна і коли її виявили?
- Ціль відновлення: потрібно відновити весь кластер, чи лише схему/таблицю/тенанта?
Реплікація допомагає коли:
- Вузол упав і потрібно зробити failover з мінімальним даунтаймом.
- Потрібна копія лише для читань, щоб зняти навантаження.
- Потрібна швидка заміна потужності, поки ви відновлюєте втрачений хост.
PITR допомагає коли:
- Дані були змінені неправильно і треба повернутися «назад у часі».
- Було застосовано зміни схеми і треба відновитись до моменту перед ними.
- Рансомвар/компроміс зашифрував або змінив дані на всіх репліках.
Жорстока правда: вам зазвичай потрібні обидва
Достатньо зрілі системи використовують реплікацію для доступності і PITR для відновлення. Якщо бюджет змушує обирати, обирайте PITR спочатку для
систем, де коректність даних важливіша за час роботи. Це більшість бізнес-систем, навіть тих власників яких вважають,
що «ми можемо це відтворити». Вони не можуть. Вони просто не хочуть платити за зберігання.
Практичні завдання (команди, виводи, рішення)
Це ті завдання, які ви запускаєте під час тренування або інциденту: швидко, конкретно і пов’язані з рішеннями. Наведені виводи
є репрезентативними. Ваше середовище відрізнятиметься, але сенс лишається.
Завдання 1 — MariaDB: підтвердити, що binlog увімкнений і який формат
cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'binlog_format';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
+---------------+-----------+
| Variable_name | Value |
+---------------+-----------+
| binlog_format | ROW |
+---------------+-----------+
Що це означає: бінлоги існують і вони на рівні рядків (добре для детермінованого відтворення).
Рішення: якщо log_bin вимкнений, припиніть вважати, що у вас є PITR; у вас лише бекапи.
Якщо binlog_format STATEMENT, розгляньте перехід на ROW для покращення відновлюваності (після оцінки впливу на навантаження).
Завдання 2 — MariaDB: знайти поточний бінлог файл/позицію (база для хроніки інциденту)
cr0x@server:~$ mariadb -e "SHOW MASTER STATUS\G"
*************************** 1. row ***************************
File: mariadb-bin.001842
Position: 987654321
Binlog_Do_DB:
Binlog_Ignore_DB:
Що це означає: це ідентифікує, де primary «зараз».
Рішення: зафіксуйте це в документі інциденту; ви будете використовувати файл/позицію або діапазони GTID для обмеження відновлення.
Завдання 3 — MariaDB: перевірити стан репліки і лаг
cr0x@server:~$ mariadb -e "SHOW SLAVE STATUS\G" | egrep "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Using_Gtid"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 2
Using_Gtid: Current_Pos
Що це означає: репліка здорова і практично наздогнала.
Рішення: якщо людська помилка щойно сталася і лаг малий, ви, ймовірно, не зможете використати лаг як вікно безпеки.
Якщо Slave_SQL_Running No, трактуйте це як інцидент — ваша «безпечна репліка» може бути мовчазно мертва.
Завдання 4 — MariaDB: негайно зупинити репліку, щоб зберегти копію «до помилки» (якщо встигли помітити)
cr0x@server:~$ mariadb -e "STOP SLAVE SQL_THREAD; SHOW SLAVE STATUS\G" | egrep "Slave_SQL_Running|Seconds_Behind_Master"
Slave_SQL_Running: No
Seconds_Behind_Master: 57
Що це означає: SQL-потік зупинено; IO-потік може ще завантажувати бінлоги, але не застосовувати їх.
Рішення: зафіксуйте цю репліку. Не перезапускайте SQL-потік, поки не витягли потрібні рядки або не склали план відновлення.
Це тактичний крок, а не фінальне відновлення.
Завдання 5 — MariaDB: знайти підозрілу подію в бінлогах за часом
cr0x@server:~$ mysqlbinlog --start-datetime="2025-12-30 09:55:00" --stop-datetime="2025-12-30 10:05:00" /var/lib/mysql/mariadb-bin.001842 | head -n 30
# at 456700001
#251230 9:58:12 server id 101 end_log_pos 456700321 CRC32 0x2a1b3c4d GTID 0-101-998877
BEGIN
# at 456700321
#251230 9:58:12 server id 101 end_log_pos 456701234 CRC32 0x1c2d3e4f Query thread_id=8899 exec_time=0 error_code=0
use appdb/*!*/;
SET TIMESTAMP=1767088692/*!*/;
DELETE FROM orders WHERE tenant_id=42;
/*!*/;
Що це означає: ви знайшли шкідливу команду і її час/GTID.
Рішення: встановіть свій PITR stop point одразу перед цим GTID/часом; або сплануйте повторне вставлення таблиці, якщо це можливо.
Якщо бінлог на рівні рядків, вивід покаже row events замість SQL, що все одно дозволяє відтворювати/пропускати точно.
Завдання 6 — MariaDB: відтворити бінлоги в відновлену копію до безпечної точки
cr0x@server:~$ mysqlbinlog --stop-datetime="2025-12-30 09:58:11" /archives/mariadb-bin.* | mariadb appdb
Query OK, 0 rows affected (0.001 sec)
Query OK, 0 rows affected (0.000 sec)
Що це означає: ви застосували зміни до моменту одразу перед шкідливою командою.
Рішення: перевірте кількість рядків/бізнес-інваріанти на цій відновленій копії; потім вирішіть чи перемикатися, витягнути рядки або виконати контрольований merge.
Завдання 7 — PostgreSQL: підтвердити, що архівація WAL налаштована
cr0x@server:~$ psql -X -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 увімкнена, а archive_command — просте копіювання в локальний архів.
Рішення: якщо archive_mode вимкнений, у вас немає PITR. Якщо archive_command крихкий (без повторів, без алертингу),
розглядайте це як баг на надійності і виправте до того, як воно знадобиться.
Завдання 8 — PostgreSQL: перевірити, що WAL фактично архівуються (не лише налаштовано)
cr0x@server:~$ psql -X -c "SELECT now(), last_archived_wal, last_archived_time, failed_count FROM pg_stat_archiver;"
now | last_archived_wal | last_archived_time | failed_count
-------------------------------+---------------------------+-----------------------------+--------------
2025-12-30 10:06:40.12345+00 | 000000010000003A0000009F | 2025-12-30 10:06:12+00 | 0
(1 row)
Що це означає: архівація працювала нещодавно; збоюв немає.
Рішення: якщо failed_count зростає, зупиніться. Ви будуєте хибне відчуття безпеки.
Виправте помилки архівації першими, інакше відновлення впирається у відсутній сегмент і зупиниться наполовину.
Завдання 9 — PostgreSQL: перевірити лаг реплікації під час інциденту
cr0x@server:~$ psql -X -c "SELECT application_name, state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
application_name | state | write_lag | flush_lag | replay_lag
------------------+-----------+-----------+-----------+------------
standby-a | streaming | 00:00:00 | 00:00:00 | 00:00:02
(1 row)
Що це означає: стендаб відстає на 2 секунди у replay.
Рішення: цей лаг не є планом відновлення. Якщо шкідливий запис уже зафіксований, він, ймовірно, потрапить і на стендаб.
Якщо потрібна «точка замороження», дійте швидко (або покладайтеся на PITR).
Завдання 10 — PostgreSQL: знайти приблизний час шкідливих змін за логами/LSN
cr0x@server:~$ psql -X -c "SELECT now(), pg_current_wal_lsn();"
now | pg_current_wal_lsn
-------------------------------+--------------------
2025-12-30 10:07:10.551+00 | 3A/9F123ABC
(1 row)
Що це означає: у вас є маркер поточного LSN.
Рішення: під час відповіді на інцидент записуйте LSN разом з часом. Вони допомагають зіставити події, якщо годинники дрейфують або логи заплутані.
Завдання 11 — PostgreSQL: відновити базовий бекап і налаштувати ціль PITR
cr0x@server:~$ sudo -u postgres bash -lc 'cat > /var/lib/postgresql/16/main/postgresql.auto.conf <
Що це означає: відновлення буде підхоплювати WAL з архіву і зупиниться на цільовому часі.
Рішення: оберіть цільовий час одразу перед руйнівною транзакцією. Якщо невпевненість, виберіть раніше і потім обережно застосуйте відомі коректні зміни.
Завдання 12 — PostgreSQL: підтвердити, що відновлення зупинилося там, де ви просили
cr0x@server:~$ psql -X -c "SELECT pg_is_in_recovery(), pg_last_wal_replay_lsn();"
pg_is_in_recovery | pg_last_wal_replay_lsn
-------------------+------------------------
f | 3A/9EFFFF00
(1 row)
Що це означає: відновлення завершено (f) і ви маєте останній промотаний LSN.
Рішення: запускайте валідаційні запити зараз. Якщо дані виглядають правильно, плануйте cutover. Якщо ні, перезапустіть відновлення з іншою ціллю часу/LSN.
Завдання 13 — Перевірка сховища: переконатися, що архівний том має місце і не мовчки заповнений
cr0x@server:~$ df -h /wal-archive
Filesystem Size Used Avail Use% Mounted on
/dev/sdb1 500G 412G 63G 87% /wal-archive
Що це означає: архів WAL наближається до заповнення.
Рішення: якщо це досягне 100%, архівація WAL впаде і PITR зламається. Впровадьте політики ретенції і алертингу; не «просто додавати місце» як стратегію.
Завдання 14 — MariaDB: перевірити, що ретенція binlog не з’їсть ваше вікно відновлення
cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'expire_logs_days'; SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';"
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| expire_logs_days| 1 |
+-----------------+-------+
+---------------------------+--------+
| Variable_name | Value |
+---------------------------+--------+
| binlog_expire_logs_seconds| 0 |
+---------------------------+--------+
Що це означає: бінлоги видаляються через 1 день за expire_logs_days.
Рішення: якщо ваш час виявлення помилок довший, ніж день (зазвичай так), збільшіть ретенцію або відправляйте бінлоги на надійне сховище.
Інакше PITR зазнає невдачі через відсутні логи саме тоді, коли вони знадобляться.
План швидкої діагностики
Коли хтось кричить «дані зникли», треба визначити, чи це (a) проблема реплікації, (b) проблема бекапів/PITR,
або (c) баг у застосунку, що ще активно руйнує дані. Це сортування, не філософія.
Перше: зупиніть кровотечу
- Заморозьте запис: вимкніть джоб, feature flag або деплой. Якщо не можете, тимчасово заблокуйте застосунок на рівні БД.
- Збережіть докази: зробіть знімок відповідного тому, якщо можете; зупиніть застосування на репліці, якщо встигли.
- Зафіксуйте маркери: поточний час, поточний бінлог файл/позиція або WAL LSN, і хто що запускав.
Друге: вирішіть, який шлях відновлення можливий
- Чи є безпечна репліка? Лише якщо вона точно до помилки (усвідомлена затримка, зупинена вчасно або окрема топологія).
- Чи можливий PITR? Підтвердіть, що архівація логів безперервна, ретенція покриває вікно, і у вас є базовий бекап.
- Чи можна зробити таргетовану репару? Іноді можна витягти рядки з snapshot/репліки без повного відновлення.
Третє: знайдіть вузьке місце (вбивця RTO)
- Пропускна здатність відновлення: завантаження базового бекапу + розпакування + продуктивність файлової системи.
- Швидкість програвання: швидкість застосування WAL/binlog; перевірте, чи вузьке місце в I/O або CPU.
- Валідація та cutover: чи можете ви швидко перевірити коректність і безпечно переключити трафік?
Типові помилки: симптоми → причина → виправлення
«Ми зробили failover, але шкідливе видалення все ще тут.»
Симптом: ви піднімаєте репліку і пропажі рядків залишаються.
Причина: реплікація поширила видалення; failover лише змінив копію, на яку ви дивитесь.
Виправлення: використайте PITR або збережену репліку/снапшот до помилки; впровадьте PITR з протестованими відновленнями; розгляньте затриману репліку як доповнення.
«PITR впав наполовину через відсутні WAL/binlog файли.»
Симптом: відновлення зупиняється з повідомленням «requested WAL segment has already been removed» (PostgreSQL) або бінлог не знайдено (MariaDB).
Причина: занадто коротка ретенція, збій конвеєра архівації або архів зберігався на тому ж диску, що вмер.
Виправлення: збільште ретенцію, алертуйте про помилки архіватора, зберігайте архіви на окремому надійному сховищі, і регулярно робіть drills відновлення, що доводять безперервність.
«Ми відновили, але дані інконсистентні / порушуються обмеження.»
Симптом: відновлена БД не стартує чисто, або застосунок падає через відсутні зв’язки/обмеження.
Причина: змішування логічних дампів з фізичним програванням логів неправильно; або відновлення схеми з одного часу, а даних — з іншого.
Виправлення: тримайте PITR цілісним: базовий бекап має відповідати потоку логів. Для часткових відновлень експортуйте/імпортуйте послідовно (схема + дані) і валідуйте.
«Репліка мала бути затриманою, але не була.»
Симптом: затримана репліка містить помилку.
Причина: затримка налаштована неправильно, репліка перезапущена і наздогнала, або моніторинг не сигналізував про зупинку застосування.
Виправлення: моніторте ефективну затримку, а не лише конфігурацію. Протестуйте workflow: чи справді ви можете зупинити apply і витягти дані під час drill?
«Диск архіву мовчки заповнився; тепер у нас немає PITR.»
Симптом: архівація починає падати; пізніше виявляють прогалини.
Причина: відсутність ретенції, алертингу або архів лише локально без планування ємності.
Виправлення: налаштуйте політики ретенції, моніторинг вільного місця та статистики архіватора, і ставте сховище WAL/binlog як критичне з SLO.
«Відновлення занадто повільне; RTO — фантазія.»
Симптом: базове відновлення займає години; програвання WAL триває ще довше; бізнес панікує.
Причина: бекапи на повільних носіях, шлях відновлення не тестували, вузькі місця через шифрування/сжаття або недостатній IOPS.
Виправлення: вимірюйте пропускну здатність відновлення щоквартально, тримайте warm standby базові бекапи, налаштовуйте сховище і розглядайте інкрементальні/базові стратегії бекапу.
Чек-листи / покроковий план
Побудуйте позицію відновлення, що переживе людей (план на 90 днів)
- Визначте RPO/RTO для кожної бази: що ви можете втратити і скільки часу можете бути недоступні.
- Впровадьте реплікацію для доступності: принаймні один стендаб/репліка в окремій зоні відмови.
- Впровадьте PITR:
- MariaDB: фізичний базовий бекап + відправка бінлогів і ретенція.
- PostgreSQL: базовий бекап + архівація WAL з моніторингом
pg_stat_archiver.
- Окреме сховище: логі архівуйте поза основними дисками і бажано поза зоною ураження.
- Ретенція за часом виявлення: зберігайте логи достатньо довго, щоб встигнути помітити помилки. Багатьом командам потрібні тижні, а не дні.
- Проводьте drills відновлення щомісяця: повне PITR відновлення в тестовий кластер з валідаційними запитами і записаною хронікою.
- Документуйте cutover: DNS/рядки підключення, режим лише для читання, координація з застосунком і rollback від відкату.
- Додайте запобіжники: обмежте доступ до консолі проду, вимагайте рев’ю міграцій, захистіть небезпечні таблиці (права) і використовуйте безпечні інструменти.
Кроки відповіді на інцидент: випадкове видалення/DROP
- Зупиніть записувачів: заморозьте джоб/деплой; не дозволяйте подальших змін.
- Позначте часову шкалу: точний час виявлення; визначте приблизний час початку за логами застосунку/аудиту.
- Виберіть стратегію відновлення:
- Малий радіус ураження: витягніть відсутні рядки з PITR відновлення або з зупиненої репліки і заново вставте.
- Великий радіус ураження: повне PITR відновлення і контрольований cutover.
- Паралельно виконуйте завдання: одна людина готує середовище відновлення; інша валідує обсяг; третя займається комунікаціями.
- Валідуйте серйозно: кількості рядків, бізнес-інваріанти, smoke-тести застосунку.
- Cutover: переключайте трафік, моніторьте помилки, тримайте старий primary ізольованим, поки не будете впевнені.
Три міні-історії з практики
Міні-історія 1: інцидент через неправильне припущення
Середня B2B платформа працювала на MariaDB з primary і двома репліками. В документації з operations було «HA: так» і всі розслабилися.
Розробник запустив cleanup-скрипт, який мав видалити тестових тенантів. Він видалив реальних тенантів. Швидко.
On-call зробив те, що підказував runbook: підняв репліку. Сайт повернувся за хвилини. Всі аплодували, поки support не повідомив про ті самі пропажі.
Звісно. Видалення реплікувалося. Вони просто переключилися на іншу копію того самого місця злочину.
У них були нічні логічні дампи, але без бінлогів, що зберігаються довше кількох годин, і без протестованого шляху «відновити на 10:03».
Команда намагалася відновити з подій застосунку і часткових експортів. Деякі тенанти вдалося, але не всі, і це зайняло дні.
Тривале виправлення не було «краще навчання розробників». Воно було в увімкненні правильного шипінгу бінлогів поза хостом, збільшенні ретенції і
щомісячних PITR drills, де хтось умисно видаляє тенанта в sandbox і відновлює його.
Міні-історія 2: оптимізація, що обернулася проти
Інша компанія запускала PostgreSQL з архівацією WAL. Хтось помітив швидке зростання обсягу архіву WAL і «оптимізував» ретенцію, залишивши лише кілька днів.
Вони також агресивно стискали архіви і відправляли на повільніше сховище. На дашборді витрат це виглядало чудово.
За три тижні довгоіснуючий баг у застосунку почав пошкоджувати підмножину рядків. Це було непомітно — достатньо, щоб пройти поверхневу перевірку.
Коли проблему виявили, команді треба було відновитися до моменту перед релізом багу.
Це було далеко поза вікном ретенції WAL.
Вони намагалися зшити відновлення зі щотижневих базових бекапів і часткових логічних експортів, але дані не збігалися з поточними міграціями схеми.
Ніч вони провели в суперечці про те, які дані вважати правдою: база чи кеші застосунку. Ніякого задоволення о 3 ранку.
Виправлення було неефектним: зберігати WAL довше (враховуючи час виявлення, а не тривогу за диск), зберігати їх на сховищі, що витримає пропускну відновлення,
і моніторити реальний RTO відновлення як метрику. Витрати важливі, але оптимізують на основі даних, а не відчуттів.
Міні-історія 3: нудна, але правильна практика, що врятувала день
Команда фінансових послуг (регуляторний тиск робить дива для звичок) працювала на PostgreSQL з потоковою реплікацією
і архівацією WAL у окреме сховище. Щомісяця плановий джоб відновлював тижневий базовий бекап в ізольоване середовище і запускав набір «інваріантів»:
баланси підсумовуються правильно, зовнішні ключі співпадають, і вибірка клієнтських шляхів може бути відтворена.
Одного дня інженер застосував міграцію, яка видалила індекс, а потім запустив оновлення, що випадково зачепило значно більше рядків.
Сайт залишився в строю — реплікація була в порядку — але дані для користувача були неправильні. Вони негайно заморозили записи.
Через те, що вони робили drills відновлення, вони вже знали час відновлення, команди і який шлях сховища найшвидший.
Вони відновили на п’ять хвилин перед міграцією, перевірили інваріанти, потім використали контрольований реплей подій застосунку для вузького вікна. Cutover був напруженим, але чистим.
Ніхто не отримав овацій. Ось у чому суть. Винагорода за правильність — тихий інцидент-чат і всі йдуть спати.
Факти та історичний контекст, які варто знати
- WAL у PostgreSQL має довгу історію: базова ідея write-ahead logging старша за багато «сучасних» продуктів бекапу.
- MariaDB відокремилася від MySQL у 2009 після того, як Oracle купив Sun; ця історія вплинула на довіру підприємств і вибір інструментів екосистеми.
- Формати бінлогів MySQL/MariaDB еволюціонували: спочатку було statement-based логування; row-based стало безпечнішим вибором для детермінованого відновлення.
- Таймлайни PostgreSQL мають значення: кожне підвищення стендаба створює новий таймлайн; забування файлів історії таймлайнів може зламати відновлення заплутаним чином.
- Реплікація ніколи не проектувалася як undo: обидві системи ставлять пріоритет на консистентність копій, а не на зворотність транзакцій.
- «Синхронна реплікація» не означає «безпечно від людей»: вона зменшує втрату даних при креші/failover, але не логічну корупцію.
- Логічна реплікація селективна, але не перемотувальна: вона чудово підходить для міграцій і часткової реплікації, але не для перемотування помилок.
- Багато outage — це провал виявлення: людська помилка часто відбувається за хвилини або години до виявлення; ретенція має відповідати реальності.
FAQ
1) Якщо в мене є реплікація, чи потрібен мені ще і PITR?
Так. Реплікація вирішує апаратні відмови і деякі сценарії failover. PITR вирішує шкідливі записи, помилки схеми і компроміс, що реплікується.
Якщо ви можете вибрати тільки одне для критичної бази, обирайте PITR і прийміть деякий ризик даунтайму.
2) Чи може затримана репліка замінити PITR?
Ні. Це корисне доповнення: швидкий спосіб витягти відсутні рядки або зберегти копію до помилки. Але вона може мовчки провалитись,
і вона не покриє повільну корупцію за межами вікна затримки.
3) Яка найбільша операційна різниця між відновленням у MariaDB і PostgreSQL?
PostgreSQL має дуже явний робочий процес PITR навколо базових бекапів і архівації WAL з потужною інструментальною видимістю (наприклад, pg_stat_archiver).
PITR у MariaDB потужний, але більше «збери свій власний шлях»: фізичні бекапи плюс управління бінлогами і ретельне відтворення.
4) Чи варто використовувати логічні дампи для PITR?
Логічні дампи чудові для портативності і відновлення на рівні таблиць, але вони самі по собі не є PITR. PITR потребує узгодженого базового бекапу + потоку логів.
Використовуйте логічні дампи як доповнення, а не основу, якщо тільки ваш розмір даних і RTO це дозволяють.
5) Як довго тримати WAL/binlog?
Орієнтуйтеся на час виявлення, а не на відчуття комфорту. Якщо ваша організація зазвичай виявляє проблеми через дні, зберігайте принаймні тижні логів.
Також переконайтесь, що ви можете дозволити собі час відновлення для цього вікна.
6) Як переконатися, що мій RTO реальний?
Виконуйте відновлення за графіком і вимірюйте: час на отримання бекапу, час на відновлення, час на програвання логів, час на валідацію,
час на cutover. Якщо ви не вимірюєте — ваш RTO це бажання.
7) Чи можна зробити PITR «до самої транзакції» надійно?
Зазвичай так, але точність варіюється. PostgreSQL може таргетувати за часовими мітками і також за LSN. MariaDB може відтворювати до datetime
і часто по межах GTID залежно від інструментів і формату логів. Чим більше логів ви зберігаєте і чим більше практики — тим точніше.
8) А як щодо відновлення лише однієї таблиці або одного тенанта?
Поширений патерн: відновити PITR у ізольоване середовище, витягти потрібні рядки (або таблицю/схему), потім акуратно злити назад у прод.
Це повільніше за повний cutover, але уникає глобального відкату для локалізованої помилки.
9) Чи впливає шифрування/сжаття архівів на відновлення?
Так. Воно може розбити пропускну відновлення і перетворити «у нас є бекапи» в «у нас є повільні бекапи». Якщо ви стискаєте або шифруєте, тестуйте швидкість відновлення під навантаженням.
Не оптимізуйте витрати на шкоду можливості відновлення.
Висновок: наступні кроки на цей тиждень
Якщо ви візьмете один операційний висновок з дебатів MariaDB vs PostgreSQL, нехай це буде таке: реплікація — це не кнопка перемотування.
Обидві бази даних можуть прекрасно відновитися після людської помилки — але лише якщо ви ставите PITR як продакшн-фічу, а не як чекбокс.
- Визначте реалістичне вікно відновлення (RPO/RTO) і вирівняйте ретенцію логів під ваш час виявлення.
- Доведіть, що PITR працює: зробіть одне повне відновлення drill end-to-end і заміряйте час.
- Інструментуйте конвеєр архівації: алертіть про помилки архівації WAL/binlog і про заповнення сховища архіву.
- Напишіть runbook «зупинити кровотечу»: заморожування записів часто різниця між 10-хвилинним фіксом і тижневим відновленням.
- Тримайте реплікацію для аптайму, але припиніть продавати це всередині компанії як захист від помилок. Це не так.
У день, коли це знадобиться, у вас не буде часу стати тією людиною, що знає, як це робити. Тренуйте м’яз зараз, поки ніхто не дивиться.