Битый бит расползся по всем копиям: как порча наследуется бэкапами
Бэкап-система делает ровно то, что от неё требуется: копирует файлы такими, какие они есть. Она не умеет отличать «файл изменился, потому что вы записали в него новые данные» от «файл изменился, потому что один бит перевернулся на диске сам по себе». Если повреждение произошло тихо, без ошибки чтения и без падения приложения, оно копируется в бэкап как легитимное изменение — и дальше живёт во всех последующих копиях, пока старые чистые версии не будут вычищены ротацией по сроку хранения.
Содержание
- Почему бэкап не может отличить порчу от правки
- Как выглядит расползание на практике
- Почему инкрементные бэкапы усугубляют проблему
- Ложное чувство безопасности: «у нас же много копий»
- Практика: где ставить проверку целостности
- Что делать, если порча уже обнаружена
- Как выстроить защиту заранее, а не разбираться постфактум
Почему бэкап не может отличить порчу от правки
У любой системы резервного копирования — rsync, restic, borg, Bacula, встроенный dump в PostgreSQL — есть одна общая черта: она работает на уровне «что лежит в источнике сейчас» и не имеет независимого эталона, с которым можно было бы сверить содержимое. Она видит изменившийся файл (по mtime, по контрольной сумме блока, по номеру транзакции в WAL) и делает вывод: файл изменился, значит нужно зафиксировать новое состояние. Дальше это состояние копируется, дедуплицируется, упаковывается в снапшот — и с точки зрения бэкап-системы всё прошло штатно.
Проблема в том, что «файл изменился» и «файл испорчен» с точки зрения байтов неразличимы. И то и другое — это набор байтов, отличный от предыдущего набора байтов. Причины порчи banальны и не всегда громкие:
- Bit rot на диске — самопроизвольная деградация магнитного или флеш-носителя, когда отдельные биты меняют значение без физической ошибки чтения. Диск отдаёт данные, ECC внутри контроллера может даже не заметить единичную ошибку, если она укладывается в допуск.
- Ошибка в оперативной памяти без ECC — данные прошли через RAM с перевёрнутым битом до того, как попали на диск, и записались уже в изменённом виде.
- Баг в приложении — например, при записи в файл конфигурации или дампа произошёл частичный сбой (диск заполнился, процесс убили сигналом), и файл сохранился в усечённом или частично перезаписанном виде, но без явной ошибки уровня ОС.
- Ошибка файловой системы без встроенных контрольных сумм (ext4, XFS, NTFS в обычном режиме) — метаданные или блоки данных повреждаются при сбое питания или из-за бага драйвера, а fsck это не всегда ловит на уровне содержимого файла.
- Порча на уровне сети или RAID-контроллера — данные повреждаются при передаче между узлами и сохраняются с ошибкой, которую никто не проверил.
Ни один из этих сценариев не поднимает тревогу на уровне бэкап-агента. Файл прочитался, у него другой хэш, чем в прошлый раз, — значит, новая версия, копируем. Это не баг конкретного инструмента, это фундаментальное ограничение: бэкап без независимой проверки целостности — это зеркало, а не детектор лжи. Подробнее о механизме самой тихой порчи данных, ещё до бэкапов, разобрано в статье про целостность данных и тихую порчу файлов.
Как выглядит расползание на практике
Возьмём типичный случай: на сервере лежит база данных или архив документов, бэкап снимается раз в сутки. В понедельник ночью на диске тихо переворачивается бит в середине большого файла — например, в дампе PostgreSQL или в бинарном файле изображения в хранилище. Файл технически читается, ошибки нет, просто несколько байт не те, что были вчера.
Дальше по дням:
- Вторник, 03:00. Бэкап-скрипт делает свою обычную работу: сверяет mtime или запускает инкрементальный снимок, видит изменившийся блок, копирует его в новый инкремент. Порченный блок зафиксирован в бэкапе №1 после порчи.
- Среда — воскресенье. Файл больше не трогают, порченный блок остаётся тем же, каждый следующий инкремент либо не копирует его повторно (потому что блок не менялся с момента порчи), либо копирует — в любом случае везде лежит одна и та же битая версия.
- Следующий понедельник. Ротация по расписанию (например, GFS — grandfather-father-son) удаляет самый старый ежедневный бэкап, который был снят до порчи. Теперь на этот момент бэкап-система хранит только версии файла ПОСЛЕ порчи.
- Через месяц. Даже последний «чистый» полный бэкап, снятый до понедельника порчи, вышел из окна хранения и удалён по политике retention. Все доступные копии — испорченные.
Ключевой момент здесь — не сам факт порчи, а то, что система бэкапов ведёт себя абсолютно правильно с её точки зрения на каждом шаге. Ошибки нет ни на одном этапе. Есть только отсутствие проверки на входе.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПочему инкрементные бэкапы усугубляют проблему
С полными бэкапами (full backup) ситуация неприятная, но управляемая: если вы храните, скажем, четыре последних полных копии с недельным интервалом, у вас есть месяц на то, чтобы заметить порчу и откатиться к чистой версии. С инкрементными и дифференциальными схемами — а именно они чаще всего используются на VPS и выделенных серверах ради экономии места и трафика — картина хуже по нескольким причинам:
- Инкремент фиксирует только дельту. Если порча произошла в файле, который редко меняется целиком (конфиг, статичный архив, редко обновляемая таблица), испорченный блок может попасть в инкремент один раз и затем «наследоваться» цепочкой без физического копирования заново — восстановление всё равно потянет именно эту версию блока, потому что цепочка инкрементов ссылается на неё.
- Восстановление требует всей цепочки. В отличие от полного бэкапа, который самодостаточен, инкрементный бэкап — это база плюс последовательность дельт. Чтобы понять, в какой именно точке цепочки появилась порча, нужно проверять каждое звено, а не просто взять «предыдущую копию».
- Дедупликация размазывает проблему шире. Инструменты вроде restic или borg используют дедупликацию по чанкам: один и тот же испорченный чанк может физически переиспользоваться в десятках снапшотов. Технически это компактно и эффективно, но означает, что «удалить порченный бэкап» — не такая простая операция, как удалить один файл: чанк может быть частью многих версий одновременно.
- Короткое окно хранения инкрементов. Часто ежедневные инкременты хранят 2-4 недели, а полные — месяцами. Если порча тихая и незаметная дольше, чем короткое окно, к моменту обнаружения инкрементная цепочка до чистого состояния уже недоступна, даже если формально «полный» бэкап месячной давности ещё жив, но уже вне зоны, которую кто-то смотрит.
Разницу между схемами и то, как считается объём каждой, разбирали отдельно в статье про разницу между инкрементальным, дифференциальным и полным бэкапом — она помогает понять, где именно в конкретной схеме появляется эта уязвимость к тихой порче.
Ложное чувство безопасности: «у нас же много копий»
Частое заблуждение: если бэкапов много (несколько копий, несколько мест хранения, правило 3-2-1), то риск потери данных из-за порчи автоматически снижается. Это верно для потери носителя — диск сгорел, дата-центр закрылся, случайно удалили файл. Но это НЕ работает против тихой порчи содержимого, потому что все копии сделаны с одного и того же — уже испорченного — источника или последовательно унаследовали порчу друг от друга.
Наглядный пример: у вас три копии бэкапа — локальная, в другом дата-центре и в облаке, все синхронизируются с одного и того же исходного сервера по одному и тому же расписанию. Если исходный файл испорчен на сервере до момента снятия бэкапа, все три копии получат одинаково испорченную версию. Количество копий не помогает, если у них общий источник истины и нет независимой проверки на входе.
То же самое справедливо для геораспределённых схем: если вы храните бэкапы в нескольких дата-центрах или странах ради отказоустойчивости, это защищает от юрисдикционных и физических рисков, но не от того, что все узлы честно скопировали один и тот же битый файл.
Единственное, что реально защищает от наследования порчи — это проверка целостности данных ДО того, как они попадут в цепочку бэкапов, а не после, и не полагание на количество копий как на замену этой проверке.
Практика: где ставить проверку целостности
Проверку нужно встраивать на нескольких уровнях, и раньше — не значит только «на исходном сервере», но именно там она самая дешёвая и эффективная:
1. На уровне файловой системы источника. ZFS и btrfs хранят контрольные суммы каждого блока данных и умеют находить порчу при обычном чтении (если есть избыточность — zraid, RAID1 — умеют и чинить). На ext4/XFS без встроенных чексумм эту роль берёт на себя периодический scrub на уровне блочного устройства (smartctl, MD RAID check) или прикладной чек-суммирующий скрипт.
# Пример запуска scrub на ZFS-пуле
zpool scrub tank
zpool status tank # смотрим на CKSUM errors после завершения
# Периодическая проверка через cron, раз в месяц
0 3 1 * * root /sbin/zpool scrub tank
2. На уровне приложения перед бэкапом. Для баз данных — не просто скопировать файл, а прогнать логическую проверку. Для PostgreSQL это pg_dump с последующей попыткой restore в тестовую базу, для MySQL — mysqlcheck или сверка контрольной суммы таблиц через CHECKSUM TABLE.
# MySQL/MariaDB: проверка целостности таблиц перед бэкапом
mysqlcheck --check --all-databases -u backup_user -p
# PostgreSQL: быстрая проверка на битые страницы
pg_dump --schema-only mydb > /dev/null && echo "OK: schema readable"
3. На уровне самого бэкап-агента. restic и borg умеют хранить и проверять чек-суммы чанков внутри своего репозитория командой check — но это проверяет целостность самого репозитория бэкапов, а не то, был ли исходный файл уже испорчен до копирования. Это не заменяет пункты 1 и 2, а дополняет их.
# restic: проверка целостности репозитория (не источника!)
restic check --read-data-subset=10%
# borg: аналогичная проверка архивов
borg check --verify-data /path/to/repo
4. Периодическая сверка контрольных сумм важных файлов вручную или скриптом. Если инфраструктура не под ZFS/btrfs, простое решение — вести реестр хэшей ключевых файлов и сверять его раз в неделю или месяц.
# Сохраняем эталонные хэши
find /data -type f -exec sha256sum {} \; > /var/lib/checksums/baseline.sha256
# Периодическая сверка — раз в месяц через cron
sha256sum -c /var/lib/checksums/baseline.sha256 --quiet 2>&1 | tee /var/log/integrity-check.log
Полезно смотреть на это как на регулярный скрабинг — периодическую фоновую проверку, а не разовое действие. О принципе такой проверки на уровне диска и о том, как обнаружить, что накопитель начинает врать, тоже стоит почитать отдельно, если вопрос именно в дисках, а не в файлах.
Что делать, если порча уже обнаружена
Если проверка целостности сработала и порча найдена — важно действовать быстро, потому что каждый следующий бэкап-цикл сокращает окно, в котором ещё существует чистая копия.
- Немедленно остановите ротацию для затронутого набора бэкапов — временно отключите cron-задачу или политику retention, чтобы не удалить единственную сохранившуюся чистую копию раньше времени.
- Найдите точку заражения — переберите доступные снапшоты/архивы от новых к старым и найдите последнюю версию файла, где контрольная сумма (или логическая проверка для БД) ещё сходится с ожидаемой. Именно отсюда начинается восстановление.
- Проверьте соседние файлы, изменённые в тот же период — единичный переворот бита редко бывает истинно единичным на уровне всей файловой системы; если диск или память уже нездоровы, порча может быть не одна.
- Восстановите из найденной чистой точки и только после этого дайте порядок бэкапам продолжаться. Восстанавливать «наугад» из последнего бэкапа без проверки — верный способ снова закрепить порчу в новом цикле.
- Разберитесь с первопричиной — прогоните SMART-тест диска, проверьте память (
memtest86+в оффлайне или ECC-логи, если память серверная), проверьте логи файловой системы на предмет ошибок ввода-вывода за период предполагаемой порчи.
Как в принципе проверять, что бэкап рабочий, без полного разворачивания системы — отдельная тема, разобранная в статье как проверить, что бэкап рабочий: там описаны уровни контроля от простого сравнения размера файла до валидации дампа базы данных, и эти же приёмы напрямую применимы к поиску момента заражения.
Как выстроить защиту заранее, а не разбираться постфактум
Разобраться с последствиями всегда дороже, чем не допустить ситуацию. Практический минимум, который стоит внедрить, если его ещё нет:
| Мера | Что даёт | Стоимость внедрения |
|---|---|---|
| Файловая система с чексуммами (ZFS/btrfs) на источнике | Обнаружение порчи при чтении, автопочинка при наличии избыточности | Требует переноса данных на новую ФС |
| Периодический scrub раз в месяц | Активный поиск порчи, а не ожидание случайного чтения | Низкая, но нужен I/O-бюджет на сервере |
| Логическая проверка БД перед дампом | Ловит порчу на уровне записей, а не просто байтов | Средняя, зависит от размера БД |
| Хранение хотя бы одного полного бэкапа за пределами окна инкрементов (например, годовой архив) | Даёт «страховочный» откат далеко назад | Дополнительное место на диске |
| Ежемесячная тестовая проверка восстановления | Ловит порчу до того, как она станет критичной | Требует времени администратора |
| Раздельные источники для критичных копий (не все бэкапы с одного скрипта в одно время) | Снижает шанс синхронной порчи всех копий | Организационная, не техническая |
Отдельно стоит продумать срок хранения и схему ротации так, чтобы окно между «когда порча вероятнее всего будет замечена» и «когда последняя чистая копия ротируется» было заведомо больше, чем реалистичное время обнаружения проблемы в вашей инфраструктуре. Как правильно рассчитать такое окно и выбрать схему хранения, разобрано в статье про сколько хранить бэкапы и какая нужна ротация.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли автоматически детектировать порчу прямо в момент бэкапа, без отдельных проверок?
Частично да — если бэкап-инструмент сравнивает контрольную сумму нового блока со старой и явно логирует изменение, вы получите факт «этот файл изменился», но не получите ответа, легитимное это изменение или порча. Различить может только независимая проверка — файловая система с чексуммами или логическая валидация на уровне приложения.
ZFS/btrfs полностью решают проблему сами по себе?
Они решают обнаружение и, при наличии избыточности (зеркало, raidz), автоматическое исправление порчи на уровне блоков диска. Но они не защищают от порчи, которая произошла на уровне приложения — например, баг записал неверные данные, и с точки зрения ФС это абсолютно валидная, честно записанная информация.
Стоит ли держать один полный бэкап «навечно» на случай такой ситуации?
Разумный компромисс — хранить один-два полных снимка за пределами обычного окна ротации (например, годовой архив) отдельно от рабочей цепочки инкрементов, именно как страховку от долгоживущей тихой порчи, которую не заметили вовремя.
Как часто нужно гонять scrub или проверку контрольных сумм?
Универсального числа нет — это ориентир, зависящий от объёма данных, интенсивности записи и критичности данных. На практике многие останавливаются на ежемесячном scrub как балансе между нагрузкой на диск и своевременностью обнаружения; для критичных БД логическую проверку разумно делать чаще, например перед каждым полным бэкапом.
Что делать, если чистой копии не осталось вообще ни в одном бэкапе?
Тогда данные восстановлению не подлежат в исходном виде — остаётся частичное восстановление (если порча затронула не весь файл, а конкретные записи/блоки) и работа с последствиями. Это самый весомый аргумент в пользу того, чтобы проверка целостности стояла на входе, а не обнаруживалась постфактум.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →