<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/sharing-is-not-always-caring-pgbouncer-pooling-modes-in-practice--4dfgv5c5q" -->

---
title: Sharing is (not always) caring. PgBouncer pooling modes...
description: A deep dive into PgBouncer's pooling modes (session, transaction, statement) explains why transaction mode improves throughput but silently breaks features...
canonical: https://daily.dev/posts/sharing-is-not-always-caring-pgbouncer-pooling-modes-in-practice--4dfgv5c5q
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Sharing is (not always) caring. PgBouncer pooling modes in practice. | daily.dev
og:description: A deep dive into PgBouncer's pooling modes (session, transaction, statement) explains why transaction mode improves throughput but silently breaks features...
og:url: https://daily.dev/posts/sharing-is-not-always-caring-pgbouncer-pooling-modes-in-practice--4dfgv5c5q
og:image: https://api.daily.dev/og/posts/4DFgv5c5Q.png
og:image:alt: Sharing is (not always) caring. PgBouncer pooling modes in practice.
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sharing is (not always) caring. PgBouncer pooling modes in practice.

**[Event-Driven by Oskar Dudycz](https://daily.dev/sources/event-driven-io)** · 35 min read · 0 upvotes · 0 comments

## Summary

A deep dive into PgBouncer's pooling modes (session, transaction, statement) explains why transaction mode improves throughput but silently breaks features relying on session state, especially PostgreSQL advisory locks used for leader election. Through live psql demos and pgbench benchmarks, it shows how transaction mode can let two clients both 'own' the same lock, how unlocks can target the wrong backend, and how failover can stall for up to an hour because PgBouncer never runs a reset query between transactions. It also covers AWS RDS Proxy's alternative approach of pinning connections when session state is detected, and walks through a real bug (Emmett issue #328) where a transaction-level advisory lock combined with a lease row caused duplicate event processing behind RDS Proxy.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://event-driven.io/en/connection-pooler-modes-advisory-locks>

## Questions this post answers

### Why does my PostgreSQL session-level advisory lock get acquired by two different instances when using PgBouncer in transaction mode?

Transaction mode returns the server backend to the pool after each transaction, so an unrelated client's statement can land on the same backend that already holds a session-level lock, and PostgreSQL's re-entrant lock semantics make that client's lock attempt also return true, giving two clients the same lock with no error or warning.

_Developers debugging phantom leader-election bugs can dig into real pooling traces and fixes surfaced on daily.dev._

### Do PostgreSQL transaction-level advisory locks like pg_try_advisory_xact_lock work correctly with PgBouncer transaction pooling mode?

Yes, transaction-level advisory locks work correctly in transaction mode because their lifetime matches exactly how long a client owns the backend in that mode, so they are released automatically when the transaction commits or rolls back, with no leaking across clients. The catch is they only protect for the duration of that single transaction, not any work done afterward.

_Teams choosing locking strategies around pooled Postgres connections can compare approaches like these through daily.dev._

### Why does PgBouncer fail to release a server connection when a client connection drops in transaction pooling mode, breaking leader-election failover?

In transaction mode PgBouncer does not run the server_reset_query (normally DISCARD ALL) when a client disconnects, because that reset only happens between transactions, not on client disconnect. So a session-level advisory lock held on that backend stays locked until PgBouncer eventually closes the idle server connection, which by default can take up to an hour (server_idle_timeout of ten minutes or server_lifetime of one hour).

_Engineers relying on database-backed failover can track these pooling gotchas via daily.dev before they hit production._

## Similar posts on daily.dev

- [Everyone into the Pool](https://daily.dev/posts/everyone-into-the-pool-mo3kijgek) · Planet PostgreSQL · 0 upvotes · 0 comments
- [How We Scaled Our Laravel \+ PostgreSQL Database with PgBouncer \(A Real-World Story\)](https://daily.dev/posts/how-we-scaled-our-laravel-postgresql-database-with-pgbouncer-a-real-world-story--se7axmtp7) · Medium · 3 upvotes · 0 comments
- [PgBouncer and RLS: tenant context, SET LOCAL, shared plans](https://daily.dev/posts/pgbouncer-and-rls-tenant-context-set-local-shared-plans-wvpn1fj2i) · Planet PostgreSQL · 0 upvotes · 0 comments
- [Google Introduces Managed Connection Pooling for AlloyDB](https://daily.dev/posts/google-introduces-managed-connection-pooling-for-alloydb-3jt7lyzbz) · InfoQ · 0 upvotes · 0 comments

---

Tags: [#database](https://daily.dev/tags/database), [#postgresql](https://daily.dev/tags/postgresql)

[View this post on daily.dev](https://daily.dev/posts/sharing-is-not-always-caring-pgbouncer-pooling-modes-in-practice--4dfgv5c5q)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Sharing is (not always) caring. PgBouncer pooling modes in practice.","url":"https://daily.dev/posts/sharing-is-not-always-caring-pgbouncer-pooling-modes-in-practice--4dfgv5c5q","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/sharing-is-not-always-caring-pgbouncer-pooling-modes-in-practice--4dfgv5c5q"},"datePublished":"2026-10-12T13:32:41.434Z","dateModified":"2026-10-12T13:33:07.934Z","description":"A deep dive into PgBouncer's pooling modes (session, transaction, statement) explains why transaction mode improves throughput but silently breaks features...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/9c7ca46f1643ba95314803872a9f0b39?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/9c7ca46f1643ba95314803872a9f0b39?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Event-Driven by Oskar Dudycz","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Event-Driven by Oskar Dudycz","logo":"https://media.daily.dev/image/upload/s--unDKCFQ0--/f_auto,q_auto/v1781695550/logos/event-driven-io?_a=BAMAMiWQ0","url":"https://daily.dev/sources/event-driven-io"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/sharing-is-not-always-caring-pgbouncer-pooling-modes-in-practice--4dfgv5c5q","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"database,postgresql","timeRequired":"PT35M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Event-Driven by Oskar Dudycz","item":"https://daily.dev/sources/event-driven-io"},{"@type":"ListItem","position":3,"name":"Sharing is (not always) caring. PgBouncer pooling modes in practice."}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/sharing-is-not-always-caring-pgbouncer-pooling-modes-in-practice--4dfgv5c5q#faq","mainEntity":[{"@type":"Question","name":"Why does my PostgreSQL session-level advisory lock get acquired by two different instances when using PgBouncer in transaction mode?","acceptedAnswer":{"@type":"Answer","text":"Transaction mode returns the server backend to the pool after each transaction, so an unrelated client's statement can land on the same backend that already holds a session-level lock, and PostgreSQL's re-entrant lock semantics make that client's lock attempt also return true, giving two clients the same lock with no error or warning. Developers debugging phantom leader-election bugs can dig into real pooling traces and fixes surfaced on daily.dev."}},{"@type":"Question","name":"Do PostgreSQL transaction-level advisory locks like pg_try_advisory_xact_lock work correctly with PgBouncer transaction pooling mode?","acceptedAnswer":{"@type":"Answer","text":"Yes, transaction-level advisory locks work correctly in transaction mode because their lifetime matches exactly how long a client owns the backend in that mode, so they are released automatically when the transaction commits or rolls back, with no leaking across clients. The catch is they only protect for the duration of that single transaction, not any work done afterward. Teams choosing locking strategies around pooled Postgres connections can compare approaches like these through daily.dev."}},{"@type":"Question","name":"Why does PgBouncer fail to release a server connection when a client connection drops in transaction pooling mode, breaking leader-election failover?","acceptedAnswer":{"@type":"Answer","text":"In transaction mode PgBouncer does not run the server_reset_query (normally DISCARD ALL) when a client disconnects, because that reset only happens between transactions, not on client disconnect. So a session-level advisory lock held on that backend stays locked until PgBouncer eventually closes the idle server connection, which by default can take up to an hour (server_idle_timeout of ten minutes or server_lifetime of one hour). Engineers relying on database-backed failover can track these pooling gotchas via daily.dev before they hit production."}}]}
```

