Перейти к содержанию

Резервные копии и окно восстановления

История цели — это цепочка: полные резервные копии по расписанию, а между ними непрерывный поток изменений (WAL в Postgres, binlog в MySQL). Можно восстановить любую секунду, покрытую полной резервной копией и изменениями после неё. Этот промежуток и есть окно восстановления (point-in-time window).

gantt
  dateFormat  YYYY-MM-DD HH:mm
  axisFormat  %a %d
  section Full backups
  daily full      :milestone, 2026-10-01 02:07, 0d
  daily full      :milestone, 2026-10-02 02:07, 0d
  daily full      :milestone, 2026-10-03 02:07, 0d
  daily full      :milestone, 2026-10-04 02:07, 0d
  section Change stream
  WAL / binlog segments :active, 2026-10-01 02:07, 2026-10-04 18:00

Полные резервные копии

Движок Режим Инструмент Восстановление
Postgres 15–17 physical (по умолчанию) pg_basebackup -Ft -X fetch по протоколу репликации базовая копия + применение WAL до секунды
Postgres 15–17 logical pg_dump -Fc, один объект на базу данных; исключение таблиц и выборочный экспорт pg_restore, плюс применение WAL через физическую цепочку, если она есть
MySQL 8.4 logical mysqldump --single-transaction --source-data=2 (процедуры, события, триггеры) загрузка + применение binlog до секунды

Каждая резервная копия записывает опись: базы данных, таблицы, столбцы, индексы, оценки числа строк и размеры, а также версию миграции, если есть таблица миграций. Именно их сравнивают консоль и tenax diff.

Поток изменений

У каждой цели с восстановлением на момент времени есть один streamer:

  • Postgres: собственный приёмник WAL в Tenax на физическом слоте репликации (ADR 0005). Слот гарантирует, что WAL не теряется, пока streamer перезапускается.
  • MySQL: mysqlbinlog --read-from-remote-server --raw --stop-never с позиции binlog последней полной копии.

Сегменты загружаются по мере завершения, а сегмент, который ещё пишется, загружается каждые partialInterval (по умолчанию 60 с), так что последняя минута тоже восстанавливаема. Каждый сегмент содержит сводку: коммиты, вставки, обновления и удаления по таблицам, DDL и, по желанию, маскированные предпросмотры строк.

Если поток прерывается (слот потерян, binlog удалён), Tenax помечает цепочку как разорванную, отправляет оповещение (TenaxStreamChainBroken), начинает новую цепочку и сразу делает полную резервную копию (причина chain-restart). Старое окно остаётся восстанавливаемым до точки разрыва.

Хранение

Полные резервные копии хранятся по уровням; сегменты — лишь настолько далеко назад, насколько нужно окну; сборка мусора удаляет всё остальное, но никогда — закреплённое.

Пресет Окно Ежедневные Еженедельные Ежемесячные Ежегодные
prod 7 дней 14 8 12 5
dev 3 дня 7 4 3 0
critical 14 дней 30 12 24 7

Первая зафиксированная полная копия дня становится ежедневной копией этого дня; первая за неделю ISO — еженедельной; и так далее. Одна резервная копия может относиться к нескольким уровням; ничего не копируется. Закрепления (tenax pin <id> --reason …) удерживают резервную копию независимо от уровней. GC записывает свой план в бакет до удаления, удаляет манифесты раньше данных и через сутки убирает осиротевшие и незавершённые загрузки.

Формат хранения

<store prefix>/<org>/<project>/<namespace>/<target>/
  full/<backup-id>/manifest.json            # written last: the commit marker
  full/<backup-id>/data/<database>.zst.age  # dumps or the base backup tar
  full/<backup-id>/inventory.json.zst.age   # also readable with the catalog key
  full/<backup-id>/tags.json                # tiers, pins, verification results
  stream/…/<segment>.zst.age                # WAL / binlog segments and their summaries
  stream/position.json                      # the streamer's confirmed position
  • Сжатие: zstd с уровнем из политики, один раз, до шифрования; включено по умолчанию (ADR 0013). compression.algorithm: none хранит данные как есть.
  • Шифрование: каждый объект шифруется age на клиенте для получателей хранилища. Бакет никогда не видит открытый текст.
  • Целостность: размер и SHA-256 каждого объекта записаны в манифесте и проверяются при каждом чтении.
  • Фиксация: резервная копия существует только после записи manifest.json. Сбой до этого оставляет сирот, которых удаляет GC, и никогда — копию, выглядящую полной.

Управление нагрузкой

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