Мгновенные снепшоты postgres на tablespace и btrfs
Yet Another Postrges on BTRFS
Для работы бывает полезно иметь несколько копий одной реальной базы для экспериментов, фикстур или просто тестовых приложений. База растет и время копирования через разворачивание дампа или с помощью шаблона также возрастает до утомительных величин. Для решения этого кейса уже описаны варианты использования файловой системы с поддержкой CoW — Btrfs. В интернете находил такие инструкции, они сводятся к тому, что делается снепшот всего сервера. И для работы второго «скопированного» нужно перегенерировать pid и сменить порт для предотвращения конфликтов. Этот способ довольно универсальный относительно конфигурации баз на сервере, но кажется имеет ограничение для неопределенного кол-ва параллельных снепшотов серверов.
В этой статье предлагаю свой вариант реализации снепшотов на одном экземпляре сервера postgres и одной базы, на произвольное кол-во копий.
Инструкция linux only, про поддержку CoW файловых систем на Windows не в курсе.
I. Подготовка
1. Нам понадобится специальный раздел для Btrfs, где будут хранится базы и снепштоы.
Это может быть отдельное устройство (подключенное аппаратно либо в менеджере виртуальных устройств)
Это может быть чистый раздел на текущем устройстве (настроить разделы via cfdisk)
Это даже может быть loop device на основе файла (См. losetup)
# Пусть устройство называется
/dev/sdb
2. Отформатируем устройство в Btrfs.
sudo apt-get update && apt-get install -y btrfs-progs
sudo mkfs.btrfs -L btrfs_disk /dev/sdb
3. Примонтируем устройство в систему.
# Для примера возьмем каталог /mnt/btrfs
sudo mkdir -p /mnt/btrfs && sudo mount /dev/sdb /mnt/btrfs
— (Optional) Можно сделать отдельный каталог только для tablespace.
sudo mkdir /mnt/btrfs/pg
4. Установить postgres сервер, если не установлен.(Инструкцию можно найти на оф. сайте)
II. Для создания начальной базы $ORIGIN (той которую будем снепшотить):
1. Создать подраздел btrfs
sudo btrfs subvolume create /mnt/btrfs/pg/$ORIGIN
2. Postgres нужны права на этот каталог
sudo chown postgres:postgres /mnt/btrfs/pg/$ORIGIN
3. Теперь создадим tablespace в этом каталоге. Tablespace может быть назван также как и директория.
psql -c "CREATE TABLESPACE $ORIGIN LOCATION '/mnt/btrfs/pg/$ORIGIN'"
4. Создадим пустую БД в tablespace.
psql -c "CREATE DATABASE $ORIGIN TABLESPACE $ORIGIN;"
6. Теперь базу можно заполнить данными — из дампа или вашего приложения.
pgbench -i -s 10 $ORIGIN
# Будет отображено время затраченное на генерацию
# У pgbench есть и другие параметры, scale только влияет на кол-во записей и
# размер базы. (У меня -s 10 дало базу 92Мб)
III. Чтобы сделать снепшот базы $ORIGIN в базу $SNAPSHOT, понадобится:
1. Создать простой каталог в pg_btrfs для «каркаса» базы $SNAPSHOT. (Пока это будет БД-пустышка, для того чтобы узнать какой OID назначит сервер)
sudo mkdir /mnt/btrfs/pg/$SNAPSHOT
2. Не забудем дать права на этот каталог пользователю postgres.
sudo chown postgres:postgres /mnt/btrfs/pg/$SNAPHOT
3. Создадим в этом каталоге tablespace.
psql -c "CREATE TABLESPACE $SNAPSHOT LOCATION '/mnt/btrfs/pg/$SNAPSHOT'"
4. Создадим пустую БД в этом tablespace.
psql -c "CREATE DATABASE $SNAPSHOT TABLESPACE $SNAPSHOT;"
5. HINT! Теперь нужно запомнить OID, который будет названием каталога внутри созданной структуры директорий после создания БД. Важно учесть, что такой подход позволяет снепшотить только одну базу в tablespace.
/mnt/btrfs/pg/
- $SNAPSHOT/
- PG_11_201809051 # версия postgres (может отличаться)
- 99174299 # - DB OID. Запишем ее в переменную
new_oid=$(sudo ls -1 /mnt/btrfs/pg/$SNAPSHOT/PG_*/ | grep -P [0-9]+)
6. Таким же образом скопируем OID БД $ORIGIN
origin_oid=$(sudo ls -1 /mnt/btrfs/pg/$ORIGIN/PG_*/ | grep -P [0-9]+)
7. Теперь удалим обычный каталог, в котором находится tablespace для снепшота. См. сноску [1].
sudo rm -rf /mnt/btrfs/pg/$SNAPSHOT/
8. И наконец сделаем честный снепшот $ORIGIN через btrfs subvolume
sudo btrfs subvolume snapshot /mnt/btrfs/pg/$ORIGIN/ /mnt/btrfs/pg/$SNAPSHOT
9. В каталоге /mnt/btrfs/pg/$SNAPSHOT будет Copy-on-Write копия $ORIGIN subvolume с OID как в оригинальной БД. Поэтому переименуем ее, чтобы она выглядела для сервера как БД $SNAPSHOT из пункта 4.
cd /mnt/btrfs/pg/$SNAPSHOT/PG_*/
sudo mv $origin_oid $new_oid
cd -
10. Теперь можно подключится к БД $SNAPSHOT и проверить, что там те же данные что и в $ORIGIN.
IV. Когда понадобится удалить БД $SNAPSHOT (тоже верно и для $ORIGIN).
0. Полезно перед удалением завершить всю активность с базой.
PGQUERY=$(cat<< EOM
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid <> pg_backend_pid()
AND datname = '$SNAPSHOT';
EOM
)
psql -c "$PGQUERY"
1. Удаляем базу $SNAPSHOT
psql -c "DROP DATABASE IF EXISTS $SNAPSHOT;"
2. Удаляем tablespace $SNAPSHOT
psql -c "DROP TABLESPACE IF EXISTS $SNAPSHOT;
3. Удаляем subvolume btrfs.
btrfs subvolume delete /mnt/btrfs/pg/$SNAPSHOT
V. Если нужно выполнить восстановление состояния из $ORIGING в $SNAPSHOT:
1. Повторяем шаги для удаления $SNAPSHOT
2. Повторяем шаги для создания $SNAPSHOT из $ORIGIN
[1] Шаги II.1–6 нужны только для создания OID базы данных в tablespace. Удаление каталога в котором зарегистрирован tablespace не должен сломать postgres (но не думаю, что будет хорошей мыслью делать запросы во время создания снепшота). Мета информация о базах и tablespace-ах продолжает хранится в системной базе postgres, и мы на нее не влияем. Возможно, есть и другой способ создания OID базы данные и его подмена в снепшот $ORIGIN, если вы знаете такой, пожалуйста поделитесь.
P.S. Этот способ очень помог мне на работе, где нужно было каждый день проверять ошибки пользователей в системе на реальных данных, т.е. копии из продакшн. База занимала больше 100Гб, а разворачивание дампа около часа. По старинке, после разворачивания тестовая база захламлялась патчами или миграциями разных разработчиков и данные в ней начинали конфликтовать. Тогда нужно либо иметь запас развернутых копий БД, либо ждать пока удалится и восстановиться новая.
В случае с Btrfs, нужно один раз подождать разворачивание оригинальной базы, а потому за пару секунд создавать снепшоты, и когда необходимо также быстро откатываться к оригинальной базе.
Все эти шаги были вынесены в 3 bash скрипта, которые совпадают с последними тремя разделами, и запускались через jenkins. За год работы проблем на стороне btrfs или postgres замечено не было.