MAATRIX / Блог / Дамп снимался четыре часа, и данные в нём из разных моментов времени

Дамп снимался четыре часа, и данные в нём из разных моментов времени

MAATRIX

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

Дамп — это процесс во времени, а не мгновенный снимок

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

Если база в это время не принимает запись — например, это staging-окружение или ночное окно техобслуживания, где приложение остановлено, — то без разницы, сколько идёт дамп: данные не меняются, и в файле неважно, читалась ли таблица orders в 02:00, а order_items в 05:30 — они всё равно отражают одно и то же неизменное состояние.

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

Как рвётся целостность: конкретный пример

Возьмём типичный интернет-магазин со схемой ordersorder_itemspayments. Дамп идёт по алфавиту или по порядку в схеме:

  1. 02:00:15 — начинается выгрузка таблицы orders. К этому моменту в базе 500 000 заказов.
  2. 02:00:15–04:30:00 — идёт дамп нескольких крупных таблиц: products, users, логов. Всё это время пользователи оформляют заказы, приложение пишет в orders и следом в order_items в рамках обычных транзакций.
  3. 04:31:40 — начинается выгрузка order_items. К этому моменту в базе уже 500 830 заказов — на 830 больше, чем было, когда снимался дамп orders.

Результат в файле дампа: 830 заказов, у которых есть строки в order_items, но нет самой записи в orders — потому что orders дампилась раньше, чем эти заказы появились. При восстановлении на сервере с включённой проверкой внешних ключей это или ошибка вставки, или (если проверки отключены на время загрузки, как часто и делают ради скорости) — тихо осевшие «сироты»: строки order_items, ссылающиеся в никуда. Отчёт «сумма по заказам» и отчёт «сумма по позициям заказов» после такого восстановления не совпадут, и разбираться, почему, придётся уже постфактум, без контекста, что дамп был несогласованным.

Направление проблемы может быть и обратным: если бы order_items дампилась раньше orders, получили бы заказы без единой позиции — тоже нарушение целостности, просто в другую сторону. Суть одна: части дампа сделаны с разницей в часы, а не в миллисекунды, и всё, что изменилось в базе за это время, размазано по файлу непредсказуемым образом.

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

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

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

Что даёт согласованный снимок и как его получить

У популярных СУБД есть штатный механизм именно для этой задачи — снимок на основе MVCC (multiversion concurrency control): в момент старта дампа фиксируется «версия» базы, и все последующие чтения, сколько бы часов они ни шли, видят базу такой, какой она была в этот зафиксированный момент, — новые записи и изменения от параллельных транзакций дампу просто не видны.

PostgreSQL. pg_dump по умолчанию именно так и работает: он открывает одну транзакцию с уровнем изоляции REPEATABLE READ, получает единый снимок в начале и весь дамп идёт в её рамках — специально ничего включать не нужно. Нюанс — параллельный режим:

pg_dump -d mydb -j 4 -F d -f /backup/mydb_dump

Флаг -j запускает несколько процессов-воркеров параллельно, и чтобы все они видели один и тот же снимок, pg_dump сам синхронизирует их через pg_export_snapshot(). Это работает штатно с обычным pg_dump, но если кто-то собирает «свой» параллельный дамп руками — несколькими независимыми сессиями psql с \copy по разным таблицам без явной синхронизации снимков — согласованность теряется точно так же, как в примере выше.

MySQL/MariaDB. Тут по умолчанию защиты нет — её нужно включать явно:

mysqldump --single-transaction --routines --triggers \
  -h localhost -u backup_user -p mydb > mydb_dump.sql

--single-transaction открывает одну транзакцию с REPEATABLE READ на InnoDB и весь дамп идёт в её снимке — без блокировки таблиц на запись, что важно для многочасового дампа на живом проде. Без этого флага mysqldump по умолчанию берёт LOCK TABLES ... READ на дампимые таблицы — это тоже даёт согласованность, но ценой блокировки записи на всё время дампа, что для четырёхчасового процесса на проде обычно неприемлемо. А если вдобавок используется --skip-lock-tables (частый выбор, чтобы не блокировать прод) без --single-transaction — согласованности не остаётся вообще никакой, и получаем ровно сценарий из примера выше.

Отдельно стоит MyISAM: у него нет транзакций, поэтому --single-transaction на такие таблицы не действует — единственная защита для них это блокировка. Если в базе есть смесь InnoDB и MyISAM, чистого решения через один флаг нет — это стоит иметь в виду при выборе движка для новых таблиц.

Где согласованность ломается даже при «правильном» флаге

--single-transaction не панацея на все случаи:

  • DDL во время дампа. Если пока идёт дамп кто-то выполнит ALTER TABLE на дампимой таблице, транзакция дампа в MySQL может быть прервана с ошибкой (ERROR 1213 или потерей согласованности, в зависимости от версии и настроек). На проде с активными миграциями это реальный риск для многочасового дампа — стоит на время дампа исключить деплои со схемными изменениями.
  • Long-running-транзакция и её побочные эффекты. Снимок на несколько часов — это фактически многочасовая открытая транзакция. В PostgreSQL это мешает автовакууму чистить мёртвые строки, которые появились после старта снимка (потому что транзакция дампа теоретически ещё может их прочитать) — на активно изменяемой базе это может ощутимо раздуть таблицы за время дампа. В MySQL с InnoDB долгая транзакция аналогично держит старые версии строк в undo-логе, не давая их вычистить — при большом потоке записи undo-пространство может заметно вырасти.
  • Дамп с реплики без учёта задержки. Частая практика — снимать дамп не с мастера, а с реплики, чтобы не грузить прод. Это разумно, но если во время дампа на реплике возникает лаг репликации (например, из-за долгих запросов самого дампа, конкурирующих за I/O), состояние реплики может «протухнуть» относительно ожидаемого — с точки зрения согласованности это не проблема (снимок остаётся внутренне цельным), а с точки зрения актуальности данных в бэкапе — да, стоит проверять.
  • Смешанные скрипты дампа. Самая частая причина реальных инцидентов — не баг СУБД, а самописный скрипт, который дампит таблицы по одной через отдельные вызовы mysqldump/pg_dump (например, чтобы параллелить или чтобы иметь возможность перезапустить только упавшую таблицу). Каждый такой отдельный вызов — это своя транзакция и свой снимок, и общего согласованного среза между вызовами уже нет, даже если у каждого отдельного вызова стоит --single-transaction.

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

Не полагайтесь на факт «дамп прошёл без ошибок» — он ничего не говорит о согласованности. Практические проверки:

  • Откройте начало файла дампа mysqldump и убедитесь, что там есть SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ и START TRANSACTION — это признак, что --single-transaction реально сработал, а не был проигнорирован (например, из-за конфликта с другим флагом или движка, который транзакции не поддерживает):
grep -m1 -A1 "START TRANSACTION" mydb_dump.sql
  • После восстановления на тестовом сервере запустите проверку ссылочной целостности напрямую — быстрее, чем ждать, пока это всплывёт в отчётах:
-- PostgreSQL / MySQL, пример для order_items → orders
SELECT oi.order_id
FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL
LIMIT 20;

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

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

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

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

ПодходКак даёт согласованностьКогда уместен
mysqldump --single-transaction / pg_dumpMVCC-снимок в одной транзакцииБазы до нескольких десятков ГБ, нужен переносимый SQL/дамп
Percona XtraBackup / физический бэкап InnoDBКопирует файлы + redo-лог, применяет его при подготовке (--prepare) до консистентного состоянияБольшие базы MySQL/MariaDB, нужен быстрый restore без пересборки индексов
pgBackRest / физический бэкап PostgreSQLКопирует данные + WAL, восстанавливает журнал до момента снимкаБольшие базы PostgreSQL, нужны инкрементальные бэкапы и PITR
Снимок ФС/хранилища (LVM, ZFS, снапшот диска у провайдера)Атомарный снимок блочного устройства на уровне ОСКогда важна скорость снятия бэкапа и есть поддержка снапшотов на инфраструктуре

Физические инструменты вроде Percona XtraBackup или pgBackRest устроены так, что читают данные и журнал транзакций, а при подготовке бэкапа докатывают журнал до единой точки — итоговая копия согласована так же, как согласована сама база при штатном восстановлении после сбоя, а не как последовательность независимых чтений таблиц. Про установку и грабли конкретно с XtraBackup — в статье как установить и настроить Percona XtraBackup на VPS, а сравнение с pgBackRest — в материале pgBackRest или Percona XtraBackup: что выгоднее и когда.

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

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

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

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

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

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

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

Дамп шёл без единой ошибки — значит, он согласован?

Нет. Отсутствие ошибок означает только, что чтение данных прошло технически успешно. Согласованность — отдельное свойство, которое обеспечивается механизмом снимка (транзакцией/MVCC), а не проверяется автоматически при экспорте. Проверяйте явно: наличие START TRANSACTION в начале файла для mysqldump, ссылочную целостность после восстановления.

Если база маленькая (пара гигабайт) и дамп идёт пять минут, стоит ли беспокоиться?

Риск ниже, но не нулевой — если за эти пять минут через базу прошёл заметный поток записи (например, обработка платежей), несогласованность возможна и в таком масштабе. --single-transaction/штатный pg_dump ничего не стоят по накладным расходам, поэтому включать их стоит всегда, а не только «для больших баз».

--single-transaction замедляет дамп?

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

Можно ли получить согласованный дамп, если часть таблиц в MyISAM, а часть в InnoDB?

Полностью транзакционно — нет, потому что MyISAM не поддерживает транзакции и --single-transaction на такие таблицы не действует. Практический выход — либо перевести таблицы в InnoDB (если это возможно по логике приложения), либо принять, что для MyISAM-части нужна кратковременная блокировка на запись именно этих таблиц, пока идёт их дамп.

Как понять, что конкретно у нас в проде используется несогласованный подход к дампу?

Откройте скрипт или cron-задачу, которая запускает бэкап, и проверьте флаги вызова mysqldump/pg_dump, а также — не является ли это несколькими отдельными вызовами по таблицам вместо одного вызова на всю базу. Если сомневаетесь — сделайте тестовое восстановление на отдельном сервере и прогоните проверку ссылочной целостности, как описано выше.

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

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

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