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

Комментарии (5)


  1. rrrad
    01.05.2022 08:35
    +3

    У postgres.ai есть чтото похожее, при чем там целая экосистема вокруг этой штуки сделана


  1. chemtech
    01.05.2022 09:23
    +1

    Спасибо за пост. Посмотрите https://github.com/postgres-ai/database-lab-engine - Thin PostgreSQL clones. DLE provides blazing-fast database cloning to build powerful development, test, QA, staging environments. Follow to stay updated.


  1. akhkmed
    02.05.2022 01:38

    Верно понимаю идею, что делается снепшот лишь одного tablespace одной БД чтобы развернуть в соседней БД в другом tablespace? Так нельзя делать, снепшот должен включать все tablespace, xact и wal. В противном случае данные превратятся в мусор при первой возможности.


    1. r9deyes Автор
      02.05.2022 23:30

      Хорошее замечание. БД и снепшоты использовались в относительно несложном приложении. Т.е. только работа с таблицами внутри одной базы.

      Ещё нужно заметить, что раздел с системными сущностями postgres остался нетронутым, он даже может не располагаться в btrfs. Поэтому кажется, что все снепшоты с базами и tablespace остаются независимыми.

      Можете привести пример, какие действия могут привести к превращению данных в мусор?


  1. mvv-rus
    02.05.2022 21:43
    +2

    Инструкция linux only, про поддержку CoW файловых систем на Windows не в курсе.

    Немного весьма сырых сведений про Windows и мгновенные копии томов: на всякий случай, вдруг кому пригодится.
    В Windows снимки томов (в том числе и путем копирования при записи AKA CoW) поддерживаются Volume Shadow Copy Service (VSS) на уровне дискового тома, т.е. — ниже уровня файловой системы. Фактическая поддержка осуществляется компонентом под названием VSS Provider. Этот компонент может быть как аппаратным (использющим возможности хранилища SAN, на котором размещен том; как именно реализована поддержка — зависит от хранилища), так и программным, реализованным внутри Windows. Программный — «Microsoft Software Shadow Copy provider» — реализует именно копирование при записи и взаимодействует с файловой системой (на том же или другом томе) для хранения в ней старых копий перезаписываемых блоков.
    Созданный снимок может быть смонтирован в системе как обычный дисковый том, с которым можно работать обычными средсвами ОС.
    Для взаимодействия с VSS используются, естественно, другие команды, нежели с файловыми системами Unix — vssadmin и diskshadow.
    Написать скрипты для использования этих команд для целей, указанных в этой статье, наверное, можно, но лично я этим не занимался и не планирую. Может, кто-нибудь ещё?

    PS А ещё для взаимодействия приложений с VSS в Windows есть концепция VSS Writer: зарегистрированных в системе модулей, к которым VSS обращается в момент создания снимка и которые приводят файлы данных «своего» приложения в состояние, в котором они записываются на ленту при резервном копировании средствами приложения — то есть, для полученных таким образом снимков файлов можно использовать опции инкрементного и разностного копирования. При этом Writer сообщает VSS список файлов относящихся к данным приложения — чтобы при создании резервной копии можно было копировать только их. Но AFAIK, для Postgres Writer никто так и не написал, так что для получения копий его файлов данных в Windows остается только брать файлы с теневой копии на момент создания снимка (crash-consistent), как и в этой статье