Данная статья — продолжение статьи «Инкрементальные бэкапы postgresql с pgbackrest — курс молодого бойца от разработчика».

В первой части мы научились делать инкрементальные бэкапы, загружать их на удаленный сервер (репозиторий с бэкапами) и откатываться на последний бэкап.
В этой статье мы научимся шифровать бэкапы, загружать их в S3-совместимое хранилище (вместо второго сервера-репозитория), восстанавливаться на чистый кластер и, наконец, восстанавливаться на определенный момент времени (point in time recovery, PITR).

Момент

Автор не претендует на роль DBA, но иногда любит настроить и посмотреть все сам.

Подготовка


Для воспроизведения данного мануала нам понадобится:

  1. Cервер с базой данных (на него мы установим pgbackrest);
  2. S3 хранилище (вы можете использовать Amazon или любое S3 совместимое, я буду использовать Amazon S3);
  3. Третий сервер. По желанию. На нем мы потренируемся разворачивать postgresql и pgbackrest с нуля, разворачивая существующий бекап из S3 хранилища (сервер сгорел, переезд и т.п.);

Подразумевается, что postgresql у вас уже установлен, а следовательно пользователь postgres тоже имеется.

Процесс установки pgbackrest я описал в предыдущей статье, но на всякий случай продублирую еще раз (напоминаю: перед установкой создайте себе sudo пользователя — я буду использовать sudo пользователя с юзернеймом pgbackrest).

Чтобы у вас было меньше проблем при воспроизведении инструкции — курсивом я прописываю где, каким пользователем и с какими правами я исполнял команду во время написания и проверки статьи.

Пойдем мы по такому пути:

Настроим postgresql и pgbackrest
Настроим шифрование бэкапов (две строчки)
Научимся делать бэкап и отправлять в S3 хранилище (пять строчек)
Сделаем бэкап
Представим, что сломали кластер, развернемся на новом сервере, подключим существующий S3 репозиторий и накатим бэкап
Посмотрим на PITR (Point In Time Recovery): восстановимся на определенный момент времени (допустим, pgbackrest забэкапил уже drop table, а нам нужно откатиться на 4 часа назад, когда табличка еще существовала)

Поехали!

Установка pgBackRest


sudo пользователь или root:

1. Скачиваем архив с pgbackrest и переносим его содержимое в папку /build:

sudo mkdir /build
sudo wget -q -O -        https://github.com/pgbackrest/pgbackrest/archive/release/2.18.tar.gz |        sudo tar zx -C /build

2. Устанавливаем необходимые для сборки зависимости:

sudo apt-get update
sudo apt-get install build-essential libssl-dev libxml2-dev libperl-dev zlib1g-dev        libpq-dev

3. Собираем pgbackrest:

cd /build/pgbackrest-release-2.18/src && sudo ./configure
sudo make -s -C /build/pgbackrest-release-2.18/src

4. Копируем исполняемый файл в директорию /usr/bin:

sudo cp /build/pgbackrest-release-2.18/src/pgbackrest /usr/bin
sudo chmod 755 /usr/bin/pgbackrest

5. pgBackRest требует наличие perl. Устанавливаем:

sudo apt-get install perl

6. Создаем директории для логов, даем им определенные права:

sudo mkdir -p -m 770 /var/log/pgbackrest
sudo chown postgres:postgres /var/log/pgbackrest
sudo mkdir -p /etc/pgbackrest
sudo mkdir -p /etc/pgbackrest/conf.d
sudo touch /etc/pgbackrest/pgbackrest.conf
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf

7. Проверяем:

pgbackrest version

Настройка postgresql и pgBackRest


sudo пользователь или root:

1. Внесем необходимые настройки в postgresql.conf (он находится в папке /etc/postgresql/11/main) для работы pgBackRest:

archive_command = 'pgbackrest --stanza=main archive-push %p' # Где main - название кластера. При установке postgres автоматически создает кластер main.
archive_mode = on
max_wal_senders = 3
wal_level = replica

2. Внесем необходимые настройки в файл конфигурации pgbackrest (/etc/pgbackrest/pgbackrest.conf):

[main]
pg1-path=/var/lib/postgresql/11/main

[global]
log-level-file=detail
repo1-cipher-pass=tr5+BXdfdoxeyUqfo6AzLTrW+c+Jfd/1QbQj2CDMMBwtB0YGH3EJajry4+Eeen6D
repo1-cipher-type=aes-256-cbc
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2 # Параметр, указывающий сколько хранить полных бэкапов. Т.е. если у вас есть два полных бэкапа и вы создаете третий, то самый старый бэкап будет удален. Можно произносить как "хранить не более двух полных бэкапов" - по аналогии с ротациями логов.
repo1-type=s3
repo1-s3-bucket=pgbackrest-part2-tutorial
repo1-s3-endpoint=s3.us-east-1.amazonaws.com
repo1-s3-region=us-east-1
repo1-s3-key=9wdS3G8U5wz7kNsFWVGck7DDZ7DtVDtbM
repo1-s3-key-secret=A9zRmW16zXKt2vVA8mmNsFWy2mUAPYHa
start-fast=y

[global:archive-push]
compress-level=3

Как вы поняли, тут мы сразу настроили шифрование и настроили поддержку S3 хранилища.

P.S. Комментариев в конфиге быть не должно.
P.S. Чтобы сгенерировать надежный ключ шифрования можно воспользоваться командой:

openssl rand -base64 48

Создание хранилища


sudo пользователь или root:

sudo mkdir -m 770 /var/lib/pgbackrest
sudo chown -R postgres /var/lib/pgbackrest/
sudo -u postgres pgbackrest --stanza=main stanza-create

Если все получилось, то в S3-бакете вы увидите файлы, которые сгенерировал pgbackrest.

Делаем бэкап:


sudo пользователь или root:

sudo -u postgres pgbackrest --log-level-console=info --stanza=main backup

pgBackRest создаст первый полный бэкап. При желании вы можете запустить команду бэкапа повторно и убедиться, что система создаст инкрементальный бэкап.

Если вы хотите повторно сделать полный бэкап, то укажите дополнительный флаг:

sudo -u postgres pgbackrest --log-level-console=info --stanza=main --type=full backup

Посмотреть список бэкапов можно с помощью команды:

sudo -u postgres pgbackrest --stanza=main info


Восстанавливаем бэкап:


sudo пользователь или root:

1. Останавливаем работающий кластер:

sudo pg_ctlcluster 11 main stop

2. Восстанавливаемся из бэкапа:

sudo -u postgres pgbackrest --stanza=main --log-level-console=info --delta --recovery-option=recovery_target=immediate restore

Чтобы восстановить базу в состояние последнего ПОЛНОГО бэкапа используйте команду без указания recovery_target:

sudo -u postgres pgbackrest --stanza=main --log-level-console=info --delta restore

Важно! После восстановления может оказаться так, что база зависнет в режиме восстановления (будут ошибки в духе ERROR: cannot execute DROP DATABASE in a read-only transaction). Решается следующим образом (нужно будет малость подождать после исполнения команды):

sudo -u postgres psql -c "select pg_wal_replay_resume()"

UPD: Как я понял, данная ошибка возникает из-за того, что после восстановления с указанием recovery_target=immediate pgbackrest ставит БД на паузу. Чтобы этого не было, нужно дополнительно указать некоторые флаги (их значение можете прочитать тут и тут):

sudo -u postgres pgbackrest --stanza=main --log-level-console=info --delta --recovery-option=recovery_target=immediate --target-action=promote --type=immediate restore

На самом деле, есть возможность восстановить конкретный бэкап по его имени. Здесь я лишь укажу ссылку на описание данной фичи в документации. Разработчики советуют использовать данный параметр с осторожностью и объясняют почему. От себя могу добавить, что я его использовал. Главное убедитесь, что после восстановления база вышла из recovery mode (select pg_is_in_recovery() должен показать «f») и на всякий случай сделайте полный бэкап после восстановления.

3. Запускаем кластер:

sudo pg_ctlcluster 11 main start

После восстановления бэкапа выполним повторный бэкап:
sudo -u postgres pgbackrest --log-level-console=info --stanza=main backup

Восстанавливаем бэкап на чистый кластер:


Давайте представим, что произошло что-то ужасное — сервер сгорел (упал, затопило, потеряли доступ). Нужно восстановить все на новый, чистый сервер. На новом сервере мы уже завели sudo пользователя, установили pgbackrest, установили postgresql и у нас появился чистый и свежий main кластер.

Все, что нам нужно сделать — настроить конфиг postgresql и pgBackRest аналогично тем, что были на прежнем сервере, перезапустить postgresql и выполнить порядок команд, аналогичный восстановлению:

sudo пользователь или root:

1. Останавливаем работающий кластер:

sudo pg_ctlcluster 11 main stop

2. Восстанавливаемся из бэкапа:

sudo -u postgres pgbackrest --stanza=main --log-level-console=info --delta --recovery-option=recovery_target=immediate  --target-action=promote --type=immediate restore

P.S. Нюансы и тонкости данной команды я описал выше. 

3. Запускаем кластер:

sudo pg_ctlcluster 11 main start

После восстановления бэкапа нам необходимо выполнить повторный бэкап:

sudo -u postgres pgbackrest --log-level-console=info --stanza=main backup

Вот, собственно, и все. Пожар потушен.

Point In Time Recovery (PITR)


Давайте представим такую ситуацию, что в 16:00 был создан бэкап, а в 18:05 мы случайно стерли важную табличку в которую за 2 часа успело попасть очень много важных данных, которые не хотелось бы потерять. PgBackRest за счет PostgreSQL предоставляет нам такую возможность: мы можем откатиться на конкретный момент времени, при условии, что у нас достаточное кол-во информации для восстановления.

Работает это так:

  1. Мы говорим, что хотим откатиться на момент времени 18:04;
  2. pgBackRest ищет последний актуальный бэкап (16:00);
  3. Восстанавливает его, но накатывает не все, а лишь то, что мы успели сделать до 18.04;

P.S. построен этот механизм на WAL логах. Почитать можно здесь.

Выполнить восстановление на конкретный момент времени (предварительно остановив кластер) можно данной командой:

sudo -u postgres pgbackrest --stanza=main --log-level-console=info --delta --type=time "--target=2020-09-06 18:27:24.561458+02" --target-action=promote restore

Важное замечание — PostgreSQL может воспроизводить WAL логи только вперед, но не назад. Что это значит на практике?

Допустим, в 16:00 был создан бэкап. В 18:05 мы случайно удалили таблицу, а в 18:10 снова был создан бэкап с уже удаленной таблицей. Когда вы попытаетесь сделать PITR — pgBackRest возьмет бэкап, который был создан в 16:00 и накатит сверху все то, что было до 18:05, а не возьмет бэкап, который был создал в 18:10 и будет откатывать все назад. Это важно понимать. Более подробно данный механизм описан здесь.

Предположим, вы сделали бэкап, в который уже попала информации о удалении таблицы. С помощью флага --set скажем, какой бэкап нужно использовать за базу (тот, в котором таблица еще была). pgBackRest возьмет данный бэкап и накатит все то, что было до удаления таблицы.

sudo -u postgres pgbackrest --stanza=main --log-level-console=info --delta --type=time --set=20200905-183838F_20200906-182612I "--target=2020-09-06 18:27:24.561458+02" --target-action=promote restore

P.S. Команду для получения списка бэкапов я показал выше.

Запустим кластер. После запуска изучим файл /var/log/postgresql/postgresql-11-main.log. В нем нас интересуют следующие строки, показывающие, что восстановление на указанный момент времени произошло успешно:

starting point-in-time recovery to 2020-09-07 11:26:52.493127+02
...
recovery stopping before commit of transaction 576, time 2020-09-07 11:27:14.584496+02
...
last completed transaction was at log time 2020-09-07 11:24:09.583761+02

Вот и все. Настоятельно советую поэкспериментировать с данным инструментом перед применением его в бою.