Мгновенные снепшоты postgres на tablespace и btrfs

ebe6d6c7e57201024e661b0c368712a3

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 замечено не было. 

© Habrahabr.ru