Резервні копії MySQL проти SQLite: що простіше відновити під тиском

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

Резервні копії не ламаються під час вашого квартального tabletop‑тренування. Вони ламаються о 02:17, коли телефон на чергуванні вібрує на тумбочці, а ваш мозок ще намагається погодити реальність.

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

Що насправді означає «легше відновити»

Більшість порівнянь резервних копій MySQL і SQLite пишуть люди, які не відновлювали жодну з них під час того, як Slack горів. При тиску «легше» — це не «менше функцій». Це контрольний список дій, які ви можете виконати швидко, повторювано й безпечно.

Питання відновлення, які мають значення о 02:17

  • Чи маю я консистентну резервну копію? Не просто «файл існує». Консистентна означає, що вона репрезентує реальну точку часу без порваних сторінок чи напівзаписаних транзакцій.
  • Чи можу я відновити без здогадок? Ідеально: одна команда й відома процедура. У реальності: вам все одно знадобиться судження, але менше розвилок у процесі допомагає.
  • Чи можу я швидко перевірити? Якщо верифікація займає години, її пропустять. Тоді ви дізнаєтеся, що бекап пошкоджений у найгірший момент.
  • Чи можу я зробити відновлення до конкретного моменту (PITR)? Якщо ні, «ми відновили» може означати «ми втратили останні шість годин».
  • Що станеться, якщо шар зберігання — проблема? Корупція рідко чемна. Вона охоче видаватиметься за помилку застосунку, поки ви не дійдете до відмови.

Операційна істина така: SQLite проста тому, що це здебільшого один файл, а MySQL відновлюваний завдяки системі з перевіреними в бою інструментами. При тиску обидва можуть бути простими, і обидва можуть бути пасткою. Різниця — у типі пастки.

Цитата, яку варто тримати в рунбуку: Надія — не стратегія. — генерал Гордон Р. Салліван

Ось менталітет відновлення. Не сподівайтеся, що копія файлу була консистентною. Не сподівайтеся, що binlog існує. Не сподівайтеся, що WAL було включено. Доведіть це.

Цікаві факти та історичний контекст (те, що має значення)

Невеликі історичні деталі пояснюють, чому кожна система поводиться певним чином під час бекапу й відновлення.

  1. SQLite з’явився у 2000 році як вбудована СУБД для інструментів, яким потрібен SQL‑движок без серверного процеса. Її історія бекапів — «скопіювати дані безпечно», а не «координувати кластер».
  2. SQLite відома як «безсерверна»: без демона, без мережевого протоколу. Ця простота — операційна перевага при відновленні — доки не втрутяться паралельність і семантика файлової системи.
  3. Режим WAL у SQLite став масовим у 2010‑х, бо покращує паралельність. Він також змінює поняття «бекапу»: сам файл .db може не містити найновіших зафіксованих даних.
  4. InnoDB став рушієм за замовчуванням у MySQL 5.5. До того часто використовували MyISAM, який не мав транзакцій з відновленням після збоїв. Якщо в вашій організації залишилися уявлення з того часу, вони можуть бути старші за ваших колег.
  5. Бінлоги MySQL були створені для реплікації, але стали основою для відновлення до конкретного моменту. Вони не опціональні, якщо вам важливо відкотити людську помилку.
  6. Percona популяризувала «гарячі» фізичні бекапи для InnoDB у 2000‑х з XtraBackup, бо логічні дампи не масштабуются і не зберігають фізичну структуру.
  7. «Копіювати datadir» колись було народним рецептом для MySQL. Іноді працювало, а потім боляче кусало людей, коли file‑per‑table, redo‑логи й метадані стали складнішими.
  8. SQLite покладається на семантику блокувань файлової системи. Помістіть його на дивну файлову систему або мережевий том із некоректними блокуваннями — і «просту файл‑БД» можна перетворити на фестиваль продуктивності й корупції.

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

Примітиви бекапу: що ви фактично зберігаєте

SQLite: «база — це файл» (і іноді ще два)

SQLite зберігає дані в одному головному файлі бази даних (зазвичай app.db). Але залежно від режиму журналювання у вас також можуть бути:

  • Ролбек‑журнал: app.db-journal (звично в режимі DELETE‑journal).
  • Файл WAL: app.db-wal плюс файл спільної пам’яті app.db-shm (в режимі WAL).

Під тиском пастка — забути, що «база» може бути трьома файлами і що їх потрібно захопити консистентно. Добра новина: якщо ви можете використати власний механізм резервного копіювання SQLite або зробити знімок файлової системи правильно, відновлення зазвичай просте.

MySQL: запущена система з кількома шарами бекапу

MySQL має серверний процес, redo‑логи, undo‑логи, файли даних і необов’язково бінлоги. Резервні копії бувають двох широких типів:

  • Логічні резервні копії (наприклад, mysqldump, mysqlpump): переносимий SQL, повільніший у масштабі, може бути частковим, зазвичай легше для огляду.
  • Фізичні резервні копії (наприклад, Percona XtraBackup, файлові знімки): швидші, зберігають фізичну структуру, потребують більше дисципліни та сумісності.

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

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

Відновлення SQLite під тиском: хороше, погане, файл

Чому SQLite може бути надзвичайно простим

Якщо у вас є консистентна копія, відновлення SQLite може бути таким же простим, як помістити файл назад на місце. Немає сервера для запуску, немає прав користувачів для відновлення, немає топології реплікації, яку треба розплутувати.

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

Чому SQLite може бути несподівано складним

Складнощі виникають не в самому відновленні. Вони в розумінні того, що ви скопіювали консистентно.

Якщо ви скопіювали файл бази під час записів додатка, ви можете отримати бекап, який здається нормальним доти, поки не виконаєте запит, що зачепить «не ту» сторінку. Або поки не виконаєте vacuum. Або поки CEO не спробує експортувати звіт — і ви не виявите, що відновили логічно неконсистентну базу, яка проходить лише поверхневі тести.

Режим WAL: кращий друг і найгірший ворог відновлення

WAL режим чудовий для паралельності. Він також класичний підступ під час бекапу. У WAL режимі недавні зафіксовані транзакції можуть зберігатися в -wal, поки не буде виконано чекпоінт у головний файл .db. Якщо ви зробите бекап лише app.db, ви можете відновити старіший стан, ніж очікуєте.

При тиску успіх відновлення SQLite часто зводиться до одного питання: чи включав ваш бекап консистентний знімок .db, -wal і -shm, чи ви використали онлайн‑метод бекапу SQLite?

Реальність корупції

SQLite стійкий, але не магічний. Корупція зазвичай походить із середовища: погане зберігання, зламані блокування на мережевих файлових системах або додатки, які поводяться з SQLite як з багатописувацькою серверною БД і винаходять власну «креативну» паралельність.

Коли SQLite корумпує, шлях відновлення зазвичай виглядає так:

  • Спробуйте відновити з відомого доброго бекапу.
  • Якщо доброго бекапу немає, спробуйте дамп‑і‑відновлення, salvaging або по‑табличний експорт.
  • Прийміть, що частина даних може бути втрачена, і сплануйте, як це узгодити на рівні застосунку.

Відновлення MySQL під тиском: варіанти, гострі краї та чому це досі розумно

Перевага MySQL у відновленні: кілька шляхів

MySQL дає вам вибір: логічне відновлення, фізичне відновлення, відновлення після краху, підняття репліки, відновлення по точному часу через binlog. Під тиском кілька опцій — це добре якщо ви вже знаєте, яку саме будете використовувати. Інакше це втома від рішень і SQL‑синтаксису.

Логічні резервні копії: надійні, але час може вбити

mysqldump — це клейка стрічка в оперуванні MySQL. Ви можете використовувати його майже скрізь, відновити у трохи інших версіях і побачити, що всередині.

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

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

Фізичні резервні копії: швидкі відновлення, більше передумов

Фізичні бекапи (як XtraBackup) можуть відновлювати великі InnoDB набори даних значно швидше, ніж логічне відтворення SQL. Вони також зберігають внутрішні структури, що допомагає з передбачуваною продуктивністю після відновлення.

Ціна в тому, що ви повинні дотримуватися сумісності версій, конфігурації та вимог інструмента. Фізичне відновлення може бути чистою перемогою — доки ви не виявите, що ніколи його не практикували, а ваш рунбук устарів на три роки.

PITR: справжня причина, чому MySQL виграє в багатьох продукційних справвах

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

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

Вбудоване відновлення після краху — це не заміна бекапу

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

Жарт №2: «Нам не потрібні бекапи, у нас RAID» — це як сказати, що вам не потрібні паски безпеки, бо у машини є чотири колеса.

Вердикт під тиском: хто перемагає і коли

Якщо потрібне найпростіше можливе відновлення

SQLite перемагає, коли ваша база невелика‑до‑середньої, записи контрольовані, і у вас є чистий механізм отримання консистентного знімка (API бекапу SQLite або правильні знімки файлової системи). Відновлення може бути простим: заміна файлу і перезапуск додатка.

Якщо потрібне відновлення до конкретного моменту і операційна гнучкість

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

Де команди насправді караються

Команди SQLite постраждають через помилкові припущення про копіювання файлів, поведінку WAL і блокування файлової системи.

Команди MySQL постраждають через невибір стратегії бекапу (логічний або фізичний + binlog) і потім імпровізацію під час аварії.

Якщо ви хочете максимально відновлювану систему під тиском, відповідь не «виберіть MySQL» або «виберіть SQLite». Відповідь така: оберіть СУБД, що відповідає вашій операційній зрілості, і потім вправляйтеся у відновлення, як слід.

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

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

Завдання для SQLite

Завдання 1: Визначити, чи база використовує WAL

cr0x@server:~$ sqlite3 /var/lib/app/app.db "PRAGMA journal_mode;"
wal

Значення: Вивід wal означає, що активні зміни можуть бути в app.db-wal.

Рішення: Бекапи мають включати app.db, app.db-wal і app.db-shm, або використовувати онлайн‑метод бекапу SQLite.

Завдання 2: Перевірити наявність WAL файлів (і їх розмір)

cr0x@server:~$ ls -lh /var/lib/app/app.db*
-rw-r----- 1 app app 512M Dec 30 01:58 /var/lib/app/app.db
-rw-r----- 1 app app  96M Dec 30 02:15 /var/lib/app/app.db-wal
-rw-r----- 1 app app  32K Dec 30 02:15 /var/lib/app/app.db-shm

Значення: Великий -wal підказує, що багато зафіксованих транзакцій ще не чекпоінтовано в основному файлі.

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

Завдання 3: Зробити консистентний онлайн‑бекап за допомогою команди бекуп SQLite

cr0x@server:~$ sqlite3 /var/lib/app/app.db ".backup '/backups/app.db.bak'"

Значення: SQLite створює консистентний бекап навіть під час використання БД (він співпрацює зі своїм блокуванням).

Рішення: Віддавайте перевагу цьому над cp коли можливо; це уникне проблеми «порваної копії».

Завдання 4: Перевірка цілісності відновленого файлу SQLite

cr0x@server:~$ sqlite3 /restore/app.db "PRAGMA integrity_check;"
ok

Значення: ok — найкраще двобуквене слово, яке ви сьогодні прочитаєте.

Рішення: Якщо не ok, зупиніться і відновіть інший бекап або переходьте в режим salvage.

Завдання 5: Швидко перерахувати таблиці, щоб підтвердити наявність схеми

cr0x@server:~$ sqlite3 /restore/app.db ".tables"
accounts audit_log invoices sessions users

Значення: Ви маєте очікувані таблиці; це не порожня чи неправильна база.

Рішення: Прямуйте до прикладних smoke‑тестів. Якщо таблиць немає — ви відновили невірний файл або частковий бекап.

Завдання 6: Врятувати дані з пошкодженої SQLite через dump

cr0x@server:~$ sqlite3 /var/lib/app/app.db ".dump" > /tmp/app_dump.sql
Error: database disk image is malformed

Значення: Файл настільки пошкоджений, що повний дамп не вдався.

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

Завдання для MySQL

Завдання 7: Підтвердити, що бінлоги ввімкнені (перевірка PITR‑здатності)

cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'log_bin';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+

Значення: Бінлоги ввімкнені, отже PITR можливе, якщо файли збережено.

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

Завдання 8: Знайти, де зберігаються binlog і в якому форматі

cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES WHERE Variable_name IN ('log_bin_basename','binlog_format');"
+------------------+----------------------+
| Variable_name    | Value                |
+------------------+----------------------+
| binlog_format    | ROW                  |
| log_bin_basename | /var/lib/mysql/binlog|
+------------------+----------------------+

Значення: Ви знаєте, де шукати і що формат ROW (добре для коректності, менш читабельний).

Рішення: Переконайтеся, що бекапи включають binlog або що їх відправляють кудись; ROW означає, що ви, ймовірно, будете використовувати mysqlbinlog для відтворення.

Завдання 9: Перевірити, що останній успішний бекап існує і має нетривіальний розмір

cr0x@server:~$ ls -lh /backups/mysql/full/
-rw-r----- 1 backup backup 8.2G Dec 30 01:00 full.sql.gz
-rw-r----- 1 backup backup 1.4M Dec 30 01:00 full.sql.gz.sha256

Значення: Мульти‑ГБ дамп підказує, що ви не випадково забекали порожню схему.

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

Завдання 10: Валідація контрольної суми перед відновленням

cr0x@server:~$ sha256sum -c /backups/mysql/full/full.sql.gz.sha256
full.sql.gz: OK

Значення: Ваш артефакт бекапу пережив зберігання, передачу і час.

Рішення: Якщо це не так — не відновлюйте. Знайдіть інший бекап, інакше ви створите ще один інцидент.

Завдання 11: Відновити mysqldump у новий інстанс (безпечніше, ніж на місці)

cr0x@server:~$ zcat /backups/mysql/full/full.sql.gz | mysql -uroot -p --host=127.0.0.1 --protocol=tcp

Значення: Зазвичай відсутність виводу означає успіх; помилки з’являться в stderr.

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

Завдання 12: Підтвердити кількість рядків для критичних таблиць після відновлення

cr0x@server:~$ mysql -uroot -p -e "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='app' AND table_name IN ('users','orders');"
+------------+------------+
| table_name | table_rows |
+------------+------------+
| users      |     184233 |
| orders     |     921044 |
+------------+------------+

Значення: Кількості правдоподібні (не нуль і не абсурдно малі).

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

Завдання 13: Визначити позицію binlog на момент бекапу (для PITR)

cr0x@server:~$ zcat /backups/mysql/full/full.sql.gz | grep -m1 "CHANGE MASTER TO"
-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.003982', MASTER_LOG_POS=19483211;

Значення: Цей дамп зафіксував файл/позицію binlog, консистентну з знімком.

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

Завдання 14: Переглянути binlog навколо підозрілого часу виконання запиту

cr0x@server:~$ mysqlbinlog --start-datetime="2025-12-30 01:00:00" --stop-datetime="2025-12-30 02:00:00" /var/lib/mysql/binlog.003982 | head
# at 19483211
#251230  1:00:00 server id 1  end_log_pos 19483315 CRC32 0xa1b2c3d4  Anonymous_GTID  last_committed=0 sequence_number=1 rbr_only=no
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;

Значення: Ви читаєте потік binlog за цей вікон.

Рішення: Вирішіть, чи робитимете відтворення за часом (зупинитись перед помилкою), чи фільтруватимете конкретний запит (складніше, ризикованіше, іноді необхідне).

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

cr0x@server:~$ mysqlbinlog --start-position=19483211 --stop-datetime="2025-12-30 01:47:00" /var/lib/mysql/binlog.003982 | mysql -uroot -p

Значення: Транзакції між позицією бекапу і кінцевим часом застосовуються.

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

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

cr0x@server:~$ journalctl -u mysql -n 50 --no-pager
Dec 30 02:11:04 db1 mysqld[2190]: InnoDB: Starting crash recovery from checkpoint LSN=7123489123
Dec 30 02:11:05 db1 mysqld[2190]: InnoDB: 1 transaction(s) which must be rolled back
Dec 30 02:11:06 db1 mysqld[2190]: InnoDB: Crash recovery finished.

Значення: MySQL виконав відновлення після краху і стартував коректно.

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

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

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

По‑перше: доведіть, що маєте потрібні артефакти

  • SQLite: Підтвердіть режим журналювання і наявність консистентного набору файлів (.db + -wal + -shm коли застосовно).
  • MySQL: Підтвердіть наявність бекапу, перевірте контрольну суму і (якщо PITR) переконайтесь, що бінлоги існують для потрібного вікна часу.

По‑друге: доведіть, що ціль відновлення розумна

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

По‑третє: визначте, чи ви обмежені I/O, CPU чи блокуваннями

  • Симптоми I/O‑вузького місця: низька пропускна здатність відновлення, високі часи очікування, сплески латентності зберігання. Виправлення: перейти на швидший диск, використовувати знімки/фізичне відновлення, уникати розпакування на тому ж повільному томі.
  • Симптоми CPU‑вузького місця: декомпресія завантажує ядра, повільні контрольні суми. Виправлення: паралельна декомпресія, перенести обчислення ближче до даних або використовувати попередньо розпакований етап.
  • Симптоми блокувань (SQLite): «database is locked», довгі затримки. Виправлення: зупинити писачів, використовувати backup API або відновити з знімка при вимкненому застосунку.

По‑четверте: чітко визначте мету відновлення

  • Вам потрібне останнє відоме робоче (швидке відновлення) чи відновлення до конкретного моменту (точне)?
  • Ви відновлюєтесь після відмови обладнання, помилки оператора чи корупції даних? Шлях відрізняється.

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

Помилки SQLite

Симптом: Після відновлення відсутні останні записи.
Корінна причина: Увімкнений режим WAL; бекап копіював лише .db без -wal.
Виправлення: Робіть бекап через sqlite3 .backup або атомарно знімайте всі пов’язані файли; перевіряйте шляхом порівняння маркерів останнього запису.
Симптом: «database disk image is malformed» під час запитів або дампу.
Корінна причина: Пошкоджений файл бази (часто через небезпечне копіювання під час запису, корупцію зберігання або зламані блокування на спільних монтуваннях).
Виправлення: Відновіть відомий добрий бекап; якщо недоступний — спробуйте salvage/дамп цілих таблиць і перебудову БД; перемістіть БД на локальний диск з коректними блокуваннями.
Симптом: «database is locked» під час бекапу або запуску застосунку.
Корінна причина: Тривалі транзакції писання або кілька писачів у середовищі без належної координації.
Виправлення: Заглушіть писачів; переконайтеся, що режим WAL і налаштування busy timeout адекватні; робіть бекапи через механізм SQLite.

Помилки MySQL

Симптом: Відновлення завершилось, але дані на кілька годин застарілі.
Корінна причина: Бінлоги не зберігалися (або не бекапилися/не шипили), тому немає PITR.
Виправлення: Увімкніть log_bin, встановіть політику зберігання і відправку бінлогів кудись ще. Тестуйте PITR щоквартально.
Симптом: Фізичне відновлення не стартує MySQL з помилками InnoDB.
Корінна причина: Несумісність версій/конфігурацій, відсутні redo/undo файли або відновлення datadir без підготовчого кроку.
Виправлення: Стандартизувати версії, зберігати конфігурації з бекапами і репетирувати процедури фізичного відновлення в ізольованому середовищі.
Симптом: Відновлення через mysqldump повільне і блокує продукцію.
Корінна причина: Відновлення в зайнятий інстанс; однопотокове відтворення; важке створення індексів під час завантаження.
Виправлення: Відновіть у новий інстанс, потім переключіть. Розгляньте фізичні бекапи для великих наборів. Використовуйте розумні стратегії завантаження (але не «оптимізуйте» бездумно).
Симптом: При відтворенні PITR виникають помилки duplicate key або дивні помилки обмежень.
Корінна причина: Відтворення почали з неправильної позиції binlog, відтворення в неправильний стан схеми або змішування GTID/не‑GTID припущень.
Виправлення: Завжди записуйте координати binlog під час бекапу. Використовуйте чисте базове відновлення, потім відтворюйте вперед. Віддавайте перевагу GTID‑сумісним налаштуванням при масштабній експлуатації.

Три міні‑історії з корпоративного життя

Інцидент №1: Неправильне припущення (копія файлу SQLite ≠ бекап)

Невелика продуктова команда запустила внутрішній інструмент для погоджень. Він використовував SQLite, бо це було «лише метадані», трафік був невеликий, і деплой залишався простим. План бекапу — нічний cron: cp app.db app.db.$(date) у директорію бекапів, а потім в об’єктне сховище.

Це працювало місяцями. Ніхто не замислювався. Саме тоді й починаються проблеми.

Одного ранку інструмент почав падати при погодженнях з неясними SQL‑помилками. На чергуванні відновили нічний бекап. Додаток піднявся, але погодження зникли — конкретно останній день змін. Усі звинувачували відновлення. Потім додаток. Потім, неминуче, on‑call.

Справжня проблема: база була в WAL‑режимі, а cron копіював лише app.db. «Бекап» був консистентним у сенсі валідного файлу SQLite. Він також не містив зафіксованих транзакцій, що перебували в WAL під час копіювання. Нічна копія акуратно зберегла старіший вигляд реальності.

Виправлення було нудним: перейти на онлайн‑бекап SQLite (або атомарно знімати весь набір файлів WAL), додати перевірку цілісності після бекапу і тест відновлення. Раптом «відновити» означало «відновити», а не «відновити‑нібито».

Інцидент №2: Оптимізація, що зіграла злий жарт (хакі швидкості для відновлення MySQL)

Середня SaaS‑компанія користувалася нічними логічними дампами MySQL. Відновлення були повільні, тож хтось запропонував класичний «прискорити імпорт»: відключити перевірку зовнішніх ключів і унікальностей під час відновлення і накрутити налаштування bulk. Це працювало в staging. Працювало в девелі. Навіть одноразово спрацювало в продакшні — і так погані ідеї заслужують довговічності.

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

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

Постмортем був болючий, але корисний: відновлення мають розглядатись як зміни в продакшні з gates перевірки. Вони перейшли на відновлення в новий інстанс, виконували перевірки цілісності і reconcile‑роботи на рівні застосунку, і тільки тоді робили cutover. Також вони переглянули фізичні бекапи, щоб скоротити RTO без шкоди коректності.

Інцидент №3: Нудна, але правильна практика, що врятувала день (PITR MySQL з binlog)

Команда у великій організації тримала MySQL з тижневими повними бекапами, щоденними інкрементальними і безперервним шипінгом binlog. Нічого гламурного. Потрібна була дисципліна: налаштування зберігання, моніторинг прогалин у binlog і періодичні вправи з відновлення та вміння наполягати: «так, ми знову тестуємо відновлення».

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

Оголосили інцидент і призупинили записи. Потім виконали відпрацьований рунбук: відновили останній повний бекап в новий інстанс, застосували інкременти і відтворили binlog до таймстемпу за кілька секунд до запуску скрипта. Сервіс повернули з мінімальною втратою даних, і команда пізніше звела кілька хвилин записів, що були целенаправлено призупинені під час відновлення.

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

Контрольні списки / поетапний план

SQLite: поетапний план відновлення

  1. Зупиніть писачів (або переведіть додаток у режим обслуговування). SQLite може обробляти паралельність, але відновлення — не час домовлятися про блокування з робочим навантаженням.
  2. Визначте режим журналювання і підтвердіть, які файли потрібно відновити.
  3. Відновіть як набір: .db плюс файли WAL якщо застосовно, з одного знімка.
  4. Запустіть перевірку цілісності і базові перевірки схеми.
  5. Зробіть smoke‑тест додатком з реальними запитами, а не просто «SELECT 1».
  6. Заберіть пошкоджений артефакт для подальшого аналізу. Не перезаписуйте його. Інциденти дорогі — витягуйте з них уроки.

MySQL: поетапний план відновлення (логічне + PITR)

  1. Визначте точку відновлення: останній робочий час або «безпосередньо перед поганим запитом». Запишіть його.
  2. Заморозьте записи якщо мета — коректність (особливо для PITR). Ви не можете переслідувати рухому ціль вічно.
  3. Перевірте артефакт бекапу (контрольна сума, здоровий розмір).
  4. Відновіть до нового інстансу коли можливо. Відновлення на місці — для надзвичайних обставин у надзвичайних обставинах.
  5. Застосуйте binlog від зафіксованої позиції до цільового таймстемпу/позиції.
  6. Перевірте дані: кількість рядків, критичні інваріанти і прикладні перевірки.
  7. Переключіть трафік (DNS, балансувальник навантаження, рядки підключення), потім моніторте помилки і реплікацію.
  8. Пост‑відновлювальна гігієна: забезпечте відновлення політик зберігання binlog, продовжіть бекапи й збережіть старий інстанс для порівняння й судової експертизи.

MySQL: поетапний план відновлення (фізичне відновлення)

  1. Підтвердьте сумісність версії і конфігурації з бекапом.
  2. Підготуйте чисте сховище з достатнім місцем для даних + запасом.
  3. Підготуйте бекап (застосуйте логи) згідно з процедурою вашого інструмента.
  4. Відновіть datadir і встановіть власника/права правильно.
  5. Запустіть MySQL і спостерігайте логи до повної готовності.
  6. Перевірте з тестами, що відповідають вашим бізнес‑інваріантам.

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

1) Чи SQLite «легше відновити», бо це один файл?

Іноді. Якщо у вас консистентний знімок, відновлення тривіальне. Якщо ви скопіювали його небезпечно або забули WAL файли — відновлення перетворюється на археологію.

2) Чи може SQLite робити відновлення до конкретного моменту як MySQL?

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

3) Чи є cp app.db коли‑небудь валідним бекапом SQLite?

Лише якщо ви гарантуєте, що база не пишеться під час копіювання, або ви робите файловий знімок, який є crash‑consistent і включає файли WAL. Інакше використовуйте механізм бекапу SQLite.

4) Для MySQL, чи достатній mysqldump?

Для малих‑та‑середніх наборів даних і переносимості — так. Для великих наборів або жорстких RTO вам знадобляться фізичні бекапи плюс binlog. І — обов’язково — практика відновлення.

5) Який найбільший «підступ» у відновленні MySQL в реальному житті?

Думати, що у вас є PITR, коли його немає. Binlog вимкнено, binlog не збережено або не відправлено — і «відкотити помилку» перетворюється на «відновити вчора і просити вибачення».

6) Якщо MySQL має відновлення після краху, навіщо потрібні бекапи?

Відновлення після краху допомагає одному інстансу піднятися після некоректного вимкнення. Воно не захищає від втрати диска, помилки оператора, ransomware чи потреби «відновити на вчора».

7) Що більш імовірно пошкодиться?

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

8) Який найшвидший безпечний шлях відновлення для кожної з них?

SQLite: відновити відомий добрий знімок DB (і набір WAL якщо застосовно), виконати PRAGMA integrity_check, перезапустити додаток. MySQL: відновити з фізичного бекапу на свіжий інстанс, потім застосувати binlog до цільової точки.

9) Чи варто тримати SQLite на мережевому сховищі?

Уникати, якщо ви не впевнені в семантиці блокувань файлової системи і поведінці під навантаженням. SQLite щасливіший на локальних дисках. Мережеві монтування — це місце, куди «проста» відправляється ускладнюватися.

10) Як часто тестувати відновлення?

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

Висновок: практичні подальші кроки

Якщо ви хочете, щоб відновлення було легким під тиском, спроектуйте його:

  • Для SQLite: припиніть робити випадкові копії файлів. Використовуйте механізм бекапу SQLite або консистентні знімки, і завжди враховуйте WAL. Додавайте перевірки цілісності після бекапу й після відновлення.
  • Для MySQL: оберіть стратегію: логічні бекапи для переносимості, фізичні для швидкості, а binlog для PITR. Потім практикуйте відновлення в чистому середовищі, поки рунбук не перестане бути фантазією.
  • Для обох: автоматизуйте валідацію бекапів, зберігайте кілька поколінь і проводьте хоча б одну реальну репетицію відновлення на квартал. Час дізнатися, що ваш бекап зламаний — не тоді, коли вже постраждали клієнти.

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

← Попередня
Ubuntu 24.04: VLAN не працюють — один прапорець bridge, який більшість забуває
Наступна →
EPYC: як AMD перетворила сервери на шоурум

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