Как мы делали поддержку PostgreSQL
Привет, Хабр. Сегодня мы, Юрий Темкин и Алексей Федоров, расскажем о том, как в нашем Кибер Бэкапе устроена поддержка PostgreSQL и СУБД на ее основе, а также обсудим новинки, появившиеся в версиях 16.5 и 17.
Свободно распространяемая СУБД PostgreSQL и российская коммерческая СУБД Postgres Pro, разработанная на платформе PostgreSQL, являются одними из самых популярных систем управления базами данных в России. СУБД Postgres Pro была значительно переработана для соответствия требованиям корпоративных заказчиков. На PostgreSQL/Postgres Pro работают многие отечественные системы автоматизации бизнес‑процессов и офисной деятельности. В настоящее время PostgreSQL — это платформа, на которую переносят базы данных Microsoft SQL и Oracle, и эта задача более чем актуальна с учетом ухода с рынка зарубежных вендоров.
Немного истории
История поддержки резервного копирования СУБД PostgreSQL началась еще в Кибер Бэкапе версии 15: можно было создавать только полную резервную копию всего сервера на локальный диск. В версии 16 мы добавили инкрементное резервное копирование и возможность хранения резервных копий в сетевых папках, NFS, управляемых хранилищах узлов хранения и на лентах.
Развитие поддержки резервного копирования СУБД PostgreSQL мы продолжили в недавно выпущенном Кибер Бэкапе 16.5. Добавили гранулярное восстановление — одной базы или ее части, восстановление набора данных на определенный момент времени в прошлом (PITR) и поддержку резервного копирования и восстановления кластера Patroni.
Поддержка PostgreSQL в Кибер Бэкапе 15
На момент начала работы над поддержкой PostgreSQL, кроме резервного копирования дисков и файлов, мы уже поддерживали в продукте некоторые приложения. Отметим, что под поддержкой мы понимаем не просто возможность бэкапа списка файлов, принадлежащих приложению. Мы бэкапим сущности самого приложения и восстанавливаем эти сущности. Резервное копирование и восстановление управляется централизованно, через веб‑консоль, также поддерживается возможность централизованного хранения архивов и управления ими, очистка хранилищ, валидация резервных копий и т. д. Для поддержки всех этих операций была создана соответствующая инфраструктура. Код всего продукта занимал около 6 млн строк на С++. И когда возникла задача добавить поддержку PostgreSQL, мы оказались перед выбором: либо продолжать писать код как раньше и добавить еще 200 тыс. строк, либо сделать SDK для более легкого включения новых защищаемых приложений в инфраструктуру продукта с возможностью наследования всех ее фич.
Мы выбрали второй вариант. В результате у нас есть центральный компонент (Generic Backup Agent — GBA), который интегрирован в инфраструктуру. Этот компонент общается с агентом, отвечающим за поддержку конкретного приложения, по протоколу REST. Для упрощения задачи агент представляет собой пассивный REST‑сервер, а собственно потоком исполнения управляет GBA. Этот компонент был написан на Golang, и в нашей архитектуре агент может быть написан на любом языке программирования — пилотный агент мы написали на Python.
Агент для PostgreSQL мы решили написать тоже на Golang, а сам бэкап делать через репликационное соединение и SQL‑команду BASE_BACKUP. По сравнению с реализациями на уровне файлов, это дало нам возможность устанавливать агент на любом хосте в локальной сети, а не только на хосте, где развернута СУБД.
В результате в Кибер Бэкапе 15 появился агент для PostgreSQL, поддерживавший только полный бэкап, хранение резервных копий только на сервере, где установлен агент, и с ограниченными возможностями восстановления, но зато разработанный в концепции SDK.
Поддержка PostgreSQL в Кибер Бэкапе 16
К версии 16 мы пришли с агентом, функциональность которого явно требовала расширения. Крупных задач на этот релиз было две. Во‑первых, добавить инкрементный бэкап, а во‑вторых, заложить структуру хранения данных, которая даст нам возможность расширить варианты восстановления: реализовать PITR и поддержать гранулярность восстановления с целого кластера БД до отдельной базы данных.
Мы решили, что полные резервные копии будем по‑прежнему хранить в виде base backup, а с инкрементами поступим по‑другому. Перед base backup мы создаем репликационный слот и при каждом инкрементном бэкапе считываем все накопившиеся в слоте WAL‑файлы. Такое решение обладает тремя преимуществами:
Админам не требуется включать и настраивать архивирование WAL — не нужно заботиться о том, куда архивировать и когда удалять файлы.
Сам инкрементный бэкап выполняется очень быстро и минимально нагружает систему — его можно делать хоть каждые 10–15 мин.
Появляется возможность делать восстановление на момент времени в прошлом (PITR).
Структура архива и базы показана на следующей диаграмме.
В Кибер Бэкапе 16 восстановление по‑прежнему требовало от пользователя дополнительных действий, но появилась возможность указать конечную точку восстановления (PITR).
Поддержка PostgreSQL в Кибер Бэкапе 16.5
При подготовке релиза 16.5 мы занимались автоматизацией восстановления. Восстановление всего кластера БД по‑прежнему осталось ручной операцией — здесь особо ничего и не придумаешь, а вот другие сценарии восстановления мы автоматизировали.
У пользователей нашей СРК есть необходимость частичного восстановления данных, например, только одной базы. Очевидную идею — выгрузить все данные бэкапа в папку, поднять экземпляр PostgreSQL и скопировать необходимые данные с помощью pg_dump — зарубили сразу же. Почему? Потому, что если у нас весь кластер БД занимает 1000 ГБ, а нам надо восстановить базу размером 1 ГБ, то потребуется:
1000 ГБ места на диске в качестве временного хранилища.
Все 1000 ГБ извлечь из архива и только после этого поднять экземпляр PostgreSQL.
После детального изучения исходников PostgreSQL стало понятно, что, если научиться подсовывать ему под слой VFS свою виртуальную файловую систему (т. е. из архива читаются только те данные, которые PostgreSQL хочет читать), мы можем сильно сэкономить и на дисковом пространстве, и на объеме извлекаемых из архива данных. Единственная загвоздка в этом сценарии: виртуальная файловая система должна поддерживать операции и чтения, и записи, т. к., прежде чем полностью загрузиться, PostgreSQL должен «проиграть» все или имеющиеся до точки PITR WAL‑файлы.
В результате мы сделали следующее: из агента выставили простой REST API по обращению к виртуальной файловой системе. В API реализовали и механизм записи в копию с использованием временного файла. Прямо в исходниках PostgreSQL в слое VFS обращение к библиотеке cstdlib заменили на обращение к нашему REST API. И это сработало!
Сначала мы поднимаем PostgreSQL в режиме single mode. Он «проигрывает» WAL‑файлы и завершает работу. После этого снова поднимаем PostgreSQL, но уже для обслуживания внешних соединений. Делаем это на том хосте, где установлен агент. Как отметили выше, хост не обязательно должен совпадать с хостом, где развернут PostgreSQL. Таким образом мы получили ценные данные по объемам передаваемых данных и паттерну работы с VFS.
Получилось так:
Для «проигрывания» WAL‑файлов PostgreSQL считывает из архива объем данных, равный объему WAL‑файлов до точки PITR, и столько же мы пишем во временный файл.
Объем данных для чтения, помимо WAL‑файлов, пренебрежительно мал.
Далее при работе pg_dump PostgreSQL читает объем данных, соизмеримый с объемом выдаваемого dump‑файла: на наших данных PostgreSQL считывал ~70% от общего объема.
При этом, чем меньше индексы, тем меньше PostgreSQL нужно считывать данных: pg_dump читает без использования индексов.
В качестве proof of concept такой сценарий подходит, но в продукте его использовать не получится по нескольким причинам:
Очень накладно поставлять PostgreSQL с патчем для всех версий и архитектур, поддерживаемых нашим продуктом.
Некоторые заказчики используют собственные сборки PostgreSQL с альтернативными значениями базовых констант, что делает такие серверы несовместимыми с нашим решением.
Существуют СУБД на базе PostgreSQL с закрытым кодом, такие как Postgres Pro.
В итоге мы немного модифицировали метод внедрения в VFS: сделали файл расширения библиотеки (.so‑файл), который заменяет функции cstdlib, и начали запускать PostgreSQL с опцией LD_PRELOAD. Мы решили описанные выше проблемы, но появились новые — потеря производительности.
Стали анализировать паттерн и поняли, что PostgreSQL читает и пишет данные блоками по 8 КБ, синхронно, из одного потока. А наш сервер REST API находится в другом процессе. Это приводит к тому, что мы делаем 3–5 syscall на каждые 8 КБ данных. И тут пришло решение, которое не обладает видимыми недостатками, зато решает проблему эффективности — FUSE.
FUSE (файловая система в пользовательском пространстве) позволяет смонтировать виртуальный каталог и превратить обращения в этот каталог в вызовы внутрь процесса, который ее создал. При этом поддерживаются многие стандартные оптимизации, относящиеся к кэшированию файловой системы: read‑ahead и write‑back (особенно это касается FUSE3). Кроме того, FUSE сама организует запросы на передачу данных и ответы в системные очереди — никакого дополнительного «транспорта» не требуется.
Диаграмма ниже иллюстрирует сказанное. И стрелки, которых много под PostgreSQL и мало под агентом, это чистая правда — PostgreSQL по‑прежнему работает с блоками по 8 КБ (но теперь у нас 1 syscall на блок), а вот VFS внутри агента получает запросы, сгруппированные в блоки по 128 КБ. Таким образом мы решили проблему с производительностью.
Использование FUSE дало нам возможность выполнять восстановление данных многими востребованными способами.
Поддержка PostgreSQL в Кибер Бэкапе 17
В последних релизах нашей СРК мы внесли несколько полезных улучшений на уровне агента для PostgreSQL. Так, в версии 17.0 агент может использовать программно‑определяемые хранилища Кибер Инфраструктуры в качестве места хранения резервных копий. В версии 17.1 мы реализовали возможность выполнять определенные команды или запускать пакетные файлы перед началом резервного копирования или после его завершения.
Гранулярное восстановление
В Кибер Бэкапе 16.5 мы добавили следующие возможности гранулярного восстановления баз данных PostgreSQL с поддержкой PITR:
Монтирование экземпляра из резервных копий Кибер Бэкапа.
Восстановление отдельных баз в существующий экземпляр PostgreSQL.
Восстановление в dump‑файл.
В августе 2024 года мы получили патент № 2 825 077 на нашу технологию гранулярного восстановления. Полный текст описания изобретения можно посмотреть здесь — много технической информации для интересующихся.
Монтирование экземпляра БД
Рассмотрим следующий сценарий. Пользователю необходимо посмотреть, проверить содержимое и по возможности восстановить одну базу или часть базы из архива PostgreSQL/Postgres Pro. Это может быть актуально для решения задач, связанных с проверкой данных в архиве, или при восстановлении отдельных таблиц без необходимости восстановить весь экземпляр или большую базу. Кроме того, это полезно при расследовании инцидентов.
В этом сценарии при восстановлении данных PostgreSQL мы выбираем опцию монтирования базы. После того как база смонтирована, используем консоль PostgreSQL для подключения к базе (или любой другой инструмент, который работает с базой через стандартное подключение, — pgAdmin, pg_dump и т. д.) и выполнения необходимых операций. По завершении в Кибер Бэкапе снова выбираем операцию монтирования и отключаем смонтированную базу.
Для того чтобы монтирование работало корректно, агент Кибер Бэкапа 16.5 должен быть установлен на систему Linux, для монтирования данных из архива требуется установка библиотеки libfuse. На клиенте должна быть установлена СУБД PostgreSQL той же версии, что и в архиве. Монтирование не поддерживаются для архивов на ленточных накопителях.
Восстановление отдельной базы
В версии 16.5 Кибер Бэкапа есть возможность восстановить отдельную базу PostgreSQL из архива экземпляра PostgreSQL в исходную базу или в новую базу подключенного экземпляра PostgreSQL.
При восстановлении выбираем из архива нужные базы для восстановления, указываем, куда восстановить, выбираем определенный момент времени в прошлом (PITR) и запускаем операцию восстановления. Отметим, что PITR доступно только при наличии в архиве инкрементных копий.
Восстановление отдельных баз в dump-файл
Предположим, требуется экспортировать базы из архива в dump‑файл для дальнейшего использования средствами PostgreSQL. Это может потребоваться, например, при переносе базы в другой экземпляр PostgreSQL.
Как и в предыдущем сценарии, при восстановлении выбираем из архива нужные базы для восстановления, указываем, куда восстановить, выбираем определенный момент времени в прошлом (PITR) и выбираем восстановление выбранных баз в dump‑файл.
Новые возможности гранулярного восстановления отдельных баз и монтирование, появившиеся в Кибер Бэкапе 16.5, позволяют восстановить требуемые данные быстрее, без необходимости восстановления всего экземпляра PostgreSQL.
Поддержка кластера Patroni
В Кибер Бэкапе 16.5 мы сделали поддержку резервного копирования и восстановления популярного кластера Patroni и обеспечили работу резервного копирования даже в условиях отказа узла без необходимости каких‑либо дополнительных действий со стороны администратора.
В новом релизе появилась возможность добавить в список защищаемых устройств кластер Patroni. При добавлении кластера указывается адрес и порт агента Patroni, а также учетная запись доступа к PostgreSQL. Соответственно, добавление экземпляра PostgreSQL как узла кластера в Кибер Бэкапе должно выполняться в контексте кластера.
Как и для отдельного экземпляра PostgreSQL, для экземпляра как узла в кластере можно создать план резервного копирования. Отличие от плана резервного копирования для отдельного экземпляра PostgreSQL заключается в том, что появляются опции, позволяющие выбрать следующие параметры:
Узел Лидер. Опция позволяет выполнять резервное копирование только лидера (master).
Узел Реплика. Опция позволяет выполнять резервное копирование только реплики (slave).
Реплика, если возможно. Опция позволяет Кибер Бэкапу попытаться выполнить резервное копирование реплики, но если этот узел недоступен, будет выполнено резервное копирование лидера.
Что касается восстановления данных, поддерживается восстановление экземпляра или базы данных в экземпляр в узле кластера. Также поддерживается восстановление экземпляра или базы данных из узла в некластеризованный экземпляр и обратно.
Как это работает, показано на следующей диаграмме.
За счет того, что соединение с PostgreSQL осуществляется по сети через репликационный протокол, мы получаем возможность работать с сервером с другого хоста. По API кластера можем зайти на него, получить конфигурацию, выбрать узел (сервер) и выполнить его резервное копирование в соответствии с настройками плана защиты. По тому же программному интерфейсу мы получаем и данные о кластере — список узлов с их индивидуальными состояниями. Если состав кластера меняется, то информация автоматически обновляется в базе Кибер Бэкапа.
В завершение отметим некоторые особенности поддержки кластеров Patroni. Для работы с агентами Patroni агент Кибер Бэкапа должен иметь доступ к внутренней сети кластера Patroni. При переключении узлов всегда создается новая полная резервная копия. Это происходит из‑за того, что при отказе узла удаляется репликационный слот. Это ограничение можно обойти, прописав имя репликационного слота в настройках Patroni. По умолчанию Patroni работает по HTTP‑протоколу. Протокол HTTPS пока не поддерживается.
Кибер Бэкап 17.1
В версии 17.1 Кибер Бэкапа реализована возможность выполнять определенные команды или запускать пакетные файлы перед началом резервного копирования баз PostgreSQL или после его завершения.
Заключение
Мы продолжаем тесное технологическое взаимодействие с компанией Postgres Pro. Также развиваем поддержку российских СУБД на базе PostgreSQL: Jatoba, Proxima DB и Tantor.