Pool Coordinator

Pool Coordinator ограничивает суммарное число backend-соединений к одной базе по всем пользователям пула, а при достижении лимита применяет приоритетное вытеснение. Это то, чем должен был стать max_db_connections в PgBouncer: справедливое применение лимита, резерв на короткие всплески и минимум на пользователя, защищающий критичные нагрузки.

Эта страница объясняет концепцию и сценарии применения. Рецепты тюнинга и разбор вывода SHOW POOL_COORDINATOR смотрите в Пул под нагрузкой.

Какую задачу решает

Без координатора каждый user-пул независим. pool_size равный 40 у пяти пользователей означает до 200 backend-соединений — и PostgreSQL приходится бороться за свои собственные лимиты.

max_db_connections в PgBouncer ограничивает общую сумму, но блокирует новые запросы, как только лимит достигнут. Кто первым занял слоты, тот и удерживает их независимо от интенсивности использования, а медленные нагрузки никогда не уступают быстрым.

Pool Coordinator в pg_doorman ограничивает сумму и:

  • Вытесняет idle-соединения у пользователей с переизбытком, когда другому пользователю нужно вырасти.
  • Ранжирует пользователей по p95 времени транзакции, чтобы медленные пулы отдавали слоты первыми. Пользователи с быстрыми запросами сохраняют преимущество переиспользования; пользователи с длинными транзакциями отдают первыми, потому что их соединения и так дольше простаивали.
  • Резервирует небольшое переполнение для коротких всплесков. Настраивается отдельно от основного лимита.
  • Гарантирует минимум на пользователя, который никогда не вытесняется. Критичные нагрузки сохраняют опору при конкуренции.

Когда использовать

Включайте координатор, когда:

  • На одной базе сосуществуют разные нагрузки, и нужен верхний предел числа backend-соединений (max_connections PostgreSQL, RAM, файловые дескрипторы).
  • Одна нагрузка имеет всплеск нагрузки, и вы хотите, чтобы она забирала простаивающие слоты у других, не вытесняя их навсегда.
  • Вы работаете рядом с потолком соединений PostgreSQL и нуждаетесь в справедливой деградации, а не в обслуживании по принципу «кто первый встал, того и тапки».

Координатор не нужен, когда:

  • pool_size каждого пользователя достаточно мал, чтобы их сумма комфортно укладывалась в max_connections PostgreSQL.
  • Нагрузки предсказуемы и заранее размечены.
  • Вы хотите простоту уровня PgBouncer. max_db_connections без вытеснения поддерживается, но не рекомендуется для общих баз.

Конфигурация

pools:
  shared_db:
    server_host: "127.0.0.1"
    server_port: 5432
    pool_mode: "transaction"

    # Общий лимит на всех пользователей этого пула.
    max_db_connections: 80

    # Резерв сверх max_db_connections для коротких всплесков.
    # Захватывается, только если в течение reserve_pool_timeout не нашлось idle-соединения.
    reserve_pool_size: 16
    reserve_pool_timeout: "3s"

    # Страховка для пользователя: соединения никогда не вытесняются у него, даже под давлением.
    # Сумма по пользователям должна быть <= max_db_connections.
    min_guaranteed_pool_size: 5

    # Льготный период для вытеснения: соединения младше этого возраста не вытесняются.
    # Защищает от колебания, когда нагрузка кратковременно простаивает.
    min_connection_lifetime: "30s"

    users:
      - username: "fast_app"
        password: "md5..."
        pool_size: 40

      - username: "batch_job"
        password: "md5..."
        pool_size: 60

Эффективный потолок: max_db_connections + reserve_pool_size = 96. Резерв поглощает доли секундные пики; если пик затягивается, включается вытеснение.

Как выбирается донор

Когда пользователь запрашивает новый бэкенд, а лимит уже достигнут:

  1. Найти кандидатов с idle-соединениями. Пользователь, у которого все соединения активны, не может стать донором — его работа в полёте.
  2. Пропустить защищённых. Пользователь ниже min_guaranteed_pool_size исключается.
  3. Пропустить недавно созданные соединения. Соединения младше min_connection_lifetime не вытесняются (это снижает колебание при коротких idle-промежутках).
  4. Ранжировать по излишку. Пользователи с наибольшим числом idle-соединений сверх min_guaranteed_pool_size получают высший ранг.
  5. Tiebreaker — p95 времени транзакции. Среди пулов с одинаковым излишком первым отдаёт медленный. Их соединения, скорее всего, простаивали потому, что следующий запрос ещё готовится на стороне приложения.

Выбранное idle-соединение закрывается; запрашивающий пользователь получает свежее соединение из PostgreSQL.

Observability

SHOW POOL_COORDINATOR показывает текущее состояние по каждой базе:

database    | max_db_conn | current | reserve_size | reserve_used | evictions | reserve_acq | exhaustions
shared_db   | 80          | 78      | 16           | 2            | 142       | 18          | 0
  • evictions быстро растёт — один пользователь голодает раз за разом. Поднимите max_db_connections или задайте этому пользователю min_guaranteed_pool_size.
  • reserve_acq высокий — всплески нормальны, но размер занижен; рассмотрите повышение max_db_connections вместо опоры на резерв.
  • exhaustions ненулевой — резерв тоже был полным. Клиенты упёрлись в query_wait_timeout, ожидая бэкенд. Поднимите лимит.

Prometheus: pg_doorman_pool_coordinator{type="..."} (gauge) и pg_doorman_pool_coordinator_total{type="evictions|reserve_acquisitions|exhaustions"} (counter). Смотрите Команды администратора и Справочник Prometheus.

Оговорки

  • Координатор работает только внутри одного пула (одной базы). Кросс-пуловые / кросс-баз ограничения не поддерживаются.
  • Вытеснение выбирает idle-соединения; пользователь, удерживающий все соединения в длинных транзакциях, не может стать донором, и другие пользователи могут голодать. Если ваша форма нагрузки именно такая — поднимите max_db_connections или разделите нагрузки.
  • min_guaranteed_pool_size — это нижняя граница для вытеснения, а не min_pool_size для прогрева. Эти соединения пул всё равно создаёт по требованию.
  • Задавать max_db_connections без min_guaranteed_pool_size — это режим PgBouncer: работает, но мелкие пользователи голодают под давлением. Для общих баз всегда задавайте оба.

Куда дальше