Источник: Nuances of Programming
PgCat — это open source прокси-сервер Postgresql, в развитии которого мы участвуем и который используем на продакшене. Функционал PgCat для кластеров Postgresql: пул соединений, балансировка нагрузки, отработка отказа реплики.
В Instacart мы применяем Postgresql почти для всех задач баз данных: эффективным кешированием, индексированием, оптимизацией запросов и вертикальным масштабированием экземпляров выжимаем из одного экземпляра Postgres немало производительности. Все это отличные стратегии, но с ними далеко не уедешь. Не ограничиваясь одним экземпляром за счет добавления реплик чтения или горизонтального сегментирования БД, мы в обоих случаях увеличиваем сложность.
Например, в первом приложению нужно «знать», сколько имеется реплик, и эффективно балансировать нагрузку между ними, перенаправлять трафик от поврежденной реплики и «понимать» концепцию задержки репликации.
Раньше эти проблемы решались на стороне приложения библиотекой Ruby Makara — адаптером подключения ActiveRecord для балансировки нагрузки и отработки отказа реплики. Но у Makara имеются недостатки, а главное — это решение только для Ruby.
Идеальное место реализации обозначенного выше функционала — прокси-уровень между клиентами и БД, взаимодействие через который осуществляется по независимому от языка протоколу Postgres.
В Instacart вместо прокси-уровня был PgBouncer, у которого очень хорош пул соединений, но нет отработки отказа реплики и ограничена балансировка нагрузки.
Изучив другие варианты, мы выбрали адекватный нашим будущим задачам PgCat. Расскажем почему, в чем отличие от PgBouncer и какой функционал мы для него разработали.
Почему PgCat?
Нам нужен прокси-сервер Postgresql, в котором, помимо пула соединений PgBouncer, имеются балансировка нагрузки, отработка отказа реплики и возможность безопасного расширения функционала в будущем.
Мы оценили варианты, исключив Pgpool из-за отсутствия режима транзакций:
От друзей из PostgresML узнали об их новом прокси Postgres PgCat с поддержкой большинства функционала. Он написан на Rust, и это плюс: благодаря гарантиям безопасного использования памяти, средства многопоточного программирования создаются без ущерба для безопасности и скорости.
Оценка PgCat
PgCat был тогда в бета-версии. Добавив недостающий функционал (несколько пулов на экземпляр и нормальное завершение работы), мы протестировали этот прокси в средах «песочницы» и продакшена. Особенно важным было сравнение PgCat с Pgbouncer по временно́й задержке и корректности.
Временна́я задержка
Чтобы сравнение при тестировании временно́й задержки было сопоставимым и сквозным, мы настроили в службе применение одной реплики PgCat, другой — PgBouncer, а клиентской библиотекой балансировали нагрузку между ними.
В рабочих нагрузках продакшена обнаружилось близкое соответствие профилей временно́й задержки PgCat и PgBouncer. Вот процентили задержки сквозной обработки запросов на обоих прокси при размере выборки данных примерно в 1,5 млн запросов продакшена, выполненных за 12 часов:
+------+-----------+---------+------------+
| | PgBouncer | PgCat | Разница |
+======+===========+=========+============+
| p50 | 4,68 мс | 4,69 мс | 10 мкс |
| p75 | 6,72 мс | 6,78 мс | 60 мкс |
| p90 | 10,8 мс | 10,9 мс | 100 мкс |
| p95 | 14,8 мс | 15,0 мс | 200 мкс |
| p99 | 26,8 мс | 27,8 мс | 1 мс |
+------+-----------+---------+------------+
Временна́я задержка сквозной обработки запросов
При переходе на PgCat заметного влияния на временну́ю задержку службы в целом не наблюдается.
Корректность
Мы проверили, что прокси корректно обращается с протоколом при взаимодействии с базой данных и клиентами, а поведение PgCat и PgBouncer как пулов соединений аналогично.
Для этого подвергли прокси синтетическим нагрузкам, протестировав его на различных шаблонах запросов в средах продакшена, а также на клиентах с хорошим и плохим поведением.
А еще, чтобы подключиться к уже имеющимся инструментам PgBouncer, протестировали поддержку в PgCat команд управления RELOAD, SHOW POOLS и т. д., применяемых в PgBouncer для мониторинга и контроля соединений.
Мы выявили и исправили проблемы, например с нормальным завершением работы, многопользовательским пулом, и сейчас PgCat без проблем справляется с рабочими нагрузками продакшена.
Отличия PgCat от PgBouncer
PgCat — это упрощенный PgBouncer. Помимо команд управления и статистики, нескольких пользователей и пулов PgBouncer, в нем поддерживаются режимы сессии/транзакции. Но чем отличается PgCat от PgBouncer?
Архитектура
PgCat — это многопоточный прокси на Rust с асинхронной средой выполнения Tokio. Pgbouncer — однопоточный прокси на C и libevent.
Схема развертывания
PgCat размещается перед одним экземпляром БД. Согласно схемам A и B ниже, поведение PgCat аналогично PgBouncer.
Но, чтобы в приложении был функционал балансировки нагрузки и отработки отказа, экземпляр PgCat должен подключаться к нескольким экземплярам в кластере репликации. Это может быть первичная и все реплики, все реплики без первичной или подмножество реплик.
Если перед каждым экземпляром PgCat лишь один экземпляр БД, балансировка нагрузки или отработка отказа не выполняется.
Проблема решается различными способами:
- Несколько контейнеризированных экземпляров PgCat развертываются за балансировщиком нагрузки, и каждый подключается к нескольким БД в кластере.
- Один экземпляр PgCat запускается на многоядерном хосте отдельно от БД.
- Несколько экземпляров PgCat вместе размещаются на машинах с БД, но взаимодействуют с другими экземплярами БД в кластере.
У каждого из этих вариантов свои плюсы и минусы. Отметим, что PgCat многопоточный и многоядерный процессор компьютера, на котором он запускается, будет кстати.
В Instacart, согласно схеме C, применяется контейнеризированная схема с репликами, экземпляры обычно разделяются на два пула: один только для первичной, другой для всех реплик.
Функционал
Балансировка нагрузки
В случае с PgCat в пуле размещается несколько экземпляров БД, между которыми затем распределяется нагрузка. Так код приложения сильно упрощается: до подключения лишь к одной конечной точке БД.
Сейчас в PgCat поддерживается два алгоритма балансировки нагрузки: случайный выбор и наименьшие соединения.
Согласно первому, из пула просто выбирается случайный экземпляр. Позже покажем, что эта стратегия лучше, чем Round-Robin.
Согласно второму, экземпляру с наименьшим количеством проверенных соединений отправляются запросы. Эта стратегия скорее для разнородных рабочих нагрузок или пулов с экземплярами БД разного размера, и с ней быстрее реакция на временные замедления реплик.
Отработка отказа реплики
В настройке с несколькими репликами нужна возможность перенаправлять трафик от отказавшей реплики, пока та не восстановится. Быстро найдя такую реплику, мы уменьшим последствия для приложения. Когда в PgCat обнаруживается отказ реплики, ее использование запрещается по умолчанию на 60 секунд.
В PgCat реплика запрещается в следующих случаях.
- Истекло время запроса post-checkout проверки работоспособности или случился его сбой.
- Клиент теряет соединение при подключении к реплике.
- У клиента истекло время ожидания проверки подключения из пула к этой реплике.
- В запросе превышено заданное время ожидания инструкции PgCat, это значение в PgCat по умолчанию отключено.
Запрет реплик в сочетании с Round-Robin чреват перекосом нагрузки, поэтому мы использовали случайную балансировку нагрузки. Рассмотрим систему с четырьмя репликами: клиенты проходят через них по порядку, хотя с разными начальными точками, то есть 3 всегда после 2, а 4 после 3 и т. д.
Проблема появляется при запрете реплики: для всех клиентов, которые переходят через нее к следующей, он разрешится одним и тем же экземпляром. Например, если запретить реплики 2 и 3, весь трафик из них окажется в реплике 4: она следующая в очереди для всех клиентов. Этот эффект должен быть временным, но оказывается постоянным, судя по нашему опыту.
Перейдя в PgCat на случайную балансировку нагрузки, мы устранили перекос:
Клиенты с плохим поведением
Клиент с плохим поведением несвоевременно отключается от прокси:
- открывает транзакцию и, пока она открыта, отключается;
- отключается во время выполнения запроса.
В PgBouncer в этих случаях соответствующее соединение с сервером закрывается. Это проблематично, если случается слишком часто: чревато сильными перегрузками подключения к серверу. Такие события негативно сказываются на производительности БД.
В PgCat эта проблема решается запросом на откат подключения к серверу и возвращением его обратно в пул.
Поддержка сегментированных баз данных
В PgCat поддерживается первоклассная концепция сегментирования: по ключу сегментирования или номеру сегмента в приложении выбирается сегмент для взаимодействия. Это функционал еще только планируется в рабочих нагрузках продакшена.
Текущее состояние
Последние полгода PgCat применяется для некоторых наших несегментированных рабочих нагрузок продакшена. Пиковая пропускная способность — около 105 000 запросов в секунду для нескольких ECS task, для каждой task — около 5200. Клиенты различались по языку и шаблонам, включая Ruby, Python и Go: все перешли на PgCat и использовали его без проблем и изменений.
Мы перенести одну из крупнейших наших БД на PgCat, добавив балансировку нагрузки некоторым службам. В итоге обработали несколько сбоев БД, упростили сопровождение ее экземпляров.
Сейчас переносим в PgCat бо́льшую часть рабочих нагрузок продакшена, думаем применить PgCat в кластерах сегментированных БД.
Скоро выходит версия 1.0 PgCat, и ведется активный поиск новых участников для развития проекта.
Читайте также:
Перевод статьи Mostafa Abdelraouf: Adopting PgCat: A Nextgen Postgres Proxy