Бывает катастрофа, после которой обычной резервной копии мало. Кто-то выполнил ошибочный запрос и снёс важные данные в три часа дня, а последняя ночная копия снята в полночь. Восстановление из неё вернёт базу к полуночи и сотрёт пятнадцать часов нормальной работы вместе с самой ошибкой. Нужно не просто восстановиться, а откатиться ровно к моменту за секунду до рокового запроса. Это и есть восстановление на момент времени, и специализированный оператор PostgreSQL в Kubernetes умеет его из коробки. Разберём, как устроена непрерывная защита данных, как настроить архивацию и как точно отмотать базу к нужной секунде.

Почему обычной резервной копии недостаточно

Классическая резервная копия это снимок базы на момент её снятия. Между двумя снимками зияет дыра: всё, что произошло после последнего снимка и до аварии, теряется. Если копии снимаются раз в сутки, можно потерять почти сутки работы. Уменьшать этот зазор частыми полными копиями дорого и тяжело для базы.

Решение в самой природе PostgreSQL. База ведёт журнал предзаписи, куда фиксирует каждое изменение прежде, чем применить его к данным. Если непрерывно архивировать этот журнал, то, имея одну базовую копию и всю цепочку журнальных записей после неё, можно воспроизвести любое состояние базы между моментом копии и последней заархивированной записью. Это сочетание базовой копии и непрерывной архивации журнала и даёт восстановление на произвольный момент времени. Оператор PostgreSQL в Kubernetes изначально поддерживает горячее резервное копирование через непрерывную физическую копию и архивацию журнала: база при этом всё время работает, простой не нужен.

Из чего складывается непрерывная защита

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

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

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

Как настроить архивацию в дешёвое хранилище

Хранят копии и журнал обычно в объектном хранилище, дешёвом и ёмком. В описании кластера задают путь назначения, учётные данные и политику хранения.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-cluster
spec:
  instances: 3
  backup:
    barmanObjectStore:
      destinationPath: "s3://my-backups/pg-cluster"
      s3Credentials:
        accessKeyId:
          name: backup-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: backup-creds
          key: ACCESS_SECRET_KEY
    retentionPolicy: "30d"

Здесь оператор будет непрерывно слать журнал и хранить копии в объектном хранилище, удерживая их тридцать дней. Для работы инструментов архивации нужен образ базы с включёнными утилитами облачной архивации, и сообщество предоставляет готовый образ, где они уже есть.

Почему таймаут архивации определяет потери при аварии

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

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

Как часто снимать базовые копии и почему это влияет на время восстановления

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

apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: weekly-backup
spec:
  schedule: "0 0 0 * * 0"   # еженедельно в полночь воскресенья
  backupOwnerReference: self
  cluster:
    name: pg-cluster

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

Как именно отмотать базу к нужной секунде

Теперь главное действие ради всего этого. Восстановление в операторе устроено как создание нового кластера из существующей копии с последующим воспроизведением журнала. Оба компонента, и базовая копия, и журнал, подтягиваются из хранилища. Восстановление умеет как доводить базу до последней доступной журнальной записи, так и останавливаться на заданном моменте времени.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-restored
spec:
  instances: 3
  bootstrap:
    recovery:
      source: pg-cluster
      recoveryTarget:
        targetTime: "2026-05-29 14:59:55+00"
  externalClusters:
    - name: pg-cluster
      barmanObjectStore:
        destinationPath: "s3://my-backups/pg-cluster"
        s3Credentials:
          accessKeyId:
            name: backup-creds
            key: ACCESS_KEY_ID
          secretAccessKey:
            name: backup-creds
            key: ACCESS_SECRET_KEY

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

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

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

  externalClusters:
    - name: pg-cluster
      barmanObjectStore:
        destinationPath: "s3://my-backups/pg-cluster"
        wal:
          maxParallel: 8   # тянуть до 8 журнальных файлов разом

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

За какими параметрами следить и о чём помнить

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

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

Какой стратегии придерживаться

Непрерывная защита PostgreSQL с оператором в Kubernetes решает задачу, неподъёмную для обычных копий: возврат базы к любой секунде, а не к редким точкам снимков. Разумная схема складывается из нескольких решений. Настроить непрерывную архивацию журнала в дешёвое объектное хранилище со сжатием и шифрованием. Снимать базовую копию по расписанию, для большинства случаев достаточно еженедельной. Помнить, что таймаут архивации задаёт окно возможных потерь, и доверять разумному значению по умолчанию. Для восстановления создавать новый кластер с указанием целевого времени, никогда не перезаписывая исходный. Включать параллельное воспроизведение журнала ради скорости. И, что важнее всего, регулярно репетировать восстановление, замеряя его время, чтобы знать свой реальный показатель возврата к жизни.

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