Целостность без ZFS: как ловить порчу файлов на обычной ext4
Дамп базы данных лежит на диске полгода, бэкап-скрипт каждую ночь честно отчитывается «OK», а когда доходит до реального восстановления — файл разворачивается наполовину, а pg_restore падает с ошибкой о повреждённых данных. Диск не выдал ни одной ошибки чтения, мониторинг молчал. Если сервер стоит на ext4 (самой распространённой файловой системе на Linux-серверах), у него просто нет встроенного механизма, который заметил бы, что несколько байт в файле однажды стали не теми, что были записаны. Разберём, как закрыть эту дыру самостоятельно — без миграции на другую файловую систему.
Содержание
- Почему это особенно больно для баз данных
- Что видит и чего не видит ext4
- Идея вручную: контрольная сумма, хранимая отдельно от файла
- Периодическая сверка и что делать с расхождением
- Инкрементальная проверка: не пересчитывать всё заново каждый раз
- Инструменты, которые автоматизируют это за вас
- Встроить проверку в конвейер бэкапа, а не держать отдельно
Почему это особенно больно для баз данных
Тихая порча данных (silent data corruption) бьёт по любым файлам, но дампы и архивы баз данных — едва ли не худший случай. Три особенности превращают редкую проблему в реальный риск:
- Это «холодные» файлы. Дамп создаётся ночью, заливается в хранилище бэкапов и в норме не открывается месяцами — именно долгое нетронутое лежание даёт деградации носителя накопиться незамеченной.
- Проверка «на глаз» не работает. Бинарный дамп PostgreSQL или сжатый
mysqldump.gzс испорченным байтом в середине либо не распаковывается вообще, либо распаковывается частично и создаёт ложное чувство, что бэкап цел. - Обнаруживается в худший момент. Не при плановой проверке, а тогда, когда прод уже упал и восстановление нужно прямо сейчас, под давлением.
Если вы проверяете «работоспособность бэкапа» только по коду возврата pg_dump или mysqldump, вы проверяете, что дамп *создался*, а не что он *останется читаемым* через три месяца хранения. Разница между этими гарантиями — ровно то, о чём эта статья.
Что видит и чего не видит ext4
У классических файловых систем вроде ext4 или XFS есть контроль целостности, но он касается только метаданных — самой структуры файловой системы: журнала транзакций, таблиц inode, каталогов. Если после сбоя питания журнал не сходится, fsck это заметит и починит. Но у содержимого обычного файла — тех самых байт дампа базы — никакой контрольной суммы на уровне файловой системы нет. Когда диск возвращает сектор, ext4 верит ему на слово: раз чтение завершилось без ошибки ввода-вывода, значит, данные корректны.
Проблема в том, что диск может отдать не те данные, которые записывал, и не сообщить об ошибке. Источников расхождения несколько: постепенная деградация магнитного слоя на HDD или заряда ячеек NAND на SSD, единичный сбой прошивки контроллера, неисправность SATA/SAS-кабеля, редкий сбой бита в памяти без ECC-коррекции. Ни один из случаев не редкость сам по себе — вероятность растёт с объёмом данных и временем хранения, а не проявляется на разовом тесте диска.
Есть отдельный класс файловых систем, спроектированных иначе: они хранят контрольную сумму для каждого блока данных отдельно от самого блока (обычно в дереве метаданных родительского уровня) и пересчитывают её при каждом чтении. Если сумма не совпала — файловая система знает о повреждении в момент чтения, а не когда кто-то случайно наткнётся на битый файл. При наличии избыточности (зеркало, несколько копий) такие системы способны восстановить блок автоматически, без участия администратора — контроль встроен в путь чтения, а не пристроен снаружи. Сравнение таких систем с ext4 и XFS — в статье ext4, XFS, ZFS, btrfs: как выбрать. Здесь же речь о другом: что делать, если переезд сейчас не вариант — сервер уже развёрнут, миграция дорога, а данные всё равно нужно защитить.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверИдея вручную: контрольная сумма, хранимая отдельно от файла
Раз файловая система не считает контрольные суммы содержимого сама, эту работу можно и нужно сделать руками. Логика простая: рассчитать хеш файла в момент, когда вы точно уверены в его корректности (сразу после создания дампа, до заливки в архив), сохранить этот хеш отдельно от самого файла, а затем периодически пересчитывать заново и сравнивать.
Ключевое слово здесь — «отдельно». Если хранить сумму в том же каталоге, на том же диске, что и файл, вы защищаетесь только от одного класса проблем (случайное изменение содержимого), но не от другого — деградации самого носителя, которая с равной вероятностью исказит и файл, и лежащую рядом сумму. Разумные варианты хранения:
- отдельный диск или раздел на том же сервере (минимальная защита, но лучше, чем ничего);
- отдельный сервер, куда льются сами бэкапы, но в отдельном пути;
- объектное хранилище (S3-совместимое) — манифест весит килобайты, а не гигабайты, хранить его там дёшево;
- git-репозиторий на отдельном сервере — заодно получаете историю: видно, когда именно изменилась сумма файла.
Команда для одного файла:
sha256sum /backup/pg/2026-08-30/full.dump.gz > /backup/pg/2026-08-30/full.dump.gz.sha256
Для целого каталога с бэкапами — один манифест на весь набор файлов за день:
find /backup/pg/2026-08-30 -type f -name '*.dump.gz' -print0 \
| sort -z \
| xargs -0 sha256sum > /var/lib/db-integrity/manifest-2026-08-30.sha256
Манифест — обычный текст: хеш и путь через два пробела на строку. Его удобно сразу скопировать туда, где хранятся эталоны:
scp /var/lib/db-integrity/manifest-2026-08-30.sha256 \
integrity@backup-host:/srv/integrity-manifests/pg/
sha256sum — разумный выбор по умолчанию: устойчив к случайным искажениям, доступен из коробки почти на любом дистрибутиве и не требователен к CPU на объёмах, типичных для дампов базы. Более старый md5sum тоже технически справится с задачей обнаружения случайной порчи (речь не о защите от намеренной подделки, где MD5 слаб), но раз вы пишете инфраструктуру заново — нет причины закладываться на более слабый алгоритм.
Периодическая сверка и что делать с расхождением
Контрольная сумма без повторной проверки — просто зафиксированный факт на дату расчёта. Смысл появляется, только когда сверка происходит регулярно и по расписанию. Проверка одного манифеста:
cd /backup/pg/2026-08-30 && sha256sum -c /var/lib/db-integrity/manifest-2026-08-30.sha256
Команда построчно сверяет текущее содержимое каждого файла с сохранённым хешем и печатает OK или FAILED. Для автоматизации важен не сырой вывод, а отфильтрованный:
sha256sum -c /var/lib/db-integrity/manifest-2026-08-30.sha256 2>&1 | grep -v ': OK$'
Если строка вида full.dump.gz: FAILED появилась для файла, который заведомо не редактировался после создания (а бэкапы дампов обычно и не редактируются — это одноразовые снапшоты, а не живые файлы), это прямой сигнал: содержимое изменилось без вашего участия, то есть один из вариантов тихой порчи.
Когда расхождение найдено, порядок действий такой: не удалять файл сразу (даже частично битый дамп иногда можно распаковать до места повреждения и вытащить хотя бы часть данных); проверить, есть ли более старая исправная копия — предыдущий цикл бэкапа, реплика, снапшот на другом узле хранения; проверить SMART и логи диска (smartctl -a /dev/sdX, dmesg | grep -i -E 'ata|scsi') — единичная порча иногда предвестник начинающейся деградации носителя, стоит успеть снять свежую копию, пока диск ещё читается штатно; и, если сама база данных жива и цела, просто пересоздать бэкап заново — испорчен дамп на диске, а не источник данных.
Отдельно стоит различать два похожих по симптому, но разных по смыслу случая: файл, который *осознанно* перезаписывается (текущий лог, рабочая директория), и файл, который *не должен* меняться после создания (архивный дамп). Схема с фиксированной суммой годится именно для второго случая — для «холодных» данных. Живые, часто меняющиеся файлы такой подход будет считать «испорченными» при каждом легитимном изменении; для них нужна другая стратегия — версионированные снапшоты бэкапа, а не статичный манифест.
Инкрементальная проверка: не пересчитывать всё заново каждый раз
Полный пересчёт sha256sum по всем файлам хранилища упирается в скорость чтения с диска и на большом архиве может занимать заметное время. Гонять его ежедневно по всему архиву целиком часто избыточно: риск порчи накапливается со временем, а не возникает мгновенно между вчера и сегодня.
Практичный компромисс — комбинировать полную и частичную проверку: новые файлы получают сумму сразу при создании (это дёшево — данные и так читаются с диска в момент записи бэкапа), а остальной архив проверяется по кругу небольшими срезами, размазывая нагрузку на дни или недели. Простой вариант — проверять по одному каталогу-дате за прогон:
#!/bin/bash
# /usr/local/bin/db-integrity-rotate-check.sh
# Раз в сутки проверяет один "срез" по кругу -- весь архив проходит проверку за N дней
BASE=/backup/pg
MANIFEST_DIR=/var/lib/db-integrity
LOG=/var/log/db-integrity-check.log
DIRS=($(find "$BASE" -maxdepth 1 -mindepth 1 -type d | sort))
IDX=$(( $(date +%j) % ${#DIRS[@]} ))
TARGET_DIR="${DIRS[$IDX]}"
TARGET=$(basename "$TARGET_DIR")
MANIFEST="$MANIFEST_DIR/manifest-$TARGET.sha256"
[ -f "$MANIFEST" ] || { echo "$(date -Is) нет манифеста для $TARGET" >> "$LOG"; exit 0; }
RESULT=$(cd "$TARGET_DIR" && sha256sum -c "$MANIFEST" 2>&1 | grep -v ': OK$')
echo "$(date -Is) проверка среза $TARGET" >> "$LOG"
if [ -n "$RESULT" ]; then
echo "$RESULT" >> "$LOG"
echo "$RESULT" | mail -s "ALERT: db integrity FAILED for $TARGET" admin@example.com
fi
Каждый файл проверяется не ежедневно, а раз в цикл — но по кругу проходит весь архив, а не только свежая часть, и пиковой нагрузки от полной сверки не возникает.
Инструменты, которые автоматизируют это за вас
Связка find + sha256sum + cron рабочая для небольшого набора файлов, но у неё есть предсказуемые ограничения: нет истории изменений, нет удобного diff «что именно поменялось», нет инкрементальности из коробки. Если объём данных растёт, есть смысл посмотреть на готовые инструменты рядом:
| Инструмент | Что делает | Когда уместен |
|---|---|---|
sha256sum/shasum + find + cron | Базовый расчёт и сверка сумм | Небольшой набор файлов, полный контроль над логикой |
hashdeep / md5deep | Считает суммы рекурсивно, хранит базу, сравнивает два дерева каталогов и печатает только расхождения | Средние объёмы, нужен готовый diff без своего парсера |
| AIDE | Полноценная база с суммами и метаданными файлов, изначально для контроля вторжений, но применима и к «холодным» данным | Заодно нужны права доступа, владелец, время изменения |
par2 (Parchive) | Не просто обнаруживает повреждение, а создаёт избыточные данные для восстановления повреждённых участков без исходной копии | Архивы, которые критично уметь восстановить без копии в другом месте |
rclone check / restic check / borg check | Встроенная проверка целостности, если бэкапы уже хранятся через эти инструменты | Один из них и так используется для бэкапов |
Отдельно стоит сказать про par2: в отличие от остальных вариантов в таблице, он не просто *обнаруживает* порчу, а может *восстановить* повреждённые байты без обращения к другой копии файла — за счёт заранее посчитанной избыточности (похоже по идее на RAID, но на уровне отдельного файла), ценой места на диске под recovery-данные. Для по-настоящему критичных архивов, которые нельзя быстро перезалить из другого места, это разумное дополнение к обычным суммам, а не замена им.
Если бэкапы БД уже льются через специализированный инструмент, а не голым pg_dump в файл, — возможно, встроенная проверка целостности уже есть и городить отдельный слой не нужно. Общие принципы автоматизации самого процесса бэкапирования — в статье автоматизация резервного копирования баз данных.
Встроить проверку в конвейер бэкапа, а не держать отдельно
Самая частая ошибка — построить проверку целостности как отдельный, никак не связанный с бэкапами процесс, который живёт своей жизнью и о котором через полгода забывают. Правильнее встроить расчёт суммы в тот же скрипт, что делает дамп, — так манифест появляется одновременно с файлом, а не «когда-нибудь потом отдельным cron-джобом»:
#!/bin/bash
# /usr/local/bin/pg-dump-with-checksum.sh
set -euo pipefail
DATE=$(date +%F)
DUMP_DIR="/backup/pg/$DATE"
MANIFEST="/var/lib/db-integrity/manifest-$DATE.sha256"
mkdir -p "$DUMP_DIR"
pg_dump -Fc mydb | gzip > "$DUMP_DIR/full.dump.gz"
# сумма считается сразу, пока файл гарантированно свежий и корректный
sha256sum "$DUMP_DIR/full.dump.gz" > "$MANIFEST"
# сразу копируем манифест туда, куда у этого сервера нет прав на запись
# задним числом -- это защищает от подмены суммы вместе с файлом
scp "$MANIFEST" integrity@backup-host:/srv/integrity-manifests/pg/
# пингуем внешний мониторинг задач -- узнать не только о провале суммы,
# но и о том, что сам скрипт вообще отработал
curl -fsS -m 10 --retry 3 https://hc-ping.com/YOUR-UUID-HERE >/dev/null || true
Здесь важны две детали. Во-первых, манифест копируется на другой сервер сразу же, а не позже отдельным шагом — иначе есть риск, что бэкап успеет испортиться до того, как сумма для него будет надёжно сохранена в стороннем месте. Во-вторых, ping во внешний сервис мониторинга задач в конце — это отдельная страховка: она отвечает не на вопрос «сумма совпала?», а на вопрос «скрипт вообще дошёл до конца?». Если cron перестал срабатывать — вы должны узнать об этом, а не выяснить постфактум, что бэкапов за неделю просто не было. Настройка такого мониторинга — в статье мониторинг cron-задач через healthchecks.io.
Отдельно стоит регулярно не просто сверять суммы, а физически пробовать восстановить бэкап на тестовом экземпляре — сумма подтверждает, что байты файла не изменились, но не подтверждает, что дамп корректно разворачивается в рабочую базу. Это смежная практика, которой посвящена статья как проверить, что бэкап действительно рабочий.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если сумма не совпала, это точно порча диска, а не что-то другое?
Не обязательно. Расхождение означает только то, что содержимое изменилось с момента расчёта эталона — причиной может быть и деградация носителя, и человеческая ошибка, и незавершённая операция копирования. Первый шаг при любом FAILED — не паника, а проверка mtime файла, журналов плановых операций и SMART диска, прежде чем делать выводы о причине.
Можно ли хранить сумму в расширенном атрибуте (xattr) файла вместо отдельного манифеста?
Технически можно (setfattr -n user.checksum.sha256 -v "$(sha256sum file | cut -d' ' -f1)" file), но у этого есть слабое место именно для защиты от порчи диска: атрибут физически хранится на том же носителе, что и файл, — при серьёзной деградации сектора могут пострадать оба. Используйте xattr вместе с внешним манифестом, а не вместо него.
Насколько часто нужно пересчитывать суммы для архива бэкапов?
Единого числа нет — ориентируйтесь на критичность данных. Для дампов, которые хранятся месяцами, разумный ритм — от раза в неделю до раза в месяц на срез, при этом свежие бэкапы стоит проверять сразу после создания (сумма считается на лету при записи, без лишнего чтения с диска). Чем дольше файл лежит нетронутым, тем больше смысла в регулярной, а не разовой сверке.
Стоит ли переходить на файловую систему со встроенными суммами вместо всей этой ручной возни?
Если данные критичны и объём проекта позволяет пересобрать инфраструктуру хранения — весомый аргумент, защита работает прозрачно на каждом чтении. Если сервер уже развёрнут на ext4, а переезд дорог или временно недоступен, ручная схема — не «костыль похуже», а рабочая и достаточная альтернатива для большинства сценариев.
par2 может заменить обычный бэкап в другом месте?
Нет. par2 защищает от порчи конкретных байт внутри файла, который физически остаётся на том же диске, но не от полной потери диска, удаления или шифровальщика. Это дополнение к географически разнесённым копиям, а не их замена.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →