MAATRIX / Блог / Миф: бэкап создался без ошибок — значит, он рабочий

Миф: бэкап создался без ошибок — значит, он рабочий

MAATRIX

«Бэкап отработал, в логе Backup completed successfully, exit code 0 — можно спать спокойно» — эта мысль звучит логично и почти у всех администраторов сидит где-то на уровне рефлекса. Проблема в том, что код возврата процесса резервного копирования отвечает всего на один узкий вопрос: не упал ли сам процесс копирования. Он ничего не говорит о том, восстановится ли из этого архива работающая система, и именно в этом зазоре чаще всего рождаются самые дорогие инциденты с данными.

Откуда берётся эта уверенность

Миф держится на вполне рациональном наблюдении: если бы с диском, сетью или архиватором было что-то не так, скрипт бы упал с ошибкой — так устроено большинство привычных нам процессов. Компилятор ругается на синтаксическую ошибку, сервис не стартует при битом конфиге, копирование файла обрывается при недоступном диске. Мозг переносит эту логику и на бэкапы: раз процесс не пожаловался, значит результат корректен.

Здесь и кроется подмена. Резервное копирование в подавляющем большинстве реализаций — это перенос байтов из точки А в точку Б: tar архивирует файлы, mysqldump выгружает содержимое таблиц в текстовый поток, rsync синхронизирует блоки. Ни один из этих инструментов по умолчанию не знает и не обязан знать, что такое «корректная база данных» с точки зрения вашего приложения. Он умеет диагностировать физические проблемы уровня файловой системы и сети — обрыв соединения, нехватку места, отсутствие прав доступа. Он не умеет диагностировать логические проблемы уровня данных — обрезанную транзакцию, рассинхронизированные внешние ключи, дамп не той базы, снятый в неудачный момент.

Усиливает миф и то, что зелёный статус в системе мониторинга — это простой, приятный, легко автоматизируемый сигнал. Куда проще завести алерт на код возврата, чем на «данные внутри архива логически непротиворечивы» — вторую проверку ещё нужно спроектировать и написать отдельно, и по умолчанию её никто не пишет.

Что на самом деле проверяет «бэкап без ошибок»

Чтобы разрушить миф предметно, полезно разложить по полочкам, что именно контролирует типовой процесс бэкапа и на каком уровне он останавливается.

Успешное завершение обычно подтверждает:

  • процесс архивации или дампа запустился и не был прерван по таймауту или сигналу;
  • команда резервного копирования не вернула код ошибки (если конвейер собран без pipefail, это не гарантирует даже этого — см. ниже);
  • файл физически записался на целевой диск или в хранилище;
  • на диске/в бакете хватило места, чтобы завершить запись.

Успешное завершение НЕ подтверждает:

  • что архив открывается и распаковывается без повреждений;
  • что дамп базы данных синтаксически корректен и применяется без ошибок;
  • что снятые данные логически консистентны — то есть отражают согласованное состояние приложения на конкретный момент, а не обрывок промежуточной транзакции;
  • что забэкапился действительно тот источник, который нужен (тот путь, тот volume, та база);
  • что восстановленная из архива система реально поднимется и заработает.

Дистанция между верхним и нижним списком — это и есть та зона, где миф даёт сбой. Файловая система прекрасно умеет сообщать «я записала эти байты корректно», но она в принципе не может ответить на вопрос «эти байты образуют работающую базу данных» — это вопрос из совершенно другого уровня абстракции, до которого файловая система не дотягивается по определению.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Как повреждённые данные копируются без единой ошибки

Разберём конкретные механизмы, из-за которых бэкап технически «успешен», а по факту непригоден.

Снимок базы данных без согласованности (consistency). Если дамп или файловая копия снимается с работающей СУБД без правильной блокировки или без использования механизма транзакционного снимка, можно получить набор файлов, где часть таблиц отражает состояние до операции, а часть — уже после. Каждая отдельная запись на диск при этом абсолютно корректна с точки зрения файловой системы: блоки записались, контрольные суммы файловой системы сошлись, ошибок ввода-вывода не было. А целостность на уровне логики приложения — например, соответствие между заказом и его позициями в разных таблицах — оказывается нарушена, потому что снимки этих таблиц физически относятся к разным моментам времени.

# опасно: файловое копирование "живой" MySQL/InnoDB без --single-transaction
# и без остановки сервиса — можно получить несогласованный снимок
cp -r /var/lib/mysql /backups/mysql-raw

# правильнее: логический дамп с согласованным снимком в рамках одной транзакции
mysqldump --single-transaction --routines --triggers mydb | gzip > dump.sql.gz

Обрыв дампа посреди работы, спрятанный конвейером. В связке команда | архиватор > файл код возврата всей команды в shell по умолчанию равен коду возврата последнего элемента конвейера — то есть архиватора, а не самой команды дампа. Если mysqldump упал с ошибкой соединения на середине работы, а gzip честно сжал то, что успело прийти на вход, весь конвейер всё равно завершится с кодом 0.

set -euo pipefail   # без этой строки обрыв mysqldump/pg_dump посреди работы
                     # не остановит скрипт и не пометит бэкап как неудачный

LVM/файловые снапшоты без «заморозки» приложения. Снапшот тома снимается мгновенно на уровне блочного устройства и формально всегда «успешен» — сама операция снятия снапшота крайне редко даёт сбой. Но если СУБД или приложение не были предупреждены (через FLUSH TABLES WITH READ LOCK, fsfreeze, штатные хуки quiesce у гипервизора), на снимке может оказаться файл журнала транзакций в промежуточном состоянии — валидный с точки зрения блочного устройства, но не тот, с которого СУБД готова стартовать чисто.

Бэкап не того источника. Скрипт архивирует путь или volume, которые давно не являются актуальным хранилищем данных — например, после переноса datadir на другой диск или пересоздания Docker-volume под новым именем. Директория физически существует (пустая или с остатками старых файлов), tar её честно упаковывает, копирование идёт без единой ошибки — просто внутри не то, что нужно. Подробный разбор именно этого и смежных сценариев есть в статье бэкапы шли год и оказались нерабочими.

Повреждение уже после успешного создания. Архив может быть создан идеально корректно, а испортиться позже — при передаче по нестабильному каналу в удалённое хранилище, при сбое диска, на котором он лежит месяцами, или из-за истечения срока жизни ключа шифрования, которым он был зашифрован. Сам факт «бэкап когда-то создался без ошибок» ничего не говорит о его состоянии в момент, когда он реально понадобится — между этими двумя моментами могут пройти месяцы или годы.

Во всех случаях объединяющий признак один: ошибка происходит не там, где её отслеживает мониторинг успешности бэкапа. Файловая система и архиватор докладывают о своей узкой зоне ответственности честно — просто эта зона гораздо уже, чем «бэкап полностью пригоден к использованию».

Типичный путь к катастрофе: годы «успешных» бэкапов

Сценарий, который повторяется на серверах с пугающей регулярностью, выглядит примерно так. Администратор один раз настраивает cron-задачу с дампом базы и архивацией файлов, один раз проверяет её вручную сразу после настройки — распаковывает архив, видит нужные данные, убеждается, что всё в порядке — и на этом ручная проверка заканчивается навсегда. Дальше в дело вступает автоматизация: скрипт запускается каждую ночь, пишет в лог OK, мониторинг видит зелёный статус, все спокойны.

За месяцы или годы в системе успевает произойти любое из перечисленных выше изменений: путь к данным сдвинулся после миграции, версия СУБД обновилась и поменяла формат некоторых системных таблиц, место в хранилище бэкапов стало заканчиваться и старая ротация начала удалять архивы агрессивнее, чем накапливаются новые рабочие копии, ключ шифрования архивов был по недосмотру перевыпущен без сохранения старого. Ни одно из этих изменений само по себе не роняет cron-задачу — она продолжает рапортовать об успехе, потому что с её узкой точки зрения действительно всё хорошо: файл создаётся, весит некий разумный размер, ошибок при записи нет.

Момент истины наступает при первой реальной аварии — отказавшем диске, случайно удалённой таблице, атаке шифровальщика. Администратор запускает восстановление из «годами исправно работавшего» бэкапа и обнаруживает один из типичных вариантов: архив пустой или почти пустой, дамп обрывается посреди SQL-синтаксиса, распакованные данные оказываются недельной или полугодовой давности, потому что реальный источник резервирования давно был не тот. Психологически это едва ли не тяжелее, чем сама авария — потому что до этого момента у команды была полная, ничем не подкреплённая уверенность в защищённости данных, и именно эта уверенность мешала настроить резервный план действий заранее.

Это не редкий пограничный случай, а системное следствие того, как устроен контроль качества бэкапа по умолчанию почти везде — контролируется процесс копирования, а не результат восстановления. Пока не появится отдельная, специально спроектированная проверка именно результата, разрыв между «бэкап выполняется» и «бэкап работает» никуда не денется сам по себе, сколько бы лет подряд лог ни писал success.

Тестовое восстановление — единственный надёжный критерий

Из всего разобранного следует прямой практический вывод: единственный способ достоверно узнать, что бэкап рабочий — это восстановить из него систему (или её ключевую часть) и убедиться, что она действительно поднимается и работает. Любая другая проверка — размер файла, код возврата, даже контрольная сумма архива — подтверждает лишь часть цепочки, а не её целиком.

Полноценная репетиция восстановления закрывает сразу несколько классов проблем, которые точечные проверки физически не видят:

  • правильность и актуальность самой процедуры восстановления — команды, порядок шагов, переменные окружения могли устареть с момента последнего реального disaster recovery;
  • права доступа и владельцев файлов после восстановления — частая причина, по которой формально «восстановленная» система не стартует;
  • порядок поднятия зависимых сервисов — база должна подняться раньше приложения, очередь сообщений раньше воркеров, и так далее;
  • реальное время, которое займёт восстановление (RTO) — цифра, которую невозможно узнать иначе, кроме как замерив на практике, и от которой зависит, впишетесь ли вы в приемлемый простой.

Периодичность полной репетиции зависит от критичности системы и от того, как часто меняется инфраструктура вокруг бэкапа — новых версий ПО, миграций, смены хранилища. Как ориентир: для некритичных систем раз в квартал-полгода, для систем, от которых прямо зависит бизнес, — минимум раз в квартал, а лучше раз в месяц или после каждого значимого изменения инфраструктуры. Точную цифру для вашего случая правильнее определять исходя из того, сколько стоит простой в случае, если восстановление вдруг не сработает, а не подгонять под красивое число. Отдельно у нас разобран практический сценарий такой репетиции — репетиция восстановления: сценарий учений, там показано, как провести её без остановки продакшена, на отдельном стенде.

Полная репетиция — самая дорогая проверка по времени и ресурсам, и делать её ежедневно объективно нецелесообразно. Но её нельзя заменить более дешёвыми промежуточными проверками полностью — можно только снизить частоту полной репетиции за счёт того, что промежуточные уровни (о них ниже) ловят большинство проблем раньше и дешевле.

Как встроить проверку в процесс, не сорвав бюджет

Практический баланс — не выбирать между «полная репетиция каждый день» и «вообще ничего не проверять», а выстроить несколько уровней проверки с разной частотой и разной стоимостью.

Дешёвые проверки, которые стоит запускать при каждом бэкапе:

# файл не пустой и не аномально мал по сравнению с обычным размером
[ -s "$BACKUP_FILE" ] || { echo "ОШИБКА: пустой бэкап" >&2; exit 1; }

# архив физически цел, не оборван
tar -tzf "$BACKUP_FILE" > /dev/null || { echo "ОШИБКА: битый архив" >&2; exit 1; }

# для дампов PostgreSQL в custom-формате — оглавление читается без ошибок
pg_restore --list "$BACKUP_FILE" > /dev/null || echo "ОШИБКА: дамп не читается"

Средние по стоимости — регулярное, но не ежедневное точечное восстановление одного файла или применение дампа в одноразовую тестовую базу (например, в контейнере, который поднимается и уничтожается за пару минут). Это уже реально проверяет логическую валидность содержимого, а не только физическую структуру архива, и стоит минут, а не часов. Подробный разбор всех промежуточных уровней проверки — от размера файла до валидации дампа — есть в статье как проверить, что бэкап рабочий, не восстанавливая всё, она хорошо дополняет эту тему конкретными командами под разные форматы бэкапов.

Дорогая, но незаменимая — полная репетиция восстановления системы целиком по календарю, как описано выше.

Отдельно стоит подключить всё это к мониторингу, который реагирует не только на явную ошибку, но и на отсутствие ожидаемого сигнала «всё в порядке» — иначе сама проверка рискует стать ещё одним процессом, за которым никто не следит, что обесценивает весь смысл её существования. Это отдельная и довольно частая грабля — статья про мониторинг бэкапов разбирает, как выстроить такую систему оповещений правильно.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Если контрольная сумма (checksum) архива совпадает с эталонной — этого достаточно, чтобы считать бэкап рабочим?

Нет. Checksum подтверждает, что файл не изменился и не повреждён при передаче или хранении с момента, когда сумма была снята — то есть защищает от физической порчи после создания. Он ничего не говорит о том, была ли сама сумма снята с изначально консистентного, логически корректного архива.

Специализированные инструменты вроде Borgbackup или restic с их встроенной командой check решают проблему полностью?

Они закрывают уровень целостности архива — контрольные суммы блоков данных, отсутствие повреждений в репозитории. Это заметно лучше, чем просто код возврата 0, но всё ещё не проверяет логическую корректность содержимого дампа СУБД и не проверяет, что вся система целиком поднимется из этих данных.

Насколько часто на самом деле встречается ситуация «бэкап годами работал, а восстановиться не удалось»?

Точных общих цифр по индустрии нет и приводить их без источника было бы нечестно, но по характеру повторяющихся историй в администраторском сообществе это далеко не единичные случаи, а системная проблема — она возникает везде, где контроль ограничен только кодом возврата процесса, независимо от масштаба компании.

Что делать, если полную репетицию восстановления физически негде проводить — только один продакшен-сервер?

Для полноценной репетиции нужен отдельный стенд, но для этого не обязательно держать постоянно работающий второй сервер — достаточно временно арендованной машины, которую поднимают на день учений и выключают после. Это ощутимо дешевле, чем держать резерв круглый год, и полностью закрывает задачу проверки.

Как быстро понять, что конкретно сломалось, если тестовое восстановление провалилось?

Начните с уровня ниже: проверьте целостность архива отдельно от попытки его применить (tar -t, borg check, pg_restore --list) — это сразу разделит проблему на «архив физически битый» и «архив цел, но не применяется» и сильно сузит область поиска причины.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →