Как зашедулить пул соединений PostgreSQL при совместном использовании в микросервисах на Python (FastAPI) и Go?
Приветствую! Столкнулась со следующей архитектурной проблемой при проектировании взаимодействия микросервисов.
Есть общая база данных PostgreSQL. К ней одновременно обращаются два сервиса:
1.Высоконагруженный сервис на Go (обрабатывает real-time стриминг данных, держит постоянный пул соединений).
2.Сервис бизнес-логики на Python (FastAPI + асинхронная SQLAlchemy 2.0 + asyncpg).
Суть проблемы:
Периодически под пиковой нагрузкой сервис на Go утилизирует почти весь лимит max_connections СУБД. В этот момент асинхронный пул Python-сервиса начинает сыпать ошибками таймаута при попытке взять соединение (TimeoutError: QueuePool limit of size X overflow).
При попытке динамически расширить пулы на стороне Python (pool_size и max_overflow), мы упираемся в жесткий потолок памяти самого сервера PostgreSQL, так как каждое новое соединение в Postgres обходится дорого.
Пробовала внедрить PgBouncer в качестве прокси-прослойки:
-В режиме сессий (session mode) проблема с перегрузкой пула со стороны Go не решается.
-В режиме транзакций (transaction mode) всё работает отлично для Go, но намертво ломается асинхронная SQLAlchemy на Python, так как она под капотом использует подготовленные выражения (Prepared Statements), которые несовместимы с транзакционным режимом PgBouncer без костылей.
Вопрос:
Как архитектурно правильно разрулить эту ситуацию?
Стоит ли полностью изолировать сервисы на уровне отдельных физических баз (делать репликацию данных), или есть способ подружить асинхронный Python-стек с транзакционным пулингом PgBouncer без потери производительности Prepared Statements?
Во-первых, общих баз у микросервисов быть не должно. Во-вторых, если у сервиса на Go постоянный пул соединений, то как в пиковые нагрузки он утилизирует max_connections СУБД? Выглядит так, будто в go'шном коде вы неправильно работаете с БД.
Физически разносить базы ради этого не надо. А тезис про несовместимость transaction mode PgBouncer с prepared statements устарел: с версии 1.21 он умеет трекать их на уровне протокола, просто выстави `max_prepared_statements` больше 0. Плюс раздели го и питон-сервисы по разным DB-юзерам со своими pool_size/max_user_connections в pgbouncer.ini, тогда го физически не сможет съесть чужую квоту. И да, QueuePool timeout — это исчерпание пула самого SQLAlchemy, а не max_connections постгреса, так что заодно проверь висящие idle-in-transaction сессии.