MySQL vs PostgreSQL: резервні копії та відновлення — хто повертає сервіс швидше

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

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

Це практичний, продакшн-рівневий погляд на резервні копії та відновлення MySQL і PostgreSQL через одну призму: час повернення сервісу. Не «час завершення завдання відновлення колись там», а час до сервісу, який може безпечно знову приймати трафік — з мінімальною втратою даних, яку ви реально готові гарантувати.

Реальне питання: що означає «повернутися онлайн»?

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

  • RTO (Recovery Time Objective): скільки часу ви можете бути недоступні.
  • RPO (Recovery Point Objective): скільки даних ви готові втратити.
  • Безпека: чи консистентна система після відновлення і чи не зіпсується вона під навантаженням.
  • Впевненість: чи шлях відновлення відпрацьований, а не теоретичний.

«Повернутися онлайн» також має рівні. Будьте конкретними.

  • Рівень 0: процес бази даних запущено.
  • Рівень 1: може безпечно приймати лише читання.
  • Рівень 2: може приймати повний трафік читання/запису, реплікація налаштована, моніторинг у зеленому статусі.
  • Рівень 3: наздогнав до цільового часу (PITR), додаток перепідключився, черга обробляється.

Моя тверда думка: якщо ваші runbook’и не визначають рівень відновлення, який ви виконуєте, у вас немає runbook’ів — у вас є відчуття.

Коротка відповідь: хто швидший, коли?

Для більшості продакшн-систем фізичні резервні копії перемагають

Якщо ваша мета — мінімізувати простій, фізичні резервні копії — дефолтний вибір як для MySQL, так і для PostgreSQL:

  • MySQL: Percona XtraBackup (для InnoDB) або еквіваленти від постачальників; плюс binlog-и для PITR.
  • PostgreSQL: pgBackRest (або подібні) плюс архівація WAL для PITR; або файлові/base бекапи з WAL.

Логічні дампи (mysqldump, pg_dump) мають своє місце — міграції, вибіркові відновлення, невеликі дані, переносимість — але вони рідко є найшвидшим способом повернутися після катастрофічного інциденту.

Де MySQL зазвичай повертає сервіс швидше

  • Коли можна підняти існуючу репліку: MySQL + semi-sync/async реплікація може зробити переключення «миттєвим», якщо в вас вже є здорова репліка і ви готові прийняти певний ризик по RPO. (Те ж саме можливо і для PostgreSQL, але інструменти та операційні практики відрізняються.)
  • Коли відновлення — це здебільшого «копіювати файли + crash recovery» і догон binlog невеликий. На практиці багато відновлень MySQL визначаються швидкістю копіювання І/O, а потім обмеженим кроком відновлення після аварії.
  • Коли ланцюг інструментів бекапу зрілий (особливо XtraBackup — він випробуваний у бою й оптимізований для великих InnoDB-ландшафтів).

Де PostgreSQL зазвичай повертає сервіс швидше

  • Коли потрібне точне, надійне PITR до часу або межі транзакції. WAL-орієнтоване відновлення PostgreSQL просте, зрозуміле, а екосистема (pgBackRest, WAL-G тощо) відмінна.
  • Коли можна відновити й почати обслуговувати читання, поки replay триває (залежно від архітектури та допусків). Поведінка відновлення PostgreSQL і видимість прогресу може бути передбачуванішою за інвестицій у належні налаштування й моніторинг.
  • Коли ви покладаєтеся на контрольні суми + дисципліну WAL і хочете додаткові захисні механізми проти прихованої корупції. Це не робить відновлення швидшим автоматично, але запобігає інцидентам «відновлення пройшло, а дані — сміття», що забирають години.

Неприємна правда

У реальних інцидентах двигун бази даних рідко є головною затримкою. Годинник вбивають:

  • копіювання терабайтів по перевантаженому каналу,
  • розпакування на процесорі з невідповідним профілем,
  • перепроігрування логів на повільному випадковому I/O,
  • очікування, поки DNS/пули підключень/деплояплікації помітять новий первинний сервер,
  • або виявлення, що ваші «резервні копії» втратили важливу частину (WAL/binlog-и, ключі, конфіги, користувачі, права).

Обирайте базу, яку вам подобається. Але для швидкого відновлення обирайте архітектуру відновлення: репліки, фізичні бекапи, збереження WAL/binlog та репетиції.

Цікаві факти й історичний контекст (коротко й корисно)

  1. InnoDB став дефолтом MySQL у MySQL 5.5, і з цим почалася довга епоха, коли фізичні інструменти бекапу були важливішими ніж логічні дампи для великих систем.
  2. PostgreSQL додав streaming replication у версії 9.0, що змістило багато організацій від мислення «відновитися з бекапу» до «промотувати стендбай».
  3. WAL (PostgreSQL) та binlog (MySQL) вирішують схожі задачі, але операційні очікування відрізняються: архівацію WAL зазвичай вважають першорядною вимогою для PITR; робота з binlog часто сприймається як «опціональна, поки не знадобиться».
  4. Percona XtraBackup популяризував гарячі фізичні бекапи для MySQL у великих інсталяціях, особливо там, де недопустимо зупиняти систему на час бекапу.
  5. Модель base backup + WAL replay у PostgreSQL стабільна вже роками, тому інструменти конкурують переважно зручністю, ефективністю зберігання та валідацією — менше щодо коректності.
  6. Підтримка контрольних сум у PostgreSQL (на рівні кластера) змусила більше команд виявляти корупцію раніше; швидко відновити правильно — цінніше, ніж швидко відновити неправильно.
  7. MySQL GTID змінив підходи до промоції й відновлення з binlog — менше «який файл/позиція», більше «який набір транзакцій». Це може пришвидшити рішення про переключення, якщо налаштовано правильно.
  8. Стиснення стало податком на час відновлення, коли бекапи перемістили в об’єктне сховище. Найдешевший рівень зберігання часто буває найповільнішим між вами і вашим RTO.

Типи резервних копій, які впливають на швидкість відновлення

Фізичні резервні копії (на рівні файлів): ваш основний інструмент для зниження простою

Фізичні копії копіюють фактичні файли бази даних (або дельти на рівні сторінок) і відновлюються шляхом повернення їх на місце. Швидкість відновлення зазвичай масштабується в залежності від:

  • послідовної пропускної здатності читання/запису (диск і мережа),
  • CPU для розпакування й перевірок контрольних сум,
  • швидкості застосування логів (WAL/binlog),
  • і кількості файлів, які треба створити (метадані файлової системи — справжній ворог).

В екосистемі MySQL фізичні бекапи зазвичай означають копії datadir, створені XtraBackup, плюс обробку redo/undo. У PostgreSQL це base backup плюс сегменти WAL, щоб дістатися до цільової точки.

Логічні резервні копії (SQL-дампи): повільно, але портативно

Логічні відновлення повільніші, бо дані перебудовуються через SQL. Це означає:

  • парсинг SQL,
  • відбудова індексів і обмежень,
  • запис багатьох дрібних транзакцій, якщо не налаштовано ретельно,
  • і потенційне інтенсивне генерування WAL/binlog під час імпорту.

Вони все ще корисні для:

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

Відновлення на основі реплікації: найшвидше відновлення — це відсутність відновлення

Якщо ви можете промотувати стендбай/репліку, ви часто обійдете будь-яке відновлення з бекапу. Але за це доведеться платити:

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

Жарт №1: Резервні копії як страхування — дорогі, нудні, і день, коли вони знадобляться, завжди жахливий, щоб вивчати умови полісу.

Шлях відновлення MySQL: що насправді займає час

Що означає «відновлення» під час інциденту MySQL

Для MySQL (переважно InnoDB, як у більшості реальних систем) відновлення зазвичай розбивається так:

  1. Отримати консистентну фізичну копію datadir (з репозиторію бекапів) на цільовий сервер.
  2. Підготувати/застосувати redo, якщо ваш інструмент бекапу цього вимагає (XtraBackup «prepare»).
  3. Запустити mysqld і дочекатися завершення crash recovery.
  4. Застосувати binlog-и, якщо потрібно PITR за межі моменту знімка бекапу.
  5. Відновити топологію реплікації (GTID або file/pos), перевірити й переключити трафік.

Найбільші поглиначі часу (MySQL)

  • Розпакування + запис файлів: якщо бекап стиснутий, а хост відновлення має менше ядер або слабкий CPU, ви отримаєте несподівану уповільненість.
  • Багато дрібних файлів: per-table tablespaces (ibd-файли) створюють метадані-шторм під час відновлення. Налаштування XFS/EXT4 допомагають, але це фізика і системні виклики.
  • Застосування redo логу: важкі операції запису генерують більше redo; «prepare» може зайняти час, як і crash recovery після старту.
  • Догон binlog: якщо ви застосовуєте години binlog-ів, ви фактично повторюєте свій трафік у важчий спосіб. Вузьке місце може бути однопоточне застосування, затримка диска або просто обсяг.

Що MySQL робить добре для швидкості відновлення

MySQL може бути жорстко швидким у запуску, якщо ви відновлюєте підготовлений бекап на швидкому сховищі і погоджуєтесь, що crash recovery та розігрів buffer pool відбуватимуться в роботі. З правильною топологією ви також можете зовсім уникнути відновлення, піднявши репліку.

Гострі краї MySQL, які відчуваєш під час відновлень

  • Припущення щодо збереження binlog: «ми зберігаємо binlog-и 24 години» — план, поки вам не знадобиться 30 годин.
  • Неправильні налаштування GTID: failover на основі GTID швидкий, якщо налаштовано правильно, і заплутаний, якщо частково.
  • Розходження налаштувань InnoDB: відновлення на сервер з іншим innodb_log_file_size, шифруванням або очікуваннями розміру сторінки може перетворити чисте відновлення в довгу сесію дебагу.

Шлях відновлення PostgreSQL: що насправді займає час

Що означає «відновлення» під час інциденту PostgreSQL

Фізичне відновлення PostgreSQL концептуально чисте:

  1. Відновити base backup (файли кластера).
  2. Надати сегменти WAL, щоб просунутися вперед (з архіву або через streaming).
  3. Переіграти WAL до цілі (останньої, по часу, LSN або іменованої точки відновлення).
  4. Промотувати і перепідключити клієнтів/репліки.

Найбільші поглиначі часу (PostgreSQL)

  • Відновлення base backup: та сама фізика — мережа, пропускна здатність диска, розпакування.
  • WAL replay: системи з інтенсивними записами генерують багато WAL; replay може обмежуватися випадковим I/O і поведінкою fsync.
  • Поведение контрольних точок: конфігурація й робоче навантаження впливають на те, наскільки болісним буде crash recovery і replay.
  • Великі об’єкти і роздути індекси: відновлення може пройти успішно, але «повернутися онлайн» може вимагати vacuum або reindex після відновлення.

Що PostgreSQL робить добре для швидкості відновлення

PITR — тривіальна функція. Ви можете обрати «відновити до моменту перед деплоєм» з меншими складнощами, а інструменти зазвичай агресивно валідують безперервність WAL. Це скорочує час, втрачений на питання «ми відновили… чому ж бракує транзакцій?»

Гострі краї PostgreSQL, які відчуваєш під час відновлень

  • Прогалини в архіві WAL: один відсутній сегмент — і ви не робите PITR; ви займаєтеся археологією.
  • Помилки в restore_command: одна тонка помилка з лапками/шляхом може безкінечно зупиняти відновлення.
  • Плутанина з timeline: повторні failover-и створюють таймлайни; відновлення по неправильному таймлайну — класична помилка «завантажилось, але неправильно».

Одна перефразована ідея від Werner Vogels (CTO Amazon): Все ламається, весь час; стійкі системи припускають це і швидко відновлюються (перефразована думка).

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

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

Завдання 1: Виміряти пропускну здатність диска на цільовому хості відновлення (Linux)

cr0x@server:~$ sudo fio --name=restore-write --filename=/var/lib/db-restore.test --size=8G --bs=1M --rw=write --iodepth=16 --direct=1
restore-write: (g=0): rw=write, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=psync, iodepth=16
...
  write: IOPS=720, BW=720MiB/s (755MB/s)(8192MiB/11374msec)

Значення: Ваша послідовна пропускна здатність на запис ≈720MiB/s. Це верхня межа для «відновлення base backup», якщо ви обмежені I/O.

Рішення: Якщо BW <200MiB/s для багатотерабайтних відновлень, не обманюйте себе щодо RTO у хвилинах. Перенесіть відновлення на швидші диски, відновлюйте з локальних знімків або піднімайте репліку.

Завдання 2: Перевірити вільне місце в файловій системі й тиск інодів

cr0x@server:~$ df -h /var/lib/mysql
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  3.5T  1.2T  2.3T  35% /var/lib/mysql
cr0x@server:~$ df -i /var/lib/mysql
Filesystem       Inodes  IUsed    IFree IUse% Mounted on
/dev/nvme0n1p2  244M    18M     226M    8% /var/lib/mysql

Значення: Є вільне місце й достатньо інодів. У MySQL з per-table файлами вичерпання інодів — реальний вбивця відновлень.

Рішення: Якщо використання інодів високе (>80%), відновлюйте на файлову систему з більшою кількістю інодів або консолідуйте tablespace-и (довгостроково). Не «надійтеся, що вміститься».

Завдання 3: Перевірити метадані цілісності бекапу (pgBackRest)

cr0x@server:~$ pgbackrest --stanza=prod check
stanza: prod
status: ok

Значення: Репозиторій і stanza доступні й достатньо консистентні, щоб pgBackRest був впевнений.

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

Завдання 4: Перелік доступних бекапів PostgreSQL і вибір потрібного

cr0x@server:~$ pgbackrest --stanza=prod info
stanza: prod
    status: ok
    cipher: none
    db (current)
        wal archive min/max (15): 0000001200000A1B000000C0/0000001200000A1C0000002F
        full backup: 2025-12-28-010001F
            timestamp start/stop: 2025-12-28 01:00:01 / 2025-12-28 01:12:33
            database size: 2.1TB, backup size: 820GB
            repo1: backup set size: 820GB, backup size: 820GB

Значення: У вас є нещодавній повний бекап, і покриття WAL існує між min/max.

Рішення: Виберіть найновіший бекап, який має повні WAL до вашого цільового часу відновлення. Якщо WAL max позаду часу інциденту, ваше RPO погіршилося — скажіть про це відразу.

Завдання 5: Відновити PostgreSQL до цільового часу (PITR)

cr0x@server:~$ sudo pgbackrest --stanza=prod --delta --type=time --target="2025-12-28 14:32:00" restore
INFO: restore command begin 2.53: --delta --stanza=prod --target=2025-12-28 14:32:00 --type=time
INFO: restore size = 2.1TB, file total = 2143
INFO: restore command end: completed successfully

Значення: Файли відновлені; база даних буде переігрувати WAL під час запуску до цільового часу.

Рішення: Якщо відновлення «успішне», але старт зависає, вузьке місце, ймовірно, у отриманні WAL або в I/O при їх replay. Перейдіть до playbook діагностики замість повторного прогону відновлень.

Завдання 6: Слідкувати за прогресом відновлення PostgreSQL

cr0x@server:~$ sudo -u postgres psql -c "select pg_is_in_recovery(), pg_last_wal_replay_lsn(), pg_last_xact_replay_timestamp();"
 pg_is_in_recovery | pg_last_wal_replay_lsn | pg_last_xact_replay_timestamp
-------------------+------------------------+------------------------------
 t                 | 0/A1C3F9B0             | 2025-12-28 14:29:17+00
(1 row)

Значення: Ще в режимі відновлення; ви можете кількісно виміряти, наскільки відстаєте за допомогою зміни timestamp/LSN.

Рішення: Якщо timestamp не рухається, підозрюйте завислий restore_command, відсутній WAL або насичення I/O. Якщо рухається повільно — оптимізуйте середовище replay (швидші диски, усуньте вузькі місця), а не чіпайте SQL.

Завдання 7: Перевірити відсутні сегменти WAL (логи PostgreSQL)

cr0x@server:~$ sudo tail -n 20 /var/log/postgresql/postgresql-16-main.log
2025-12-28 14:35:02 UTC [21741] LOG:  entering standby mode
2025-12-28 14:35:02 UTC [21741] LOG:  restored log file "0000001200000A1B000000F1" from archive
2025-12-28 14:35:05 UTC [21741] LOG:  could not restore file "0000001200000A1B000000F2" from archive: No such file or directory
2025-12-28 14:35:05 UTC [21741] LOG:  waiting for WAL to become available at 0/A1BF0000

Значення: Прогалина в архіві. Відновлення заблоковано.

Рішення: Знайти відсутній WAL (другий архів, проблема з життєвим циклом об’єктного сховища) або погодитись на гірше RPO і відновитися до ранішої точки. Не чекайте «на всяк випадок».

Завдання 8: Перевірити доступність MySQL binlog-ів для PITR

cr0x@server:~$ mysql -uroot -p -e "SHOW BINARY LOGS;"
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| binlog.000812    | 1073741824|
| binlog.000813    | 1073741824|
| binlog.000814    |  512345678|
+------------------+-----------+

Значення: Binlog-и існують на цьому сервері (або там, де ви перевіряєте). Список файлів вказує на схему збереження.

Рішення: Підтвердіть, що у вас є binlog-и, які покривають вікно інциденту. Якщо потрібен binlog.000811, а його нема — ви не можете зробити PITR через цю межу.

Завдання 9: Визначити стан GTID MySQL перед перенаправленням реплікації

cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'gtid_mode'; SHOW MASTER STATUS\G"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_mode     | ON    |
+---------------+-------+
*************************** 1. row ***************************
             File: binlog.000814
         Position: 83422119
     Executed_Gtid_Set: 4a9b1c9c-8b60-11ee-9f4e-0242ac120002:1-987654321

Значення: GTID увімкнено; у вас є конкретний виконаний набір, який можна порівняти між серверами.

Рішення: Використовуйте авто-позиціювання на основі GTID, якщо можливо. Якщо GTID вимкнено в частинах кластера, failover стає повільнішим і ризикованішим — заплануйте виправлення поза режимом пожежогасіння.

Завдання 10: Підготувати відновлення MySQL XtraBackup (apply redo)

cr0x@server:~$ sudo xtrabackup --prepare --target-dir=/backups/mysql/full-2025-12-28
xtrabackup: starting shutdown with innodb_fast_shutdown = 1
xtrabackup: completed OK!

Значення: Бекап консистентний і готовий до копіювання назад. Якщо цей крок довгий, застосування redo важке.

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

Завдання 11: Скопіювати datadir MySQL назад і виставити права

cr0x@server:~$ sudo systemctl stop mysql
cr0x@server:~$ sudo rsync -aHAX --info=progress2 /backups/mysql/full-2025-12-28/ /var/lib/mysql/
      1.23T  42%  610.23MB/s    0:35:12 (xfr#214, to-chk=1290/2143)
cr0x@server:~$ sudo chown -R mysql:mysql /var/lib/mysql

Значення: Відновлення обмежено I/O; швидкість rsync дає ETA. Правильні власники дозволяють mysqld читати файли.

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

Завдання 12: Запустити MySQL і підтвердити поведінку crash recovery

cr0x@server:~$ sudo systemctl start mysql
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/error.log
2025-12-28T14:40:01.120345Z 0 [Note] InnoDB: Starting crash recovery.
2025-12-28T14:40:12.551231Z 0 [Note] InnoDB: Crash recovery finished.
2025-12-28T14:40:13.883412Z 0 [Note] mysqld: ready for connections.

Значення: Crash recovery швидко завершився; сервіс приймає з’єднання.

Рішення: Якщо crash recovery довго триває або циклічно повторюється, ваші логи/redo можуть не відповідати (погане відновлення, неправильні innodb-настройки, часткова копія). Зупиніться і валідуйте кроки бекапу/відновлення замість безцільних перезапусків.

Завдання 13: Застосувати MySQL binlog-и, щоб дістатися цільової точки

cr0x@server:~$ mysqlbinlog --start-datetime="2025-12-28 14:00:00" --stop-datetime="2025-12-28 14:32:00" /var/lib/mysql/binlog.000812 /var/lib/mysql/binlog.000813 | mysql -uroot -p
cr0x@server:~$ echo $?
0

Значення: Застосування binlog пройшло успішно (код виходу 0). Це ваш крок PITR.

Рішення: Якщо воно помиляється, швидко вирішіть: виправити і прогнати знову (якщо можете), або погодитись на іншу точку відновлення. Не застосовуйте часткові binlog-и і не називайте це «добре», якщо вам не подобається лікування проблем з цілісністю даних.

Завдання 14: Перевірити, що PostgreSQL може приймати записи і знаходиться на очікуваному таймлайні

cr0x@server:~$ sudo -u postgres psql -c "select pg_is_in_recovery(); select timeline_id from pg_control_checkpoint();"
 pg_is_in_recovery
-------------------
 f
(1 row)

 timeline_id
------------
         19
(1 row)

Значення: Відновлення завершене, і ви можете відстежувати, на якому таймлайні перебуваєте.

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

Завдання 15: Знайти відставання реплікації / затримку застосування (PostgreSQL standby)

cr0x@server:~$ sudo -u postgres psql -c "select client_addr, state, sync_state, write_lag, flush_lag, replay_lag from pg_stat_replication;"
 client_addr |  state  | sync_state | write_lag | flush_lag | replay_lag
-------------+---------+------------+-----------+-----------+-----------
 10.0.2.41    | streaming | async    | 00:00:00  | 00:00:01  | 00:00:03
(1 row)

Значення: Стенбай майже наздогнав; replay lag невеликий.

Рішення: Якщо replay lag — хвилини/години, промоція цього стенда може зруйнувати ваше RPO. Виберіть інший стендбай або відновіться з бекапу з WAL до безпечнішої точки.

Завдання 16: Підтвердити стан реплікації MySQL після failover

cr0x@server:~$ mysql -uroot -p -e "SHOW SLAVE STATUS\G" | egrep "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Last_Error"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_Error:

Значення: Репліка здорова і наздогнала мастера.

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

Playbook швидкої діагностики: знайти вузьке місце за хвилини

Відновлення зазвичай провалюються банальними способами. Мета — визначити, в якому саме банальному способі ви опинилися сьогодні.

По-перше: чи заблоковані ви відсутніми логами (WAL/binlog)?

  • PostgreSQL: перевірте логи на «could not restore file … waiting for WAL». Якщо воно є — ви не повільні, ви застрягли.
  • MySQL: підтвердіть покриття binlog-ів для цільового інтервалу. Якщо потрібні binlog-и відсутні, PITR неможливий через цю прогалину.

По-друге: вузьке місце — пропускна здатність (мережа/диск) чи replay (CPU/випадковий I/O)?

  • Якщо копіювання base backup повільне — виміряйте мережу і диск. Слідкуйте за iostat, fio, прогресом rsync.
  • Якщо копія швидка, але старт/відновлення повільне — це replay/checkpoint/crash recovery. Виміряйте прогрес WAL replay (PostgreSQL) або логи crash recovery і затримки I/O (MySQL).

По-третє: база «запустилась», але сервіс ні?

  • Пули підключень все ще вказують на старі хости.
  • DNS TTL і кеши застаріли.
  • Секрети не розгорнуті в середовищі відновлення.
  • Фаєрвол/групи безпеки блокують доступ репліки/додатка.

По-четверте: чи стала продуктивність новою відмовою?

  • Холодний кеш: buffer pool і shared buffers пусті.
  • Autovacuum, чекпойнти або purge наздоганяють систему.
  • Відсутні індекси, бо хтось відновив з часткового логічного дампу.

Жарт №2: Найшвидше відновлення — те, яке ви тестували минулого тижня — дивно, бази даних люблять, коли їм приділяють увагу.

Три корпоративні міні-історії з практики

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

Компанія мала MySQL primary і дві репліки. Також був «резервний копіювання binlog-ів», що означало cron-скрипт, який копіював binlog-и в об’єктне сховище. Усі спали спокійно.

Потім деплой пішов не так. Пакетна задача виконала запит проти продакшну з неправильною WHERE. Дані були консистентні, реплікація поширила помилку, і коли помітили, вона вже була на кожному вузлі.

Команда вирішила робити PITR: відновити нічний фізичний бекап, потім застосувати binlog-и до «безпосередньо перед деплоєм». Легко. Але binlog-и були агресивно ротаційні й видалялися на primary, а cron-скрипт копіював лише «поточні» binlog-и. Старі були втрачені. Не «складно знайти». Зникли.

Відновили нічний бекап і прийняли RPO, якого ніколи не записували. Більше не в даних; болючим було те, що втрату можна було уникнути. Припущення було: «якщо ми бекапимо binlog-и, ми можемо робити PITR». Реальність: «якщо ми зберігаємо безперервний ланцюг binlog-ів, ми можемо робити PITR».

Виправлення було нудне: явне збереження binlog за RPO, плюс перевірка безперервності з оповіщенням про прогалини. Наступний інцидент був менш драматичним, бо став менше несподіванкою.

Міні-історія 2: Оптимізація, яка зіграла проти вас

Команда PostgreSQL хотіла швидші бекапи. Вони збільшили стиснення і перемістили бекапи на дешевше сховище. Вікна бекапів скоротилися, щомісячні рахунки впали, і хтось отримав оплески на зустрічі планування.

Місяці потому відмова сховища вивела з ладу primary і standby в одному стояку. Вони відновили на новий хост і виявили правду: час відновлення — це не час бекапу. Відновлення означало витягти терабайти зі «холодного» сховища, розпаковувати сильно стиснуті архіви на меншому CPU інстансі і потім переігрувати гору WAL.

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

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

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Платформа платежів працювала на PostgreSQL з pgBackRest. Щотижня інженер робив drill відновлення в ізольованому середовищі. Не повний пожежний дрилл з керівництвом — просто тихе завдання: відновити нічний бекап, переіграти WAL до вибраного часу, виконати набір валідаційних запитів і видалити середовище.

Це здавалося майже смішним. Відновлення завжди працювало. Запити завжди відповідали очікуванням. Тікети були повторювані.

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

Пізніше стався реальний збій: людина видалила приєднання тому в неправильній консолі. Playbook відновлення вже був, профіль хоста відновлення відомий, і команда не мусила повторно відкривати систему о 2:00 ночі. Відновлення було швидке саме тому, що воно було нудним.

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

1) «Відновлення успішне», але PostgreSQL не дійде до цільового часу

Симптоми: pgBackRest restore завершується; Postgres стартує; логи показують очікування WAL; pg_last_xact_replay_timestamp перестає рухатись.

Причина: Відсутній сегмент WAL через прогалину в архіві, видалення за політикою життєвого циклу або невірно налаштований archive_command/restore_command.

Виправлення: Знайти WAL у вторинному архіві; виправити restore_command; налаштувати збереження відповідно до RPO; додати постійні перевірки прогалин WAL у моніторинг.

2) MySQL запущений, але crash recovery триває вічно

Симптоми: mysqld стартує, але залишається недоступним; в error log показано тривале crash recovery; спайки диска ростуть.

Причина: Відновили на повільне сховище; redo-логи важкі; бекап не було підготовлено; або розходження налаштувань InnoDB викликають додаткову роботу.

Виправлення: Перевірити крок XtraBackup prepare; відновити на швидші диски; узгодити innodb_log_file_size та релевантні налаштування з середовищем бекапу; уникати повторних перезапусків, які заново тригерять відновлення.

3) Логічне відновлення «йде», але ETA — дні

Симптоми: pg_restore/mysqldump імпорт повзе; CPU зайнятий парсингом; WAL/binlog-и ростуть швидко; реплікація відстає.

Причина: Неправильний інструмент для заявленого RTO; відновлення працює однопоточно; індекси/обмеження відбудовуються в найгіршому порядку; недостатнє тюнінгування.

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

4) Репліки є, але failover все одно займає вічність

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

Причина: Немає автоматизованого cutover; занадто великий TTL DNS; пули підключень не оновлюються; сертифікати/секрети прив’язані до імен хостів; або реплікація відставала більше, ніж дозволяє RPO.

Виправлення: Тренуйте failover; зменшіть TTL розумно; використовуйте стабільні VIP/проксі; забезпечте автоматичне перепідключення додатків; моніторте lag реплікації з оповіщеннями, прив’язаними до RPO.

5) Відновлення швидке, але продуктивність така погана, що фактично недоступно

Симптоми: База відповідає, але p95 латентність жахлива; CPU і I/O стрибки; кеші холодні; autovacuum/purge штурмують систему.

Причина: Холодний кеш плюс фонове обслуговування плюс інтенсивне навантаження одразу після відновлення.

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

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

Вирішіть стратегію відновлення для кожного типу інциденту

  • Втрата вузла/заліза: віддавайте перевагу failover-у на репліку/стенбай (найшвидше), потім відновлюйте з бекапів.
  • Логічна корупція (помилковий деплой, випадкові видалення): віддавайте перевагу PITR з фізичного бекапу + логів.
  • Підозра на приховану корупцію: відновіть в ізольоване середовище, валідуйте, а потім переключайтесь; не промотуйте потенційно корумповану репліку.

Чек-лист перед інцидентом (робіть спокійно)

  1. Визначте RTO/RPO для кожного сервісу (запишіть, додайте в runbook).
  2. Впровадьте фізичні бекапи (XtraBackup / pgBackRest) з перевіркою.
  3. Забезпечте безперервне збереження логів: binlog/WAL покривають принаймні RPO + буфер.
  4. Тримайте «швидкий рівень відновлення» для найсвіжіших бекапів і логів на сховищі, яке може забезпечити потрібну пропускну здатність.
  5. Підготуйте хости відновлення з достатнім CPU для розпакування й достатнім IOPS для replay.
  6. Автоматизуйте або як мінімум заскриптуйте: відновлення, валідаційні запити й кроки переключення.
  7. Проводьте drill’и відновлення регулярно; фіксуйте реальні часи й вузькі місця.

Покроковий план під час інциденту (з таймбоксом)

  1. Таймбокс триажу (5–10 хвилин): вирішіть, чи робите failover або відновлення. Не займайтесь обома одночасно, якщо немає достатньо людей і дисципліни.
  2. Зафіксуйте ціль: «максимально пізньо» чи «час перед інцидентом». Сразу повідомте про наслідки для RPO.
  3. Перевірте безперервність логів: покриття WAL/binlog. Якщо чогось бракує — скоригуйте ціль і повідомте.
  4. Розпочніть відновлення паралельно з підготовкою додатка: готуйте DNS/LB/конфіги, поки тека рухається.
  5. Слідкуйте за метриками прогресу: пропускна здатність копіювання, timestamp/LSN replay, затримки диска.
  6. Виводьте в мережу по рівнях: спочатку лише читання або обмежений трафік; повний навантаження — коли стабільно.
  7. Відновіть резервність: додайте репліки/стендаби перед тим, як оголосити перемогу; один відновлений primary — це випробування на стійкість.

Поширені запитання

1) Хто відновлюється швидше: MySQL чи PostgreSQL?

За фізичних бекапів і гарного сховища обидві системи можуть бути «достатньо швидкими» для багатьох систем. Переможець зазвичай той, хто має безперервні логи, протестований runbook і шлях відновлення, який уникає повільного сховища. Якщо змушувати логічні відновлення — обидві будуть повільними; MySQL частіше страждає на відбудову схем/індексів, PostgreSQL — на генерацію WAL і час побудови індексів. Не обирайте рушій за швидкістю дампу.

2) Чи промоція репліки завжди швидша за відновлення з бекапу?

Майже завжди так. Але це може порушити RPO, якщо є відставання реплікації, і не допоможе при логічній корупції, що реплікована. Також: якщо репліки поділяють ту саму корупцію (помилки сховища/прошивки трапляються), ви швидко промотите «сміття».

3) Яка найбільш поширена помилка PITR?

Відсутні логи. Прогалини в архівах WAL у PostgreSQL; прогалини в збереженні/збиранні binlog-ів у MySQL. Інструменти не відновлять те, чого ви не зберегли.

4) Чи логічні бекапи марні?

Ні. Вони чудові для переносимості, часткових відновлень, аудитів і міграцій. Просто вони рідко є правильним інструментом для «повернутися за 15 хвилин». Тримайте їх як другий рівень захисту, а не як головний план RTO.

5) Чи допомагає стиск швидкості відновлення чи шкодить?

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

6) Як зрозуміти, чи я обмежений I/O чи CPU під час відновлення?

Слідкуйте за пропускною здатністю відновлення разом із використанням CPU і затримками диска. Якщо CPU завантажений, а диски тихі — домінує розпакування/перевірки контрольних сум. Якщо CPU помірок, а затримки диска високі і пропускна здатність низька — ви I/O-обмежені. Тоді лагодьте правильну проблему замість того, щоб кричати на базу.

7) Для PostgreSQL: чи можу я обслуговувати трафік під час recovery?

Ви можете обслуговувати читання зі стендабу в режимі recovery (hot standby) залежно від конфігурації й випадку використання. Для PITR відновлення, що готується стати новим primary, зазвичай чекaють, поки recovery дійде до цілі, а потім промотують. Якщо потрібні швидкі читання — тримайте архітектуру зі стендабами; не імпровізуйте це під час інциденту.

8) Для MySQL: який найшвидший безпечний патерн відновлення?

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

9) Як часто нам робити drill’и відновлення?

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

10) Що перевіряти після відновлення, крім «воно стартувало»?

Виконайте критичні для додатка запити, перевірте кількість рядків або контрольні суми для ключових таблиць, підтвердіть коректність користувачів/прав, підтвердіть, що реплікацію можна відновити, і перевірте, що система дісталася вашого цільового часу PITR. «Сервіс запущено» — це Рівень 0; це не фініш.

Висновок: наступні кроки, які ви можете виконати

Якщо ваша мета — швидко повернутися онлайн, дебати MySQL vs PostgreSQL здебільшого відволікають. Стійкі відмінності у виробництві пов’язані з операційною позицією: безперервні логи, фізичні бекапи, швидке сховище для відновлень і відпрацьовані процедури.

Практичні наступні кроки:

  1. Визначте ваш рівень відновлення (Рівень 1/2/3) для кожного сервісу і запишіть його разом з RTO/RPO.
  2. Стандартизуйте фізичні бекапи (XtraBackup для MySQL InnoDB; pgBackRest для PostgreSQL) і автоматизуйте валідацію.
  3. Перевірте безперервність логів: імплементуйте роботу, що перевіряє ланцюги WAL/binlog end-to-end, а не просто «файли існують».
  4. Заміряйте ваші відновлення на реальному обладнанні. Окремо виміряйте швидкість копіювання та швидкість replay.
  5. Тримайте швидкий шлях відновлення: останні бекапи і логи на сховищі, що може забезпечити ваш RTO, плюс хости відновлення з потрібними ресурсами для розпакування й I/O.
  6. Проводьте drill’и, поки це не стане нудно. Нудне — швидке. Нудне — надійне. Нудне — те, за що платять ваші клієнти.
← Попередня
Успадкування властивостей ZFS: сюрприз, що змінює дочірні датасети
Наступна →
Виправити помилку Proxmox «IOMMU не ввімкнено» для PCI passthrough (VT-d/AMD‑Vi) безпечно

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