<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>karandashov.com – Infra</title><link>https://karandashov.com/tags/infra/</link><description>Recent content in Infra on karandashov.com</description><generator>Hugo -- gohugo.io</generator><language>ru</language><managingEditor>vlad.karandashov.tech@gmail.com (Влад Карандашов)</managingEditor><webMaster>vlad.karandashov.tech@gmail.com (Влад Карандашов)</webMaster><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://karandashov.com/tags/infra/index.xml" rel="self" type="application/rss+xml"/><item><title>PgBouncer: лекарство с побочными эффектами. Часть 2 - конфигурация и эксплуатация</title><link>https://karandashov.com/posts/2026-06-26-pgbouncer-2/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><author>vlad.karandashov.tech@gmail.com (Влад Карандашов)</author><guid>https://karandashov.com/posts/2026-06-26-pgbouncer-2/</guid><description>
&lt;p&gt;&lt;figure&gt;
&lt;img src="https://karandashov.com/posts/2026-06-26-pgbouncer-2/cover.png" title="PgBouncer: как не выстрелить в ногу при эксплуатации" alt="PgBouncer: лекарство с побочными эффектами. Часть 2 - конфигурация и эксплуатация" loading="lazy" /&gt;
&lt;figcaption&gt;PgBouncer: как не выстрелить в ногу при эксплуатации&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;В &lt;a
href="https://karandashov.com/posts/2026-06-25-pgbouncer-1/"&gt;первой части&lt;/a&gt; мы разобрали базовую механику PgBouncer: client/server connections,
режимы pooling, ограничения transaction pooling и prepared statements.&lt;/p&gt;
&lt;p&gt;Теперь более приземленная часть. Какой конфиг брать за стартовую точку, как выбирать размеры пулов, какие таймауты
согласовать с HikariCP, что мониторить и где PgBouncer ставить в production.&lt;/p&gt;
&lt;div class="hextra-cards hx:mt-4 hx:gap-4 hx:grid not-prose" style="--hextra-cards-grid-cols: 3;"&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"href="https://karandashov.com/posts/2026-06-25-pgbouncer-1/"
&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/&gt;&lt;/svg&gt;Часть 1: обзор&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Pooling modes, session state и prepared statements&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"href="https://www.pgbouncer.org/config.html"
target="_blank" rel="noreferrer"&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/&gt;&lt;/svg&gt;PgBouncer config&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Официальное описание параметров&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;Стартовая точка для конфига&lt;span class="hx:absolute hx:-mt-20" id="стартовая-точка-для-конфига"&gt;&lt;/span&gt;
&lt;a href="#%d1%81%d1%82%d0%b0%d1%80%d1%82%d0%be%d0%b2%d0%b0%d1%8f-%d1%82%d0%be%d1%87%d0%ba%d0%b0-%d0%b4%d0%bb%d1%8f-%d0%ba%d0%be%d0%bd%d1%84%d0%b8%d0%b3%d0%b0" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Ниже я привел не идеальный production config на все случаи жизни. Такого не бывает. Но это нормальная стартовая точка для backend
сервисов, которые хотят использовать &lt;code&gt;transaction&lt;/code&gt; pooling, не гонять DDL через PgBouncer и оставить себе пространство
для мониторинга.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;pgbouncer.ini&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[databases]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Пул для прикладного runtime-трафика.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; В production пароль лучше не класть прямо в строку базы.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; PgBouncer может брать данные из auth_file/auth_query, а PostgreSQL должен принимать выбранную схему аутентификации.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app_db&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;host=postgres-primary port=5432 dbname=app_db \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; pool_mode=transaction pool_size=20 reserve_pool_size=5 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; connect_query=&amp;#39;SET search_path TO app_schema&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[pgbouncer]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Сетевой интерфейс PgBouncer.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;listen_addr&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;0.0.0.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;listen_port&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;6432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Для production лучше scram-sha-256 или hba/auth_query.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; plain годится для локального демо.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;auth_type&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;scram-sha-256&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;auth_file&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/pgbouncer/userlist.txt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;admin_users&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;pgbouncer_admin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;stats_users&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;pgbouncer_monitor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Кажется уже обсудили.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;pool_mode&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;transaction&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Сколько клиентских соединений PgBouncer готов принять.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Это иожет быть гораздо больше числа реальных соединений к PostgreSQL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;max_client_conn&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Размер server pool на пару database/user.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;default_pool_size&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;reserve_pool_size&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;reserve_pool_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Предохранители от случайного съедания всей базы одним пользователем или одной базой.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;max_db_connections&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;25&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;max_user_connections&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;25&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Поддержка protocol-level prepared statements в transaction pooling.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;max_prepared_statements&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; pgJDBC часто присылает extra_float_digits как startup parameter.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ignore_startup_parameters&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;extra_float_digits&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; В transaction pooling нельзя полагаться на session reset.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;server_reset_query&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;server_reset_query_always&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Таймауты server connections к PostgreSQL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;server_idle_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;server_lifetime&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1800&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Очередь ожидания свободного server connection.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;query_wait_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Защита от зависших транзакций и медленного login.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;idle_transaction_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;client_login_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;; Логи и агрегированная статистика.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;log_connections&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;log_disconnections&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;log_pooler_errors&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;log_stats&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Что здесь важно:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pool_size&lt;/code&gt; считается на пару database/user.
Если у вас много пользователей или баз, суммарный лимит может оказаться выше, чем кажется.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_db_connections&lt;/code&gt; и &lt;code&gt;max_user_connections&lt;/code&gt; нужны как предохранители.
Один забытый пользователь не должен съесть весь PostgreSQL.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reserve_pool_size&lt;/code&gt; помогает пережить короткий всплеск, но не должен маскировать постоянную нехватку основного пула.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server_reset_query&lt;/code&gt; в transaction pooling обычно не используется.
Документация прямо говорит, что в transaction mode клиенты не должны полагаться на session-based features.&lt;/li&gt;
&lt;li&gt;DDL, миграции, &lt;code&gt;CREATE EXTENSION&lt;/code&gt;, &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;, codegen и ручной maintenance лучше вести прямым
подключением к PostgreSQL, а не через этот runtime pool.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Для локального стенда можно упростить auth и положить &lt;code&gt;user/password&lt;/code&gt; в строку базы. Для production так делать не надо:
секреты потом придется ротировать, а PgBouncer — отдельный сервер аутентификации со своей процедурой обслуживания.&lt;/p&gt;
&lt;h2&gt;Как выбирать размеры пулов&lt;span class="hx:absolute hx:-mt-20" id="как-выбирать-размеры-пулов"&gt;&lt;/span&gt;
&lt;a href="#%d0%ba%d0%b0%d0%ba-%d0%b2%d1%8b%d0%b1%d0%b8%d1%80%d0%b0%d1%82%d1%8c-%d1%80%d0%b0%d0%b7%d0%bc%d0%b5%d1%80%d1%8b-%d0%bf%d1%83%d0%bb%d0%be%d0%b2" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;При подборе размеров можно идти в таком порядке:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Сначала ограничить локальный пул в приложении: &lt;code&gt;maximumPoolSize&lt;/code&gt; не должен быть сильно больше реальной потребности сервиса.&lt;/li&gt;
&lt;li&gt;Оценить, сколько ваша инсталляция PostgreSQL реально переваривает backend-процессов под нагрузкой.&lt;/li&gt;
&lt;li&gt;Поставить &lt;code&gt;pool_size&lt;/code&gt; PgBouncer ниже этого числа, оставив место для админских подключений, миграций, maintenance и фоновых задач.&lt;/li&gt;
&lt;li&gt;Добавить небольшой &lt;code&gt;reserve_pool_size&lt;/code&gt;, если бывают короткие всплески.&lt;/li&gt;
&lt;li&gt;Дальше мониторить wait time и tail latency, а не только количество открытых соединений.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;И не надо путать &lt;code&gt;max_client_conn&lt;/code&gt; с емкостью базы. Большое значение &lt;code&gt;max_client_conn&lt;/code&gt; означает, что PgBouncer примет
много клиентов. Это не значит, что PostgreSQL внезапно стал готов выполнять столько же запросов одновременно.
Запросы просто будут ожидать в очереди.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Помните: PgBouncer не решает проблемы плохих запросов.&lt;/strong&gt; Почитайте домкликовский разбор
&lt;a
href="https://habr.com/ru/companies/domclick/articles/548450/"target="_blank" rel="noopener"&gt;«Выдерни шнур, выдави стекло»&lt;/a&gt;.
Если запрос стал медленным из-за плана, статистики, временных файлов или новых параметров — пулер не вылечит причину.
Он только скроет ее за очередь.&lt;/p&gt;
&lt;h2&gt;Таймауты: PgBouncer и внутренний пул приложения должны договориться&lt;span class="hx:absolute hx:-mt-20" id="таймауты-pgbouncer-и-внутренний-пул-приложения-должны-договориться"&gt;&lt;/span&gt;
&lt;a href="#%d1%82%d0%b0%d0%b9%d0%bc%d0%b0%d1%83%d1%82%d1%8b-pgbouncer-%d0%b8-%d0%b2%d0%bd%d1%83%d1%82%d1%80%d0%b5%d0%bd%d0%bd%d0%b8%d0%b9-%d0%bf%d1%83%d0%bb-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d1%8f-%d0%b4%d0%be%d0%bb%d0%b6%d0%bd%d1%8b-%d0%b4%d0%be%d0%b3%d0%be%d0%b2%d0%be%d1%80%d0%b8%d1%82%d1%8c%d1%81%d1%8f" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Таймауты — это место, где PgBouncer легко превратить в генератор странных разрывов соединений.
Они спасают систему от зависших клиентов, но при неправильных значениях дают неожиданные ошибки.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;server_idle_timeout&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="server_idle_timeout"&gt;&lt;/span&gt;
&lt;a href="#server_idle_timeout" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;server_idle_timeout&lt;/code&gt; закрывает server connection к PostgreSQL, если он простаивает дольше заданного времени. Дефолт — 600 секунд.&lt;/p&gt;
&lt;p&gt;Слишком маленькое значение приводит к постоянному циклу:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;load spike → PgBouncer opens server connections&lt;/li&gt;
&lt;li&gt;short idle → PgBouncer closes them&lt;/li&gt;
&lt;li&gt;next spike → PgBouncer opens them again&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PostgreSQL снова тратит ресурсы на login, auth, backend process, прогрев кэшей.
В мониторинге это будет похоже на зубчатый график server connections.&lt;/p&gt;
&lt;p&gt;Слишком большое значение держит лишние backend-процессы в PostgreSQL даже после того, как нагрузка ушла.&lt;/p&gt;
&lt;p&gt;Стартовая рекомендация: не ставить секунды «на всякий случай». Для web-нагрузки часто разумнее минуты: условные
300–600 секунд. Если нагрузка приходит редкими большими пачками, значение надо подбирать по реальному профилю.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;server_lifetime&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="server_lifetime"&gt;&lt;/span&gt;
&lt;a href="#server_lifetime" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;server_lifetime&lt;/code&gt; закрывает неиспользуемое server connection, если оно прожило дольше заданного времени. Дефолт — 3600 секунд.&lt;/p&gt;
&lt;p&gt;Эта настройка помогает не держать backend-процессы бесконечно. В старом соединении могут накопиться prepared statements,
кэши, relcache, session-level мусор от неидеального приложения. Но слишком короткий lifetime снова дает много накладных
расходов.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;query_wait_timeout&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="query_wait_timeout"&gt;&lt;/span&gt;
&lt;a href="#query_wait_timeout" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;query_wait_timeout&lt;/code&gt; ограничивает время, которое query ждет свободный server connection. Если за это время PgBouncer не
назначил backend, клиентское соединение отключается. Дефолт — 120 секунд.&lt;/p&gt;
&lt;p&gt;120 секунд для web API часто слишком много. Пользователь уже ушел, upstream gateway уже вернул timeout, а PgBouncer все
еще честно держит запрос в очереди. Потом он наконец выполнится или отвалится, но приложению от этого легче не станет.&lt;/p&gt;
&lt;p&gt;Настраивайте &lt;code&gt;query_wait_timeout&lt;/code&gt; от SLA запроса. Если API должен отвечать за 2 секунды, нет смысла держать запрос в
очереди PgBouncer минуту. Лучше получить раннюю ошибку, заалертиться по saturation и чинить причину.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;idle_transaction_timeout&lt;/code&gt; и &lt;code&gt;transaction_timeout&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="idle_transaction_timeout-и-transaction_timeout"&gt;&lt;/span&gt;
&lt;a href="#idle_transaction_timeout-%d0%b8-transaction_timeout" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;idle_transaction_timeout&lt;/code&gt; отключает клиента, если он слишком долго сидит в &lt;code&gt;idle in transaction&lt;/code&gt;.
&lt;code&gt;transaction_timeout&lt;/code&gt; ограничивает общее время состояния &lt;code&gt;in transaction&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В transaction pooling это особенно полезно. Пока клиент держит транзакцию открытой, он держит server connection. Один
забытый код вроде «открыли транзакцию, сходили в другой сервис, подождали ответ, потом продолжили SQL» быстро съедает
пул.&lt;/p&gt;
&lt;p&gt;Я бы не начинал с агрессивных значений на первом деплое. Сначала включить мониторинг, найти реальные длинные транзакции,
починить код, потом ставить защитные лимиты. Но в целом это отличная гигиена: разработчики лишний раз подумают, что
именно происходит внутри &lt;code&gt;@Transactional&lt;/code&gt; метода.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;client_idle_timeout&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="client_idle_timeout"&gt;&lt;/span&gt;
&lt;a href="#client_idle_timeout" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;client_idle_timeout&lt;/code&gt; закрывает idle client connections. Документация советует держать его больше client-side connection
lifetime и использовать в основном для сетевых проблем.&lt;/p&gt;
&lt;p&gt;Для HikariCP это означает: не ставьте &lt;code&gt;client_idle_timeout&lt;/code&gt; меньше, чем пул ожидает жить со своим соединением к
PgBouncer. Иначе Hikari будет получать закрытые соединения извне, чаще валидировать и переоткрывать их.&lt;/p&gt;
&lt;p&gt;В &lt;a
href="https://github.com/brettwooldridge/HikariCP"target="_blank" rel="noopener"&gt;документации HikariCP&lt;/a&gt; для &lt;code&gt;maxLifetime&lt;/code&gt; есть похожая логика: значение
должно быть на несколько секунд меньше внешнего лимита жизни соединения. Если где-то между приложением и PgBouncer есть
load balancer, NAT или proxy idle timeout, Hikari должен узнать об этом раньше пользователя.&lt;/p&gt;
&lt;p&gt;Примерный набор для приложения:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;application.yaml&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spring&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;datasource&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hikari&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;maximum-pool-size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;minimum-idle&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;connection-timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;max-lifetime&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1700000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;keepalive-time&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;120000&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;И в PgBouncer:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;pgbouncer.ini&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;server_idle_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;server_lifetime&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1800&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;query_wait_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;idle_transaction_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;30&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Не копируйте бездумно, смотрите на смысл:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hikari &lt;code&gt;connection-timeout&lt;/code&gt; отвечает за ожидание свободного соединения внутри приложения.&lt;/li&gt;
&lt;li&gt;PgBouncer &lt;code&gt;query_wait_timeout&lt;/code&gt; отвечает за ожидание свободного server connection к PostgreSQL.&lt;/li&gt;
&lt;li&gt;Hikari &lt;code&gt;max-lifetime&lt;/code&gt; и &lt;code&gt;keepalive-time&lt;/code&gt; относятся к client connection до PgBouncer.&lt;/li&gt;
&lt;li&gt;PgBouncer &lt;code&gt;server_idle_timeout&lt;/code&gt; и &lt;code&gt;server_lifetime&lt;/code&gt; относятся к server connection до PostgreSQL.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Когда эти уровни путают, начинаются лишние reconnect storm.&lt;/p&gt;
&lt;h2&gt;Мониторинг&lt;span class="hx:absolute hx:-mt-20" id="мониторинг"&gt;&lt;/span&gt;
&lt;a href="#%d0%bc%d0%be%d0%bd%d0%b8%d1%82%d0%be%d1%80%d0%b8%d0%bd%d0%b3" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;У PgBouncer есть admin database &lt;code&gt;pgbouncer&lt;/code&gt;, где доступны &lt;code&gt;SHOW STATS&lt;/code&gt;, &lt;code&gt;SHOW POOLS&lt;/code&gt;, &lt;code&gt;SHOW CLIENTS&lt;/code&gt;, &lt;code&gt;SHOW SERVERS&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Что смотреть в первую очередь:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cl_waiting&lt;/code&gt; в &lt;code&gt;SHOW POOLS&lt;/code&gt; - клиенты ждут свободный server connection;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxwait&lt;/code&gt; - сколько ждет самый старый клиент в очереди;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sv_active&lt;/code&gt;, &lt;code&gt;sv_idle&lt;/code&gt;, &lt;code&gt;sv_used&lt;/code&gt;, &lt;code&gt;sv_login&lt;/code&gt; - состояние server connections;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;total_wait_time&lt;/code&gt; / &lt;code&gt;avg_wait_time&lt;/code&gt; в &lt;code&gt;SHOW STATS&lt;/code&gt; - накопленное и среднее ожидание backend connection;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;avg_xact_time&lt;/code&gt; и &lt;code&gt;avg_query_time&lt;/code&gt; - насколько транзакции длиннее самих запросов;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;total_client_parse_count&lt;/code&gt;, &lt;code&gt;total_server_parse_count&lt;/code&gt;, &lt;code&gt;total_bind_count&lt;/code&gt; - что происходит с prepared statements;&lt;/li&gt;
&lt;li&gt;количество ошибок в логах: лимиты, login failures, query timeouts, pooler errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Мне нравится подход из статьи
&lt;a
href="https://habr.com/ru/companies/okmeter/articles/420429/"target="_blank" rel="noopener"&gt;USE, RED, PgBouncer, его настройки и мониторинг&lt;/a&gt;.
Там PgBouncer разбирают по USE:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Utilization: насколько заняты server connections, сколько времени они проводят в query/transaction.&lt;/li&gt;
&lt;li&gt;Saturation: есть ли &lt;code&gt;cl_waiting&lt;/code&gt;, растет ли &lt;code&gt;maxwait&lt;/code&gt;, появляется ли &lt;code&gt;total_wait_time&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Errors: отказы по лимитам, таймауты, ошибки авторизации, pooler errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Отдельно полезно смотреть разницу между &lt;code&gt;total_xact_time&lt;/code&gt; и &lt;code&gt;total_query_time&lt;/code&gt;.
Если транзакции заметно длиннее запросов, приложение держит соединение, но не выполняет SQL.
Для transaction pooling это по сути налог на емкость пула.&lt;/p&gt;
&lt;p&gt;В production метрики PgBouncer надо смотреть рядом с:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hikari active/idle/pending connections;&lt;/li&gt;
&lt;li&gt;latency HTTP endpoints;&lt;/li&gt;
&lt;li&gt;PostgreSQL &lt;code&gt;pg_stat_activity&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;PostgreSQL locks;&lt;/li&gt;
&lt;li&gt;CPU PostgreSQL и PgBouncer;&lt;/li&gt;
&lt;li&gt;количеством backend-процессов;&lt;/li&gt;
&lt;li&gt;p95/p99 query latency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Иначе легко перепутать причину и следствие. &lt;code&gt;cl_waiting&lt;/code&gt; может расти потому, что &lt;code&gt;pool_size&lt;/code&gt; мал. А может потому, что
один SQL стал в 20 раз медленнее и занял все server connections.&lt;/p&gt;
&lt;h2&gt;Production: где поставить PgBouncer&lt;span class="hx:absolute hx:-mt-20" id="production-где-поставить-pgbouncer"&gt;&lt;/span&gt;
&lt;a href="#production-%d0%b3%d0%b4%d0%b5-%d0%bf%d0%be%d1%81%d1%82%d0%b0%d0%b2%d0%b8%d1%82%d1%8c-pgbouncer" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Есть несколько типичных схем.&lt;/p&gt;
&lt;h3&gt;PgBouncer рядом с приложением&lt;span class="hx:absolute hx:-mt-20" id="pgbouncer-рядом-с-приложением"&gt;&lt;/span&gt;
&lt;a href="#pgbouncer-%d1%80%d1%8f%d0%b4%d0%be%d0%bc-%d1%81-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d0%b5%d0%bc" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Sidecar или локальный процесс около приложения уменьшает latency на client connection и хорошо подходит для
короткоживущих клиентов. Но каждый экземпляр приложения получает свой PgBouncer, а значит надо внимательно считать
суммарные server connections к PostgreSQL.&lt;/p&gt;
&lt;p&gt;Если 20 pod&amp;rsquo;ов и у каждого &lt;code&gt;pool_size=20&lt;/code&gt;, база потенциально увидит 400 server connections. В этом случае PgBouncer
перестает быть глобальным ограничителем.&lt;/p&gt;
&lt;h3&gt;PgBouncer как отдельный сервис&lt;span class="hx:absolute hx:-mt-20" id="pgbouncer-как-отдельный-сервис"&gt;&lt;/span&gt;
&lt;a href="#pgbouncer-%d0%ba%d0%b0%d0%ba-%d0%be%d1%82%d0%b4%d0%b5%d0%bb%d1%8c%d0%bd%d1%8b%d0%b9-%d1%81%d0%b5%d1%80%d0%b2%d0%b8%d1%81" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Один или несколько PgBouncer перед PostgreSQL лучше агрегируют нагрузку от многих приложений. Проще контролировать общий
лимит соединений к базе, проще мониторить.&lt;/p&gt;
&lt;p&gt;Цена - еще один сетевой hop и отдельная точка отказа. Нужны HA, rolling restart, health checks, понятная схема failover.&lt;/p&gt;
&lt;p&gt;Официальный FAQ PgBouncer аккуратно отвечает на вопрос «ставить на web server или database server»: зависит от нагрузки
и модели failover. То есть универсальной схемы нет.&lt;/p&gt;
&lt;h3&gt;Несколько PgBouncer под разные профили&lt;span class="hx:absolute hx:-mt-20" id="несколько-pgbouncer-под-разные-профили"&gt;&lt;/span&gt;
&lt;a href="#%d0%bd%d0%b5%d1%81%d0%ba%d0%be%d0%bb%d1%8c%d0%ba%d0%be-pgbouncer-%d0%bf%d0%be%d0%b4-%d1%80%d0%b0%d0%b7%d0%bd%d1%8b%d0%b5-%d0%bf%d1%80%d0%be%d1%84%d0%b8%d0%bb%d0%b8" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Иногда один общий PgBouncer становится узким местом. Разные приложения могут иметь разный профиль:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;короткие OLTP-запросы;&lt;/li&gt;
&lt;li&gt;длинные read-only отчеты;&lt;/li&gt;
&lt;li&gt;фоновые batch-job&amp;rsquo;ы;&lt;/li&gt;
&lt;li&gt;админские операции;&lt;/li&gt;
&lt;li&gt;отдельные listener&amp;rsquo;ы для &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;сервисы с разными требованиями к latency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;В таком случае разумно разделить ресурсы: один PgBouncer или один pool для коротких runtime-запросов, другой — для
длинных операций, третий — для отдельной группы приложений. Это не обязательно должны быть разные инстансы; иногда
достаточно разных database aliases с разными &lt;code&gt;pool_size&lt;/code&gt;, &lt;code&gt;max_db_connections&lt;/code&gt; и &lt;code&gt;query_wait_timeout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Смысл простой: медленный batch не должен съедать server connections у пользовательского API. А пользовательский API не
должен мешать maintenance-задачам, которым нужен отдельный прямой доступ к PostgreSQL.&lt;/p&gt;
&lt;h3&gt;Несколько PgBouncer и &lt;code&gt;so_reuseport&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="несколько-pgbouncer-и-so_reuseport"&gt;&lt;/span&gt;
&lt;a href="#%d0%bd%d0%b5%d1%81%d0%ba%d0%be%d0%bb%d1%8c%d0%ba%d0%be-pgbouncer-%d0%b8-so_reuseport" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;PgBouncer исторически однопоточный: один процесс в основном использует одно CPU-ядро. На большой нагрузке это может стать
бутылочным горлышком, особенно с включенным prepared statement tracking, где PgBouncer разбирает и переписывает сообщения.&lt;/p&gt;
&lt;p&gt;Если не хотите делать полноценные разные экземпляры на разные группы сервисов —
можно запускать несколько процессов на одном порту через &lt;code&gt;so_reuseport&lt;/code&gt;.
В документации это же используется как часть rolling restart сценария:
несколько процессов слушают один порт, перезапускаются по одному, клиенты переподключаются без полного простоя.&lt;/p&gt;
&lt;h2&gt;Failover&lt;span class="hx:absolute hx:-mt-20" id="failover"&gt;&lt;/span&gt;
&lt;a href="#failover" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Отдельно стоит сказать про работу в отказоустойчивом режиме.&lt;/p&gt;
&lt;p&gt;PgBouncer не делает полноценный PostgreSQL failover. Он может перечитать DNS, принять &lt;code&gt;RELOAD&lt;/code&gt;, выполнить &lt;code&gt;RECONNECT&lt;/code&gt;,
закрыть старые server connections и открыть новые. Но решение «кто теперь primary» принимает не PgBouncer.&lt;/p&gt;
&lt;p&gt;Если за PgBouncer стоит HAProxy, Patroni, managed PostgreSQL или другой слой маршрутизации, надо отдельно проверять:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;что происходит с уже открытыми server connections;&lt;/li&gt;
&lt;li&gt;как быстро PgBouncer узнает новый адрес;&lt;/li&gt;
&lt;li&gt;нужно ли выполнять &lt;code&gt;RECONNECT&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;что видят приложения во время переключения;&lt;/li&gt;
&lt;li&gt;какие ошибки retry-логика считает безопасными.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Лучше один раз отрепетировать failover на стенде, чем впервые узнать реальное поведение PgBouncer ночью.&lt;/p&gt;
&lt;h2&gt;Где PgBouncer не нужен&lt;span class="hx:absolute hx:-mt-20" id="где-pgbouncer-не-нужен"&gt;&lt;/span&gt;
&lt;a href="#%d0%b3%d0%b4%d0%b5-pgbouncer-%d0%bd%d0%b5-%d0%bd%d1%83%d0%b6%d0%b5%d0%bd" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;PgBouncer не надо тащить в каждый проект просто потому что «в проде так делают».&lt;/p&gt;
&lt;p&gt;Он может быть лишним, если:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;у вас один маленький сервис и &lt;code&gt;max_connections&lt;/code&gt; не проблема;&lt;/li&gt;
&lt;li&gt;вы достаточно богаты и поднимаете отдельные БД на каждый чих;&lt;/li&gt;
&lt;li&gt;приложение уже держит аккуратный маленький pool, а реплик мало;&lt;/li&gt;
&lt;li&gt;workload завязан на session state;&lt;/li&gt;
&lt;li&gt;нужно много &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;используются server-side cursors между транзакциями;&lt;/li&gt;
&lt;li&gt;миграции, аналитика и runtime перемешаны в одном канале;&lt;/li&gt;
&lt;li&gt;проще поднять managed PostgreSQL с подходящим лимитом соединений.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Иногда первый правильный шаг — уменьшить Hikari &lt;code&gt;maximumPoolSize&lt;/code&gt;, найти длинные транзакции и добавить индекс.
PgBouncer не должен становиться способом отложить работу по оптимизации.&lt;/p&gt;
&lt;h2&gt;Итог&lt;span class="hx:absolute hx:-mt-20" id="итог"&gt;&lt;/span&gt;
&lt;a href="#%d0%b8%d1%82%d0%be%d0%b3" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;PgBouncer хорош, когда у вас много клиентов и ограниченное число PostgreSQL backend-процессов. Он особенно полезен в
микросервисной среде, где каждый сервис приносит свой connection pool и сумма этих пулов быстро становится больше
разумного &lt;code&gt;max_connections&lt;/code&gt;. А ресурсы БД имеет смысл перераспределять.&lt;/p&gt;
&lt;p&gt;PgBouncer не отменяет пул внутри приложения. HikariCP защищает сам сервис от лишней локальной конкуренции и затрат на
подключение, а PgBouncer защищает PostgreSQL от лишних соединений, процессов и затрат.&lt;/p&gt;
&lt;p&gt;Самый рабочий режим - &lt;code&gt;transaction pooling&lt;/code&gt;, но он требует дисциплины: никаких session-level ожиданий, миграции напрямую,
LISTEN отдельно, prepared statements настроены согласованно, таймауты замерены, мониторинг включен.&lt;/p&gt;
&lt;p&gt;Когда эта дисциплина есть, PgBouncer становится скучным и полезным инфраструктурным компонентом. Именно таким он и должен
быть.&lt;/p&gt;</description></item><item><title>PgBouncer: лекарство с побочными эффектами. Часть 1 - обзор</title><link>https://karandashov.com/posts/2026-06-25-pgbouncer-1/</link><pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate><author>vlad.karandashov.tech@gmail.com (Влад Карандашов)</author><guid>https://karandashov.com/posts/2026-06-25-pgbouncer-1/</guid><description>
&lt;p&gt;&lt;figure&gt;
&lt;img src="https://karandashov.com/posts/2026-06-25-pgbouncer-1/cover.png" title="PgBouncer: лекарство с побочными эффектами" alt="PgBouncer: лекарство с побочными эффектами" loading="lazy" /&gt;
&lt;figcaption&gt;PgBouncer: лекарство с побочными эффектами&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;Про PgBouncer обычно вспоминают в двух ситуациях.
Либо PostgreSQL уже начал ругаться на &lt;code&gt;too many connections&lt;/code&gt;, а приложение завопило
&lt;code&gt;PSQLException: FATAL: sorry, too many clients already&lt;/code&gt;.
Либо кто-то заранее посмотрел на количество pod&amp;rsquo;ов, умножил его на размер Hikari pool и тихо отложил кофе.&lt;/p&gt;
&lt;p&gt;На первый взгляд PgBouncer элегантно решает проблему: поставили маленький прокси перед PostgreSQL, направили туда
приложения, получили меньше настоящих соединений к базе. Но PgBouncer тем и опасен, что долго работает «прозрачно».
А потом в один день выясняется, что временная таблица пропала, &lt;code&gt;LISTEN&lt;/code&gt; не слушает, миграция оставила странную ошибку
prepared statement cache, а &lt;code&gt;SET search_path&lt;/code&gt; повел себя не как на локальной базе.&lt;/p&gt;
&lt;p&gt;Это первая обзорная часть: зачем PgBouncer нужен, почему он не заменяет connection pool внутри
приложения, чем отличаются &lt;code&gt;session&lt;/code&gt;, &lt;code&gt;transaction&lt;/code&gt; и &lt;code&gt;statement&lt;/code&gt; pooling, где ломается session state и что сейчас
происходит с prepared statements.&lt;/p&gt;
&lt;p&gt;Во второй части разберем эксплуатацию: стартовый конфиг, sizing, таймауты, мониторинг, production-топологии и failover.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;Примеры дальше в основном из Java-мира, поэтому часто встречаются HikariCP, pgJDBC и Spring Boot. Это не принципиально.
Та же логика работает для Hibernate, jOOQ, Spring Data JDBC, Go-сервисов, Python ORM и любого клиента PostgreSQL.
Меняются только названия драйверов и конкретные настройки.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Пример использования PgBouncer можно посмотреть &lt;a
href="https://gitlab.com/VladKarandashov/jooq-gradle-demo"target="_blank" rel="noopener"&gt;в этом демо примере&lt;/a&gt;.&lt;/p&gt;
&lt;div class="hextra-cards hx:mt-4 hx:gap-4 hx:grid not-prose" style="--hextra-cards-grid-cols: 3;"&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"href="https://www.pgbouncer.org/features.html"
target="_blank" rel="noreferrer"&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/&gt;&lt;/svg&gt;PgBouncer features&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Официальная карта режимов и несовместимых PostgreSQL-фич&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"href="https://karandashov.com/posts/2026-06-26-pgbouncer-2/"
&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/&gt;&lt;/svg&gt;Часть 2: эксплуатация&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Конфиг, sizing, таймауты, мониторинг и production&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;Что делает PgBouncer&lt;span class="hx:absolute hx:-mt-20" id="что-делает-pgbouncer"&gt;&lt;/span&gt;
&lt;a href="#%d1%87%d1%82%d0%be-%d0%b4%d0%b5%d0%bb%d0%b0%d0%b5%d1%82-pgbouncer" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;PostgreSQL обслуживает обычные клиентские соединения отдельными процессами. У соединения с базой есть память, session
state, кэши, prepared statements, параметры сессии, возможные временные объекты и открытая транзакция.&lt;/p&gt;
&lt;p&gt;Если у нас один монолит и пул с 10 соединениями, жизнь спокойная. Если у нас 40 экземпляров сервиса с таким же пулом,
получается уже до 400 соединений. Даже если большинство из них почти ничего не делает, PostgreSQL все равно должен их
держать: память, backend-процессы, планировщик ОС, служебные проверки, контекстные переключения.&lt;/p&gt;
&lt;p&gt;&lt;a
href="https://www.pgbouncer.org/usage.html"target="_blank" rel="noopener"&gt;Официальная документация PgBouncer&lt;/a&gt; описывает его как слой, через который клиент
подключается так же, как к PostgreSQL. С точки зрения приложения это обычный PostgreSQL port. Внутри PgBouncer принимает
клиентские соединения и держит собственный ограниченный набор серверных соединений к базе.&lt;/p&gt;
&lt;p&gt;В результате появляются два разных типа соединений:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;client connection - соединение приложения с PgBouncer;&lt;/li&gt;
&lt;li&gt;server connection - настоящее соединение PgBouncer с PostgreSQL.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Другими словами, HikariCP в условном Java-приложении управляет client connections к PgBouncer. PgBouncer управляет
server connections к PostgreSQL. Они стоят на разных этажах и решают разные задачи.&lt;/p&gt;
&lt;details class="hx:last-of-type:mb-0 hx:rounded-lg hx:bg-neutral-50 hx:dark:bg-neutral-800 hx:p-2 hx:mt-4 hx:group" &gt;
&lt;summary class="hx:flex hx:items-center hx:cursor-pointer hx:select-none hx:list-none hx:p-1 hx:rounded-sm hx:transition-colors hx:hover:bg-gray-100 hx:dark:hover:bg-neutral-800 hx:before:mr-1 hx:before:inline-block hx:before:transition-transform hx:before:content-[''] hx:dark:before:invert hx:rtl:before:rotate-180 hx:group-open:before:rotate-90"&gt;
&lt;strong class="hx:text-lg"&gt;Почему именно PgBouncer&lt;/strong&gt;
&lt;/summary&gt;
&lt;div class="hx:p-2 hx:overflow-hidden"&gt;
&lt;p&gt;Есть и другие подходы: Pgpool-II, PgCat, Odyssey и множество других.
Но PgBouncer выбирают из-за простоты и понятной зоны ответственности.&lt;/p&gt;
&lt;p&gt;Он не пытается быть балансировщиком запросов, репликатором, query router&amp;rsquo;ом и полноценной HA-платформой одновременно.
Он делает одну вещь: принимает много клиентских соединений и ограничивает число реальных соединений к PostgreSQL.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PgBouncer маленький и быстро стартует;&lt;/li&gt;
&lt;li&gt;хорошо совместим с большинством стандартных PostgreSQL-фич, поэтому приложения часто его не замечают;&lt;/li&gt;
&lt;li&gt;конфиг читается за вечер, а не за неделю;&lt;/li&gt;
&lt;li&gt;есть понятная admin console: &lt;code&gt;SHOW POOLS&lt;/code&gt;, &lt;code&gt;SHOW STATS&lt;/code&gt;, &lt;code&gt;SHOW CLIENTS&lt;/code&gt;, &lt;code&gt;SHOW SERVERS&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;легко поставить как отдельный сервис или рядом с приложением;&lt;/li&gt;
&lt;li&gt;давно используется в production и хорошо описан в документации.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Цена простоты тоже есть: PgBouncer использует однопоточную архитектуру и работает только на одном ядре процессора на один экземпляр.
Из-за этого при пиковых нагрузках PgBouncer может упираться в процессор.
Но такая архитектура обеспечивает отличную производительность и низкое потребление памяти для большинства стандартных задач.
К тому же есть весьма понятные способы обходить это ограничение.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h2&gt;Почему PgBouncer не заменяет connection pool внутри сервиса&lt;span class="hx:absolute hx:-mt-20" id="почему-pgbouncer-не-заменяет-connection-pool-внутри-сервиса"&gt;&lt;/span&gt;
&lt;a href="#%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-pgbouncer-%d0%bd%d0%b5-%d0%b7%d0%b0%d0%bc%d0%b5%d0%bd%d1%8f%d0%b5%d1%82-connection-pool-%d0%b2%d0%bd%d1%83%d1%82%d1%80%d0%b8-%d1%81%d0%b5%d1%80%d0%b2%d0%b8%d1%81%d0%b0" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Иногда PgBouncer воспринимают как внешний HikariCP: раз есть пул перед базой, можно убрать пул в приложении или сделать
его очень большим. Обычно это плохой план.&lt;/p&gt;
&lt;p&gt;Pool внутри сервиса отвечает за локальную дисциплину:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ограничивает количество потоков или virtual threads, которые одновременно полезут в базу;&lt;/li&gt;
&lt;li&gt;переиспользует уже открытые соединения;&lt;/li&gt;
&lt;li&gt;не тратит время бизнес-запроса на новое подключение;&lt;/li&gt;
&lt;li&gt;проверяет живость соединений;&lt;/li&gt;
&lt;li&gt;интегрируется с транзакциями фреймворка и ORM;&lt;/li&gt;
&lt;li&gt;дает метрики на уровне конкретного сервиса.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PgBouncer отвечает за другое:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;принимает много клиентских соединений от разных сервисов;&lt;/li&gt;
&lt;li&gt;не дает приложениям открыть слишком много реальных процессов в PostgreSQL;&lt;/li&gt;
&lt;li&gt;переиспользует настоящие соединения к базе между клиентами;&lt;/li&gt;
&lt;li&gt;перераспределяет ограниченный пул server connections между приложениями;&lt;/li&gt;
&lt;li&gt;ставит клиентов в очередь, если все server connections заняты.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Если убрать локальный пул и ходить в PgBouncer «на каждый запрос новым connection», мы снова заплатим за создание
JDBC-объекта, аутентификацию, TLS/сетевые расходы и лишние ошибки при всплесках. PostgreSQL будет защищен, но приложение
хуже проконтролирует собственную конкуренцию.&lt;/p&gt;
&lt;p&gt;Если оставить локальный пул, но поставить &lt;code&gt;maximum-pool-size&lt;/code&gt; в сотни «потому что PgBouncer все равно ограничит», мы
просто перенесем очередь из приложения в PgBouncer. Сервис будет держать много занятых worker threads или virtual
threads, а пользователи увидят задержку уже после того, как запрос добрался до базы.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Размер локального пула подбирается от профиля конкретного сервиса. Размер PgBouncer pool — от возможностей PostgreSQL и
общей нагрузки всех клиентов.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;PgBouncer не делает базу быстрее. Он помогает пережить большое количество клиентов и перераспределить нагрузку.
Но если запросы медленные, индексов нет, а транзакции держат locks по секундам, PgBouncer просто выстроит ваши проблемы
в аккуратную очередь.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Режимы pooling&lt;span class="hx:absolute hx:-mt-20" id="режимы-pooling"&gt;&lt;/span&gt;
&lt;a href="#%d1%80%d0%b5%d0%b6%d0%b8%d0%bc%d1%8b-pooling" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;У PgBouncer три основных режима. В документации они описаны как разные уровни &amp;ldquo;brutality&amp;rdquo;,
и эта шутка в документации довольно точно передает инженерный смысл.&lt;/p&gt;
&lt;h3&gt;Session pooling&lt;span class="hx:absolute hx:-mt-20" id="session-pooling"&gt;&lt;/span&gt;
&lt;a href="#session-pooling" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;session&lt;/code&gt; - самый спокойный режим и дефолт PgBouncer.&lt;/p&gt;
&lt;p&gt;Клиент подключился к PgBouncer, PgBouncer выдал ему server connection к PostgreSQL и держит эту пару до конца клиентской
сессии. Когда клиент отключается, server connection возвращается в пул.&lt;/p&gt;
&lt;p&gt;Плюсы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;почти все PostgreSQL-фичи работают как при прямом подключении;&lt;/li&gt;
&lt;li&gt;можно использовать &lt;code&gt;LISTEN&lt;/code&gt;, session-level advisory locks, временные таблицы, session variables;&lt;/li&gt;
&lt;li&gt;меньше сюрпризов для ORM и старого кода.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Минус очевидный: если приложение держит постоянные соединения, то экономия слабая. Десять соединений в одном экземпляре
приложения займут десять server connections в PgBouncer на все время жизни сервиса.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;session&lt;/code&gt; полезен, когда нужно снизить стоимость переподключений или обслужить короткоживущих клиентов, но нельзя ломать
session state.&lt;/p&gt;
&lt;h3&gt;Transaction pooling&lt;span class="hx:absolute hx:-mt-20" id="transaction-pooling"&gt;&lt;/span&gt;
&lt;a href="#transaction-pooling" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;transaction&lt;/code&gt; - самый популярный режим, ради которого и используют PgBouncer.&lt;/p&gt;
&lt;p&gt;Server connection выдается клиенту только на время транзакции. Транзакция закончилась — соединение возвращается в пул,
а следующая транзакция этого же клиента может попасть уже на другое соединение с PostgreSQL.&lt;/p&gt;
&lt;p&gt;Это дает главную экономию: приложение может держать много client connections к PgBouncer, но настоящих PostgreSQL
соединений будет заметно меньше. Особенно хорошо это работает, если сервисы делают короткие транзакции.&lt;/p&gt;
&lt;p&gt;Цена — потеря стабильной привязки клиента к одной PostgreSQL-сессии.
Если приложение работает классическими короткими запросами без session state, вы можете этого не почувствовать.
Более сложные ситуации требуют отдельной проверки.
В &lt;a
href="https://www.pgbouncer.org/features.html"target="_blank" rel="noopener"&gt;официальной таблице совместимости&lt;/a&gt; это сформулировано так: transaction
pooling специально разрывает ожидание клиента, что за ним закреплен один server connection. Использовать его можно,
только если приложение не опирается на session state.
Но не стоит пугаться, в современных микросервисах эти ситуации встречаются крайне редко.&lt;/p&gt;
&lt;h3&gt;Statement pooling&lt;span class="hx:absolute hx:-mt-20" id="statement-pooling"&gt;&lt;/span&gt;
&lt;a href="#statement-pooling" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;statement&lt;/code&gt; - самый агрессивный режим. Server connection возвращается в пул после каждого statement. Многошаговые
транзакции запрещены, потому что иначе PgBouncer сломал бы саму семантику транзакции.&lt;/p&gt;
&lt;p&gt;Для обычного backend это почти никогда не то, что нужно. Такой режим можно рассматривать для очень специфичной нагрузки:
много коротких независимых запросов в autocommit и полная уверенность, что приложению не нужны транзакции из нескольких
SQL-команд.&lt;/p&gt;
&lt;p&gt;Если сомневаетесь между &lt;code&gt;transaction&lt;/code&gt; и &lt;code&gt;statement&lt;/code&gt;, почти наверняка вам нужен &lt;code&gt;transaction&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Что ломается в transaction pooling&lt;span class="hx:absolute hx:-mt-20" id="что-ломается-в-transaction-pooling"&gt;&lt;/span&gt;
&lt;a href="#%d1%87%d1%82%d0%be-%d0%bb%d0%be%d0%bc%d0%b0%d0%b5%d1%82%d1%81%d1%8f-%d0%b2-transaction-pooling" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Окей, вероятнее всего, если вы задумались над PgBouncer, то вам нужен &lt;code&gt;transaction&lt;/code&gt; pooling. Но какие именно ограничения
в нем есть?&lt;/p&gt;
&lt;p&gt;Главный принцип простой: все, что живет дольше одной транзакции и привязано к PostgreSQL-сессии, под transaction pooling
становится подозрительным.&lt;/p&gt;
&lt;p&gt;Официальная таблица совместимости PgBouncer отдельно отмечает несколько важных случаев:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Возможность PostgreSQL&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;code&gt;session&lt;/code&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;code&gt;transaction&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;code&gt;SET&lt;/code&gt; / &lt;code&gt;RESET&lt;/code&gt; session state&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;нет&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;code&gt;LISTEN&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;нет&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;code&gt;NOTIFY&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;protocol-level prepared plans&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;да, при &lt;code&gt;max_prepared_statements &amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;SQL &lt;code&gt;PREPARE&lt;/code&gt; / &lt;code&gt;DEALLOCATE&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;нет&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;temp tables &lt;code&gt;ON COMMIT DROP&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;temp tables &lt;code&gt;PRESERVE ROWS&lt;/code&gt; / &lt;code&gt;DELETE ROWS&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;нет&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;session-level advisory locks&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;нет&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;code&gt;LOAD&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;да&lt;/td&gt;
&lt;td style="text-align: left"&gt;нет&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Это не произвольные ограничения именно PgBouncer. Это следствие того, что после завершения транзакции следующий
statement клиента может попасть на другую PostgreSQL-сессию.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;SET&lt;/code&gt;, &lt;code&gt;search_path&lt;/code&gt; и переменные&lt;span class="hx:absolute hx:-mt-20" id="set-search_path-и-переменные"&gt;&lt;/span&gt;
&lt;a href="#set-search_path-%d0%b8-%d0%bf%d0%b5%d1%80%d0%b5%d0%bc%d0%b5%d0%bd%d0%bd%d1%8b%d0%b5" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;В transaction pooling нельзя полагаться на обычный &lt;code&gt;SET something = ...&lt;/code&gt;, если это session-level изменение.&lt;/p&gt;
&lt;p&gt;Плохая идея:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;set&lt;/span&gt; search_path &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; surveys_schema;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; surveys;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Первый statement мог выполниться на одном server connection, второй — на другом. Даже если сегодня повезло, контракт уже
сломан.&lt;/p&gt;
&lt;p&gt;Варианты лучше:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;задавать &lt;code&gt;search_path&lt;/code&gt; на роли или базе в PostgreSQL;&lt;/li&gt;
&lt;li&gt;использовать &lt;code&gt;SET LOCAL&lt;/code&gt; внутри транзакции;&lt;/li&gt;
&lt;li&gt;указывать схему явно;&lt;/li&gt;
&lt;li&gt;для PgBouncer использовать &lt;code&gt;connect_query&lt;/code&gt;, если это подходит вашей модели;&lt;/li&gt;
&lt;li&gt;для параметров, которые PostgreSQL умеет репортить клиенту, смотреть в сторону &lt;code&gt;track_extra_parameters&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Пример настройки PgBouncer с помощью &lt;code&gt;connect_query&lt;/code&gt;:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;pgbouncer.ini&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;jooq_demo_db&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;host=postgres port=5432 dbname=jooq_demo_db user=test password=test \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; pool_mode=transaction pool_size=20 reserve_pool_size=5 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; connect_query=&amp;#39;SET search_path TO surveys_schema&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Это лучше, чем выполнять &lt;code&gt;SET search_path&lt;/code&gt; из приложения в начале каждой «сессии», которой в transaction pooling
фактически нет. Но и здесь надо понимать границы: &lt;code&gt;connect_query&lt;/code&gt; выполняется при создании server connection, а не перед
каждым клиентским запросом.&lt;/p&gt;
&lt;h3&gt;Временные таблицы&lt;span class="hx:absolute hx:-mt-20" id="временные-таблицы"&gt;&lt;/span&gt;
&lt;a href="#%d0%b2%d1%80%d0%b5%d0%bc%d0%b5%d0%bd%d0%bd%d1%8b%d0%b5-%d1%82%d0%b0%d0%b1%d0%bb%d0%b8%d1%86%d1%8b" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Временные таблицы живут в PostgreSQL-сессии. Поэтому обычный сценарий «создал temp table, потом в другой транзакции
дочитал из нее данные» не совместим с transaction pooling.&lt;/p&gt;
&lt;p&gt;Совместимый вариант — временная таблица, которая целиком живет внутри одной транзакции и умирает на &lt;code&gt;COMMIT&lt;/code&gt;:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;good.sql&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;begin&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;create&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;temporary&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;table&lt;/span&gt; tmp_ids
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id uuid
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) &lt;span style="color:#66d9ef"&gt;on&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;commit&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;drop&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; tmp_ids
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;values&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;018f0000-0000-7000-8000-000000000001&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; id &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; id &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; tmp_ids);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;commit&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Этот фрагмент нормальный для transaction pooling: &lt;code&gt;ON COMMIT DROP&lt;/code&gt; попадает в совместимую зону, потому что таблица
умирает вместе с транзакцией.&lt;/p&gt;
&lt;p&gt;Плохой вариант — создать temp table и ожидать, что она переживет несколько транзакций клиента:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;bad.sql&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;create&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;temporary&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;table&lt;/span&gt; tmp_ids(id uuid) &lt;span style="color:#66d9ef"&gt;on&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;commit&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;preserve&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;rows&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; tmp_ids
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;values&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;018f0000-0000-7000-8000-000000000001&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;commit&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; id &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; id &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; tmp_ids);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;После &lt;code&gt;COMMIT&lt;/code&gt; следующий запрос может попасть на другой server connection, где такой временной таблицы нет. Поэтому
&lt;code&gt;ON COMMIT PRESERVE ROWS&lt;/code&gt; и &lt;code&gt;ON COMMIT DELETE ROWS&lt;/code&gt; в официальной таблице совместимости помечены как несовместимые с
transaction pooling.&lt;/p&gt;
&lt;p&gt;Для прикладного кода это хороший повод спросить себя: а точно ли здесь нужна временная таблица? Часто ее можно заменить
CTE, &lt;code&gt;VALUES&lt;/code&gt;, массивом параметров, JSON-to-recordset или нормальной staging-таблицей с ключом batch/job.&lt;/p&gt;
&lt;h3&gt;LISTEN / NOTIFY&lt;span class="hx:absolute hx:-mt-20" id="listen--notify"&gt;&lt;/span&gt;
&lt;a href="#listen--notify" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;NOTIFY&lt;/code&gt; можно выполнить через transaction pooling. &lt;code&gt;LISTEN&lt;/code&gt; - уже нельзя.&lt;/p&gt;
&lt;p&gt;PostgreSQL сам описывает &lt;code&gt;LISTEN&lt;/code&gt; как регистрацию текущей session на notification channel. Регистрация очищается, когда
сессия заканчивается. PgBouncer в transaction mode не дает приложению стабильную server session, значит подписчик будет
сломан концептуально.&lt;/p&gt;
&lt;p&gt;Если приложению нужен &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;, делайте отдельный канал с уровнем session pooling или прямым подключением к
PostgreSQL. Для Java это особенно важно: pgJDBC получает notifications через &lt;code&gt;PGConnection&lt;/code&gt;, то есть listener и так
обычно живет на отдельном выделенном соединении.&lt;/p&gt;
&lt;h3&gt;Advisory locks&lt;span class="hx:absolute hx:-mt-20" id="advisory-locks"&gt;&lt;/span&gt;
&lt;a href="#advisory-locks" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Session-level advisory locks через transaction pooling использовать нельзя. Они привязаны к сессии, а сессия после
транзакции больше не принадлежит клиенту.&lt;/p&gt;
&lt;p&gt;Transaction-level advisory locks (&lt;code&gt;pg_advisory_xact_lock&lt;/code&gt;) обычно укладываются в модель transaction pooling, потому что
их жизненный цикл совпадает с транзакцией. Но это не повод превращать PgBouncer в распределенный lock manager. Если lock
держится долго, он занимает server connection и уменьшает полезную емкость пула.&lt;/p&gt;
&lt;p&gt;Если хочется отдельно освежить механику advisory locks, есть хороший разбор на Хабре:
&lt;a
href="https://habr.com/ru/companies/otus/articles/1015524/"target="_blank" rel="noopener"&gt;«Advisory Locks в PostgreSQL»&lt;/a&gt;. Для PgBouncer там особенно важна
разница между session-level и transaction-level блокировками.&lt;/p&gt;
&lt;h3&gt;DDL и миграции&lt;span class="hx:absolute hx:-mt-20" id="ddl-и-миграции"&gt;&lt;/span&gt;
&lt;a href="#ddl-%d0%b8-%d0%bc%d0%b8%d0%b3%d1%80%d0%b0%d1%86%d0%b8%d0%b8" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;DDL лучше не гонять через transaction pool. Не потому, что PgBouncer вообще не умеет DDL, а потому что миграции любят
ровно те вещи, которые плохо дружат с агрессивным pooling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;долгие транзакции;&lt;/li&gt;
&lt;li&gt;advisory locks;&lt;/li&gt;
&lt;li&gt;смена схемы под работающими prepared statements;&lt;/li&gt;
&lt;li&gt;команды, которым нужен особый transaction mode;&lt;/li&gt;
&lt;li&gt;расширения и session state;&lt;/li&gt;
&lt;li&gt;отдельные прямые проверки metadata.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Практичный паттерн такой:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;application.yaml&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spring&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;datasource&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jdbc:postgresql://pgbouncer-app:6432/app_db&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;flyway&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jdbc:postgresql://postgres:5432/app_db&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Runtime-трафик идет через PgBouncer. Flyway, Liquibase, &lt;code&gt;CREATE EXTENSION&lt;/code&gt;, &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt; и прочие
DDL-задачи идут напрямую в PostgreSQL.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;После DDL, который меняет форму результата prepared statements, можно получить &lt;code&gt;cached plan must not change result type&lt;/code&gt;.
В документации PgBouncer для &lt;code&gt;max_prepared_statements&lt;/code&gt; прямо советуют после таких миграций выполнять &lt;code&gt;RECONNECT&lt;/code&gt; в admin
console, чтобы серверные соединения переподготовили statements.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Prepared statements: старый страх и новая реальность&lt;span class="hx:absolute hx:-mt-20" id="prepared-statements-старый-страх-и-новая-реальность"&gt;&lt;/span&gt;
&lt;a href="#prepared-statements-%d1%81%d1%82%d0%b0%d1%80%d1%8b%d0%b9-%d1%81%d1%82%d1%80%d0%b0%d1%85-%d0%b8-%d0%bd%d0%be%d0%b2%d0%b0%d1%8f-%d1%80%d0%b5%d0%b0%d0%bb%d1%8c%d0%bd%d0%be%d1%81%d1%82%d1%8c" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Самая частая страшилка про PgBouncer: «transaction pooling несовместим с prepared statements».
Она пугала много лет, но сейчас ее надо произносить аккуратнее.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img src="https://karandashov.com/posts/2026-06-25-pgbouncer-1/additional.png" title="PgBouncer и Prepared Statement: друзья или враги" alt="PgBouncer и Prepared Statement: друзья или враги" loading="lazy" /&gt;
&lt;figcaption&gt;PgBouncer и Prepared Statement: друзья или враги&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;Есть два разных уровня:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SQL-level prepared statements: &lt;code&gt;PREPARE&lt;/code&gt;, &lt;code&gt;EXECUTE&lt;/code&gt;, &lt;code&gt;DEALLOCATE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Protocol-level prepared statements: то, что используют драйверы через PostgreSQL extended query protocol.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Первый вариант по-прежнему не работает нормально в transaction pooling. Если вы выполняете:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;bad.sql&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;prepare&lt;/span&gt; find_survey(uuid) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; surveys
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;$&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;execute&lt;/span&gt; find_survey(&lt;span style="color:#e6db74"&gt;&amp;#39;018f0000-0000-7000-8000-000000000001&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;то &lt;code&gt;PREPARE&lt;/code&gt; может случиться на одном server connection, а &lt;code&gt;EXECUTE&lt;/code&gt; - на другом. PgBouncer не переписывает SQL-level
&lt;code&gt;PREPARE/EXECUTE/DEALLOCATE&lt;/code&gt;, он отправляет их в PostgreSQL как есть.&lt;/p&gt;
&lt;p&gt;Второй вариант интереснее. Начиная с &lt;a
href="https://www.pgbouncer.org/changelog.html"target="_blank" rel="noopener"&gt;PgBouncer 1.21.0&lt;/a&gt;, появился tracking
protocol-level named prepared statements. В свежем PgBouncer
&lt;a
href="https://www.pgbouncer.org/config.html#max_prepared_statements"target="_blank" rel="noopener"&gt;&lt;code&gt;max_prepared_statements&lt;/code&gt;&lt;/a&gt; уже по умолчанию равен &lt;code&gt;200&lt;/code&gt;,
а не &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Официальная документация описывает механизм примерно так:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;клиент готовит statement под своим именем;&lt;/li&gt;
&lt;li&gt;PgBouncer хранит соответствие клиентского имени и внутреннего имени;&lt;/li&gt;
&lt;li&gt;одинаковый SQL получает общий internal name вида &lt;code&gt;PGBOUNCER_123&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;если текущий server connection еще не знает этот statement, PgBouncer подготовит его перед выполнением;&lt;/li&gt;
&lt;li&gt;при &lt;code&gt;max_prepared_statements = 0&lt;/code&gt; эта поддержка выключена.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Это важная поправка для Java. Все популярные ORM вроде Hibernate, Spring Data JDBC, jOOQ и другие по умолчанию используют
JDBC &lt;code&gt;PreparedStatement&lt;/code&gt;, когда им надо передавать bind values.&lt;/p&gt;
&lt;p&gt;pgJDBC, в свою очередь, использует extended protocol, а после нескольких выполнений может перейти к server-side prepared
statement. В &lt;a
href="https://jdbc.postgresql.org/documentation/server-prepare/"target="_blank" rel="noopener"&gt;документации pgJDBC&lt;/a&gt; дефолтный
&lt;code&gt;prepareThreshold&lt;/code&gt; равен &lt;code&gt;5&lt;/code&gt;. Отключается это параметром &lt;code&gt;prepareThreshold=0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;То есть современные варианты такие:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;application.properties&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-properties" data-lang="properties"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Вариант 1: используем поддержку PgBouncer 1.21+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spring.datasource.url&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;jdbc:postgresql://pgbouncer:6432/app_db&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Вариант 2: отключаем server-side prepared statements в pgJDBC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spring.datasource.url&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;jdbc:postgresql://pgbouncer:6432/app_db?prepareThreshold=0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Скопировать код"
aria-label="Скопировать код"
data-copied-label="Скопировано!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Первый вариант обычно лучше для классического Java backend, если PgBouncer свежий и &lt;code&gt;max_prepared_statements&lt;/code&gt; включен.
Prepared statements уменьшают parse/plan overhead и не заставляют драйвер каждый раз слать полный SQL.&lt;/p&gt;
&lt;p&gt;Второй вариант полезен как диагностика или временное решение, если:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PgBouncer старый;&lt;/li&gt;
&lt;li&gt;используется не PgBouncer, а совместимый пулер с другими ограничениями;&lt;/li&gt;
&lt;li&gt;приложение ловит ошибки prepared statement cache после DDL;&lt;/li&gt;
&lt;li&gt;есть библиотека, которая странно именует statements или смешивает типы параметров.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;У поддержки prepared statements есть цена. PgBouncer должен разбирать и переписывать protocol messages, хранить query
cache, держать prepared statements на server connections. На больших нагрузках это добавляет CPU и память. Зато один
подготовленный план запроса может переиспользоваться между разными клиентами, если SQL совпадает.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;a
href="https://supabase.com/docs/guides/troubleshooting/disabling-prepared-statements-qL8lEL"target="_blank" rel="noopener"&gt;Supabase в документации по Supavisor transaction mode&lt;/a&gt;
до сих пор рекомендует отключать prepared statements для ряда клиентов. Это хороший пример, почему нельзя переносить
правила между разными pooler&amp;rsquo;ами автоматически. PgBouncer 1.25 и managed pooler у провайдера могут вести себя по-разному.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Что дальше&lt;span class="hx:absolute hx:-mt-20" id="что-дальше"&gt;&lt;/span&gt;
&lt;a href="#%d1%87%d1%82%d0%be-%d0%b4%d0%b0%d0%bb%d1%8c%d1%88%d0%b5" class="subheading-anchor" aria-label="Ссылка на этот раздел"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;На уровне обзора картина такая:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PgBouncer нужен, чтобы ограничить количество настоящих соединений к PostgreSQL.&lt;/li&gt;
&lt;li&gt;Он не заменяет connection pool внутри приложения.&lt;/li&gt;
&lt;li&gt;Самый полезный режим для backend-сервисов - &lt;code&gt;transaction&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transaction&lt;/code&gt; pooling требует дисциплины вокруг session state.&lt;/li&gt;
&lt;li&gt;Prepared statements больше не надо автоматически выключать, но надо понимать разницу между SQL-level и protocol-level
prepared statements.&lt;/li&gt;
&lt;li&gt;Миграции и DDL лучше вести прямым подключением к PostgreSQL.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Во второй части разберем, как это эксплуатировать: с какого конфига стартовать, как выбирать размеры пулов, какие
таймауты согласовать с HikariCP, какие метрики смотреть и где PgBouncer лучше ставить в production.&lt;/p&gt;
&lt;div class="hextra-cards hx:mt-4 hx:gap-4 hx:grid not-prose" style="--hextra-cards-grid-cols: 1;"&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"href="https://karandashov.com/posts/2026-06-26/"
&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/&gt;&lt;/svg&gt;PgBouncer: эксплуатация, таймауты и production&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Продолжение: конфиг, sizing, мониторинг, топологии и failover&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;/div&gt;</description></item></channel></rss>