Привет! Хочу рассказать о своем новом небольшом проекте - pg-status. Это очень легкий и производительный микросервис, который помогает определять статус хостов postgresql. В первую очередь его задача - помочь вашему backend найти живого мастера и достаточно синхронную реплику.
Ключевые особенности:
очень легко поднять как sidecar и интегрировать с существующей конфигурацией postgresql
помогает определять мастера и синхронную реплику, работать с failover
помогает балансировать нагрузку между хостами
Буду очень рад поддержке в виде звезд на github если вам понравится мой проект!
Но сначала начнем с введения что за проблема которую решает pg-status.
PostgreSQL на нескольких хостах
Чтобы повысить устойчивость базы данных PostgreSQL, а так же увеличить нагрузку, которую она может выдержать - поднимают несколько хостов по классической схеме master-replica. То есть есть один мастер хост в которого можно писать. Есть реплика или несколько реплик которые получают изменения из мастера с помощью физической или логической репликации. Все замечательно и классно работает, но нужно помнить о важных нюансах:
любой из хостов может упасть
реплика может занять место мастера (failover)
реплика может сильно отставать от мастера
И в связи с этим, с перспективы backend какого-нибудь приложения, которое ходит в бд есть несколько вопросов с которыми приходится сталкиваться:
как определить какой из хостов живой мастер
как определить какие из хостов живые реплики
как определить насколько сильно реплика отстает от мастера, чтобы решить стоит ли с нее читать
как переключить клиентский пул соединений или в целом переключиться на новый хост, когда происходит смена мастера
как сбалансировать нагрузку между хостами
В целом в мире уже есть различные решения касательно этого вопроса. У каждого есть преимущества и недостатки. Мне известны следующие способы
Через DNS
Есть определенный хост который смотрит на мастера и хост который смотрит на реплику. Базово тут нет обработки переключения мастера, а так же никак не помогает определить состояние реплик - нужно делать sql запрос самому.
Можно к этому добавить какой-нибудь сервис, который будет определять состояние хостов и делать запись в DNS, но:
обновление DNS может занимать секунды и десятки секунд, иногда это критично
DNS может уйти в read only режим
Насколько знаю облачные провайдеры postgresql часто предоставляют именно такой механизм.
Мультихост в libpq
Умеет находить первый живой хост в списке который подходит под условие (мастер или реплика). То есть никак не помогает в балансировке нагрузки.
Смену мастера может заметить только после непосредственной попытке выполнить sql запрос, падение и повторное переподключение где снова произойдет перебор хостов.
Прокси
Можно поднять прокси, в котором можно на горячую менять конфиг. И нужно к нему поднять что-то, что будет говорить прокси, что нужно переключиться.
В целом это довольно хорошее решение и тут по сути важен именно механизм определения состояния хостов postgresql и предоставления ручек для прокси. В данном решении так же отлично может подойти pg-status как вот это что-то, что будет говорить прокси, что нужно переключиться.
Либо можно использовать pgpool‑II, который специально создан для таких задач и может не только помогать с попаданием в нужный хост, а еще и предоставить пулл соединений со своей стороны, и даже реализует сам механизм failover. Из минусов могу только отметить что его довольно сложно разворачивать и конфигурировать.
CloudNativePG
Насколько я знаю в этом решении уже все есть из коробки. Тут чисто вопрос сложности и нужности развертывания именно таким образом, через Kubernetes.
Мое решение - pg-status
На моем текущем проекте как раз используется облачный провайдер postgresql который предоставляет механизм failover и предоставляет возможность попадать в мастер через DNS. Мне же в приложении не хотелось попадать в ситуацию, когда DNS долго не может обновить мастер. А так же хотелось не просто попадать в мастер, а так же балансировать нагрузку на реплики и понимать насколько реплика отстает от мастера. При этом не хотелось усложнять архитектуру приложения - поднимать какой-то общий прокси, который может отказать и из-за чего все встанет.
В итоге отличным решением показалось - сделать крохотный sidecar который будет поднят рядом с backend, который возьмет на себя задачу - выдать мне необходимый хост. На backend я держу клиентский пул соединений и перед выдачей коннекта прикладному коду я проверяю состояние хостов и в случае чего сразу переподключаюсь куда нужно.
От того, что можно использовать именно как sidecar получаются интересные бонус поинты:
Падение sidecar может помешать только одному инстансу основного сервиса, а не всем.
Определяется доступность pg именно от местоположения инстанса. Таким образом по показаниям pg-status я могу сообщить в health check что на этот инстанс сервиса не надо пускать нагрузку так как он не может достучаться до pg (например потому что из конкретного ДЦ оборвалась сеть до ДЦ где развенут PG).
Так получился pg-status. Его задача опрашивать хосты время от времени и помнить в памяти текущее состояние хостов. И предоставить несколько удобных ручек, которые будут очень быстро отвечать.
К pg-status можно обращаться непосредственно из вашего backend, на каждый запрос например, чтобы убедиться, что мастер не переключился. И если переключился, то перейти на него. Или с помощью специальных ручек подобрать для чтения подходящую реплику с точки зрения отставания от мастера.
Например у меня есть библиотека для python - context-async-sqlalchemy, в которой есть специальное место, куда можно было бы подключить pg-status чтобы всегда попадать куда нужно.
Как пользоваться
Установка
Можно собрать из исходников, можно использовать deb или бинарники, можно использовать как docker контейнер (есть легкие alpine контейнеры и на ubuntu). В данный момент моя целевая архитектура это linux amd64, но если нужно микросервис можно скомпилировать через cmake под нужную. Подробнее можно увидеть тут.
Использование
Сервис предоставляет возможности настраивать свое поведение через переменные окружения. Какие-то нужно задать обязательно: такие как параметры для подключения к хостам. Какие-то не обязательно так как заданы дефолтные значения, но можно подобрать под себя. Подробнее про все параметры можно прочитать тут
Запущенный сервис предоставляет простые HTTP ручки:
GET /master- выдает текущего мастераGET /replica- выдает рандомную реплику по round-robin алогритмуGET /sync_by_time- выдает синхронную реплику по времени или мастера, то есть отставание от мастера измеряется во времениGET /sync_by_bytes- выдает синхронную реплику по байтам (по журналу логов WAL LSN) или мастер, то есть отставание от мастера измеряется в байтах сколько записано в журналGET /sync_by_time_or_bytes- по сути хост из sync_by_time или из sync_by_bytesGET /sync_by_time_and_bytes- по сути хост из sync_by_time и из sync_by_bytesGET /hosts- выдает список всех хостов и их текущее состояние: живость, мастер или реплика
то есть как видите для определения нужной реплики предоставлен широкий функционал. Максимально приемлемый лаг по времени или байтам так же может быть задан через переменные окружения. Почти все ручки могут работать в двух режимах:
просто выдать хост как plain text
выдать хост в json формате, например
{"host": "localhost"}
Регулируется заголовком Accept: application/json
Так же pg-status можно использовать вместе с каким-нибудь прокси или любым другим решением где нужно что-то, что будет непосредственно отвечать за опрос хостов. То есть backend всегда подключается на один и тот же хост - хост прокси, который всегда ведет например на мастера. Но сам прокси не умеет определять что происходит с postgresql, но зато он может дергать http ручки pg-status, чтобы понять нужно ли поменять параметры проксирования.
Детали реализации pg-status
Это микросервис написанный на Си. Выбрал этот язык по нескольким причинам:
максимально эффективный с точки зрения ресурсов так как задумывался как sidecar
мне нравится Си и этот проект показался подходящим, чтобы написать именно на нем
Микросервис предоставляет из себя 2 части и 2 активных треда:
1. Мониторинг PG
1 тред это тред мониторинга. Он время от времени опрашивает все хосты с помощью библиотеки libpq, делая один sql запрос и таким образом узнает состояние хостов. Эта часть имеет довольно широкий список настроек, которые задаются через переменные окружения:
как часто нужно опрашивать хосты
timeout для подключения к одному хосту
через сколько попыток подключения объявить хост мертвым
предел отставания реплики от мастера в миллисекундах, когда еще считается синхронной
предел отставания реплики от мастера в байтах по WAL LSN, когда еще считается синхронной
В данный момент поддерживается только физическая репликация.
2. HTTP сервер
2 тред это тред http сервера. Его задача обслуживать запросы от клиента и выдавать из оперативной памяти текущее состояние хостов. Реализовано с помощью библиотеки libmicrohttpd. Показывает отличный performance, который спокойно позволяет вашему backend обращаться к pg-status хоть на каждый sql запрос который вы делаете. Я тестировал в docker контейнере с 0.1 cpu и 6 mib оперативной памяти и при запросах с хоста получил 1500 rps с крайне низкой задержкой ответа. Подробнее метрики можно посмотреть тут
Потенциальные доработки
В данный момент функциональность микросервиса меня устраивает и он уже служит в моих продуктах. Потенциально я задумываюсь о том, что может быть еще полезно доработать, например:
поддержать логическую репликацию
в json формат ответа добавить точное число отставания реплики от мастера по времени и байтам, чтобы клиент мог навернуть вокруг этого свою логику
Если вам понравился проект и вы заинтересованы в каких-либо доработках или улучшениях - смело создавайте issue на github!
Итог
Получился очень легковесный микросервис, который помогает решить задачу определения состояния хостов postgresql и при этом его очень легко поднять
Сервис доступен под MIT лицензией
Открытый исходный код на github: https://github.com/krylosov-aa/pg-status
Можно собрать из исходников, можно использовать deb или бинарники, можно использовать как docker контейнер
Буду очень рад поддержке в виде звезд на github! Спасибо!
chemtech
Если failover pg организован в Managed postgresql в yandex cloud, то как голосовать?
krylosov-aa Автор
Там как раз используется DNS потому что там используется особый FQDN.
Вот в документации явно сказано что иногда переключение может занимать до 10 минут.