{"id":28598107,"url":"https://github.com/ozontech/pg_doorman","last_synced_at":"2026-04-01T23:13:05.480Z","repository":{"id":280870706,"uuid":"943293485","full_name":"ozontech/pg_doorman","owner":"ozontech","description":"PostgreSQL Pooler","archived":false,"fork":false,"pushed_at":"2026-03-29T11:32:30.000Z","size":6624,"stargazers_count":216,"open_issues_count":8,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-03-29T13:49:53.372Z","etag":null,"topics":["pooler","postgresql","rust"],"latest_commit_sha":null,"homepage":"https://t.me/pg_doorman","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ozontech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-05T13:28:28.000Z","updated_at":"2026-03-27T09:49:50.000Z","dependencies_parsed_at":"2026-01-25T16:01:00.948Z","dependency_job_id":null,"html_url":"https://github.com/ozontech/pg_doorman","commit_stats":null,"previous_names":["ozontech/pg_doorman"],"tags_count":37,"template":false,"template_full_name":null,"purl":"pkg:github/ozontech/pg_doorman","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozontech%2Fpg_doorman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozontech%2Fpg_doorman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozontech%2Fpg_doorman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozontech%2Fpg_doorman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ozontech","download_url":"https://codeload.github.com/ozontech/pg_doorman/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ozontech%2Fpg_doorman/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292832,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pooler","postgresql","rust"],"created_at":"2025-06-11T11:42:09.203Z","updated_at":"2026-04-01T23:13:05.463Z","avatar_url":"https://github.com/ozontech.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"![pg_doorman](/static/logo_color_bg.png)\n\n# PgDoorman\n\n[![BDD Tests](https://github.com/ozontech/pg_doorman/actions/workflows/bdd-tests.yml/badge.svg)](https://github.com/ozontech/pg_doorman/actions/workflows/bdd-tests.yml)\n[![Library Tests](https://github.com/ozontech/pg_doorman/actions/workflows/lib-tests.yml/badge.svg)](https://github.com/ozontech/pg_doorman/actions/workflows/lib-tests.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nA high-performance multithreaded PostgreSQL connection pooler built in Rust. Does one thing and does it well — pools connections so your PostgreSQL handles thousands of clients without breaking a sweat.\n\n## Why PgDoorman?\n\n**Drop-in replacement. No app changes.** PgDoorman caches and remaps prepared statements transparently across server connections in transaction mode — just point your connection string at it and go. No `DISCARD ALL`, no `DEALLOCATE`, no driver hacks. PgBouncer added similar support in 1.21, but remains single-threaded; Odyssey added it in 1.3, but has known reliability issues in edge cases.\n\n**Battle-tested with real drivers.** Two years of production use with Go (pgx), .NET (Npgsql), Python (asyncpg, SQLAlchemy), Node.js. Protocol edge cases — pipelined batches, async Flush, Describe flow, cancel requests over TLS — are covered by comprehensive multi-language BDD tests.\n\n**Natively multithreaded.** PgBouncer is single-threaded. Running multiple instances via `SO_REUSE_PORT` leads to unbalanced pools: clients connect evenly but disconnect unpredictably, leaving some instances overloaded while others sit idle. PgDoorman uses a single shared pool across all worker threads, ensuring correct connection distribution at any scale.\n\n**Full extended query protocol support.** Benchmarks show Odyssey is up to 61% slower with the extended query protocol in transaction mode. Odyssey also has known crashes under query cancellation stress and segfaults on large packets. PgDoorman handles simple, extended, and prepared protocols equally well — including pipelined batches and async Flush flow that cause issues in other poolers.\n\n**Built for operations.** `pg_doorman generate --host your-db` creates a config by introspecting PostgreSQL — no manual user/database enumeration. `pg_doorman -t` validates config before deploy (PgBouncer and Odyssey lack this). YAML config with human-readable durations (`\"30s\"`, `\"5m\"`, `\"1h\"`). Built-in Prometheus endpoint — no external exporter needed (PgBouncer requires a separate process; Odyssey's built-in metrics segfault when combined with standard logging).\n\n**Dead backend detection.** When a client holds a transaction open, pg_doorman probes the backend and returns an error immediately if the server is gone (failover, OOM kill). Other poolers rely on TCP keepalive, leaving clients hanging for minutes.\n\n## Benchmarks\n\nAutomated benchmarks on AWS Fargate (16 vCPU, pool size 40, pgbench 30s per test):\n\n| Scenario | vs PgBouncer | vs Odyssey |\n|----------|-------------|------------|\n| Extended protocol, 500 clients + SSL | x3.5 | +61% |\n| Prepared statements, 500 clients + SSL | x4.0 | +5% |\n| Simple protocol, 10,000 clients | x2.8 | +20% |\n| Extended + SSL + Reconnect, 500 clients | +96% | ~0% |\n\nPgBouncer is single-threaded — these ratios reflect a single PgBouncer instance vs a single PgDoorman instance. [Full benchmark results](https://ozontech.github.io/pg_doorman/benchmarks.html).\n\n## Comparison\n\n| | PgDoorman | PgBouncer | Odyssey |\n|---|:-:|:-:|:-:|\n| Multithreaded | Yes | No | Yes |\n| Prepared statements in transaction mode | Yes | Since 1.21 | Since 1.3 |\n| Full extended query protocol | Yes | Yes | Partial |\n| Deferred `BEGIN` (lazy server acquire) | Yes | No | No |\n| Stale backend detection (idle-in-transaction) | Yes | No | No |\n| Zero-downtime binary upgrade | Yes | Yes | Yes |\n| Config test mode (`-t` / `--test-config`) | Yes | No | No |\n| Auto-config from PostgreSQL | Yes | No | No |\n| YAML / TOML config | Yes | No (INI) | No (own format) |\n| Human-readable durations \u0026 sizes | Yes | No | No |\n| Native `pg_hba.conf` format | Yes | Yes | Since 1.4 |\n| Auth query (dynamic users) | Yes | Yes | Yes |\n| Auth query passthrough (per-user backend identity) | Yes | No | Yes |\n| PAM auth | Yes | Yes | Yes |\n| LDAP auth | No | Since 1.25 | Yes |\n| PAUSE / RESUME / RECONNECT | Yes | Yes | Yes |\n| TLS: minimum TLS 1.2, Mozilla ciphers | Yes | Yes | No (allows TLS 1.0, weak ciphers) |\n| Prometheus metrics | Built-in | External | Built-in |\n\n## Quick Start\n\n### Minimal config\n\n```yaml\ngeneral:\n  host: \"0.0.0.0\"\n  port: 6432\n  admin_username: \"admin\"\n  admin_password: \"change_me\"\n\npools:\n  mydb:\n    server_host: \"127.0.0.1\"\n    server_port: 5432\n    pool_mode: \"transaction\"\n    users:\n      - username: \"app\"\n        password: \"md5...\"           # hash from pg_shadow / pg_authid\n        pool_size: 40\n```\n\n\u003e **Passthrough authentication (default):** When `server_username` and `server_password` are omitted, PgDoorman reuses the client's cryptographic proof (MD5 hash or SCRAM ClientKey) to authenticate to PostgreSQL automatically. This is the recommended setup when the pool username matches the backend PostgreSQL user — no plaintext passwords in config needed.\n\u003e\n\u003e Set `server_username` / `server_password` only when the backend user differs from the pool user (e.g., username mapping) or for JWT authentication where there is no password to pass through.\n\n### Auth query (dynamic users)\n\nInstead of listing every user in the config, pg_doorman can look up credentials directly from PostgreSQL. The query must return a column named `passwd` or `password` containing the MD5 or SCRAM hash. Any extra columns are ignored.\n\nQuickstart — using `pg_shadow` directly (requires superuser):\n\n```yaml\npools:\n  mydb:\n    server_host: \"127.0.0.1\"\n    server_port: 5432\n    pool_mode: \"transaction\"\n    auth_query:\n      query: \"SELECT passwd FROM pg_shadow WHERE usename = $1\"\n      user: \"postgres\"\n      password: \"postgres_password\"\n```\n\nBy default auth_query runs in **passthrough mode**: each dynamic user gets their own backend pool and authenticates as themselves. To force all users through a single backend role, set `server_user` / `server_password` (dedicated mode).\n\n\u003e Static users (defined in `users`) are checked first. auth_query is only consulted when the username is not found among static users.\n\n\u003e **Production:** don't use superuser for auth queries. Create a [`SECURITY DEFINER` function](https://ozontech.github.io/pg_doorman/reference/pool.html#auth-query-settings) with a dedicated low-privilege role instead.\n\nOr generate a config automatically:\n\n```bash\npg_doorman generate --host your-db-host --output pg_doorman.yaml\n```\n\n### Run and connect\n\n```bash\n# Start\npg_doorman pg_doorman.yaml\n\n# Connect — same as you would to PostgreSQL directly\npsql -h localhost -p 6432 -U app mydb\n```\n\nYour application connection string changes only the host and port:\n\n```\npostgresql://app:secret@localhost:6432/mydb\n```\n\n## Pooling Modes\n\nPgDoorman supports two pooling modes, configured per pool or per user:\n\n**Transaction mode** (default, recommended) — server connection is acquired when a transaction starts and released back to the pool when it ends. One backend serves many clients, giving the best connection utilization.\n\n**Session mode** — server connection is held for the entire client session. Use this when your application relies on session-level features like `LISTEN/NOTIFY`, temporary tables, or advisory locks.\n\n```yaml\npools:\n  mydb:\n    pool_mode: \"transaction\"   # or \"session\"\n```\n\n## SQL Feature Compatibility\n\nWhat works in each pooling mode:\n\n| Feature | Transaction | Session |\n|---------|:-----------:|:-------:|\n| Regular queries (SELECT, INSERT, ...) | Yes | Yes |\n| Prepared statements (Parse/Bind/Execute) | Yes (transparent caching) | Yes |\n| SET / RESET | Yes (auto-RESET ALL on checkin) | Yes |\n| Cursors (DECLARE / FETCH / CLOSE) | Yes (auto-CLOSE ALL on checkin) | Yes |\n| LISTEN / NOTIFY | No — use session mode | Yes |\n| Temporary tables | No — use session mode | Yes |\n| Advisory locks (`pg_advisory_xact_lock`) | Yes (transaction-scoped) | Yes |\n| Session-level advisory locks | No — use `pg_advisory_xact_lock` | Unreliable with pooling |\n| DISCARD ALL | Yes | Yes |\n| COPY | Yes | Yes |\n\nIn transaction mode, PgDoorman automatically cleans up server state (`RESET ALL`, `CLOSE ALL`) when returning a connection to the pool, so the next client gets a clean connection.\n\n\u003e **Advisory locks and connection pooling:** Session-level advisory locks (`pg_advisory_lock`) are unreliable with any connection pooler — the lock is tied to a backend connection, not to your application session, so another client may inherit or release it unexpectedly. Use transaction-level `pg_advisory_xact_lock()` instead, which is automatically released at transaction end and works correctly in transaction mode.\n\n## Admin Commands\n\nConnect to the admin console and manage pools at runtime:\n\n```sql\n-- Block new connections (active transactions continue)\nPAUSE mydb;\nPAUSE;          -- all pools\n\n-- Unblock waiting clients\nRESUME mydb;\nRESUME;         -- all pools\n\n-- Force backend connection rotation (epoch-based, no downtime)\nRECONNECT mydb;\nRECONNECT;      -- all pools\n```\n\nFull connection rotation pattern: `PAUSE → RECONNECT → RESUME`.\n\n```sql\n-- Trigger binary upgrade (zero downtime)\nUPGRADE;\n```\n\nSee [admin commands documentation](https://ozontech.github.io/pg_doorman/tutorials/basic-usage.html) for details.\n\n## TLS / SSL\n\nPgDoorman supports TLS encryption on both the client-facing and server-facing sides.\n\n### Client-facing TLS\n\nEncrypt connections between your application and PgDoorman:\n\n```yaml\ngeneral:\n  tls_certificate: \"/path/to/server.crt\"\n  tls_private_key: \"/path/to/server.key\"\n  tls_mode: \"require\"                      # disable | allow | require | verify-full\n  # tls_ca_cert: \"/path/to/ca.crt\"        # required for verify-full\n  # tls_rate_limit_per_second: 500         # limit TLS handshakes (0 = unlimited)\n```\n\n| Mode | Behavior |\n|------|----------|\n| `disable` | TLS not allowed |\n| `allow` | TLS accepted but not required (default when cert is configured) |\n| `require` | TLS required, client certificates not verified |\n| `verify-full` | TLS required, client certificate verified against CA |\n\n### Server-facing TLS\n\nEncrypt connections from PgDoorman to PostgreSQL:\n\n```yaml\ngeneral:\n  server_tls: true\n  verify_server_certificate: true         # verify PostgreSQL server certificate\n```\n\n### Security defaults\n\nPgDoorman enforces strict TLS defaults out of the box:\n\n- **TLS 1.2 minimum** — TLS 1.0/1.1 are rejected (deprecated per RFC 8996)\n- **Mozilla Intermediate cipher suites** — only modern AEAD ciphers (AES-GCM, ChaCha20-Poly1305) with forward secrecy (ECDHE/DHE); no RC4, DES, or CBC\n- **Full hostname verification** — `verify-full` checks both Subject Alternative Names (SANs) and Common Name (CN) via OpenSSL's `verify_hostname()`; Odyssey only checks CN, which is [obsolete practice](https://datatracker.ietf.org/doc/html/rfc6125)\n- **Startup validation** — certificates and keys are loaded and verified at startup, not at first connection\n\n## Monitoring\n\nBuilt-in Prometheus metrics endpoint — no external exporters needed.\n\n```yaml\nprometheus:\n  enabled: true\n  host: \"0.0.0.0\"\n  port: 9127\n```\n\nScrape `http://host:9127/` to collect metrics. Key metrics:\n\n| Metric | Labels | Description                                 |\n|--------|--------|---------------------------------------------|\n| `pg_doorman_pools_clients` | status, user, database | Clients by status (active / idle / waiting) |\n| `pg_doorman_pools_servers` | status, user, database | Servers by status (active / idle)           |\n| `pg_doorman_pool_size` | user, database | Configured max pool size                    |\n| `pg_doorman_pools_queries_count` | user, database | Total queries executed                      |\n| `pg_doorman_pools_queries_percentile` | percentile, user, database | Query time p50 / p90 / p95 / p99 (ms)       |\n| `pg_doorman_pools_transactions_count` | user, database | Total transactions executed                 |\n| `pg_doorman_pools_avg_wait_time` | user, database | Avg client wait for server (ms)             |\n| `pg_doorman_pools_bytes` | direction, user, database | Bytes sent / received                       |\n| `pg_doorman_pool_prepared_cache_entries` | user, database | Prepared statement cache entries            |\n| `pg_doorman_auth_query_cache` | event, database | Auth query cache hits / misses              |\n| `pg_doorman_auth_query_dynamic_pools` | database | Active dynamic user pools                    |\n| `pg_doorman_total_memory` | — | Process memory usage (bytes)                |\n| `pg_doorman_connection_count` | type | Connections by type (plain / tls / total)   |\n\n## Signals \u0026 Zero-Downtime Upgrade\n\n| Signal | Effect |\n|--------|--------|\n| `SIGHUP` | Reload configuration without restart |\n| `SIGUSR2` | Start binary upgrade + graceful shutdown of old process |\n| `SIGTERM` | Immediate shutdown |\n\n### Binary upgrade (zero downtime)\n\nReplace the pg_doorman binary while clients stay connected:\n\n```bash\n# Replace the binary on disk, then:\nkill -USR2 $(cat /tmp/pg_doorman.pid)\n\n# Or from the admin console:\nUPGRADE;\n```\n\nPgDoorman validates the new binary's configuration (`-t` flag) before starting it. If validation fails, the upgrade is aborted and the old process continues. Active clients experience no interruption — new connections are served by the new process, existing ones drain gracefully.\n\nFor systemd services:\n\n```ini\nExecReload=/bin/kill -SIGUSR2 $MAINPID\n```\n\n## Installation\n\n**Pre-built binaries:** Download from [GitHub Releases](https://github.com/ozontech/pg_doorman/releases).\n\n```bash\n# Ubuntu/Debian\nsudo add-apt-repository ppa:vadv/pg-doorman \u0026\u0026 sudo apt-get install pg-doorman\n\n# Fedora/RHEL/Rocky\nsudo dnf copr enable vadvya/pg-doorman \u0026\u0026 sudo dnf install pg-doorman\n\n# Docker\ndocker pull ghcr.io/ozontech/pg_doorman\n```\n\n### Building from source\n\n```bash\n# Recommended: build with jemalloc tuning for optimal memory management\nJEMALLOC_SYS_WITH_MALLOC_CONF=\"dirty_decay_ms:30000,muzzy_decay_ms:30000,background_thread:true,metadata_thp:auto\" \\\n  cargo build --release\n\n# Binary will be at target/release/pg_doorman\n```\n\n## Coming from PgBouncer?\n\nPgDoorman uses YAML instead of INI, but the concepts are the same:\n\n| PgBouncer (INI) | PgDoorman (YAML) | Notes |\n|-----------------|-------------------|-------|\n| `pool_mode = transaction` | `pool_mode: \"transaction\"` | Same semantics |\n| `max_client_conn = 1000` | `general.max_connections: 1000` | |\n| `default_pool_size = 20` | `users[].pool_size: 20` | Set per-user, not globally |\n| `server_lifetime = 3600` | `general.server_lifetime: \"1h\"` | Human-readable durations |\n| `server_idle_timeout = 600` | `general.idle_timeout: \"10m\"` | |\n| `auth_query = ...` | `pools.\u003cdb\u003e.auth_query.query: ...` | Same concept, YAML structure |\n| `listen_addr = *` | `general.host: \"0.0.0.0\"` | |\n| `listen_port = 6432` | `general.port: 6432` | |\n| `admin_users = admin` | `general.admin_username: \"admin\"` | |\n\nKey differences:\n\n- **Prepared statements work out of the box** — no `DEALLOCATE` required, transparent caching across connections\n- **Multithreaded** — one process, one pool, all CPU cores; no need for `SO_REUSE_PORT` hacks\n- **Auto-config** — run `pg_doorman generate --host your-db` to create a config from PostgreSQL\n- **Human-readable durations** — `\"30s\"`, `\"5m\"`, `\"1h\"` instead of raw seconds\n\n## patroni_proxy\n\nThis repository also includes `patroni_proxy` — a TCP proxy for Patroni-managed PostgreSQL clusters. Zero-downtime failover: existing connections are preserved during cluster topology changes.\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"static/patroni_proxy_architecture_dark.png\" /\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"static/patroni_proxy_architecture_light.png\" /\u003e\n    \u003cimg src=\"static/patroni_proxy_architecture_light.png\" alt=\"patroni_proxy architecture\" width=\"700\" /\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n- **pg_doorman** deploys on the same host as PostgreSQL — connection pooling and prepared statement caching benefit from low latency to the database\n- **patroni_proxy** deploys as a sidecar in the application pod — TCP routing and role-based failover (leader/sync/async) with least-connections balancing\n\nSee [patroni_proxy documentation](https://ozontech.github.io/pg_doorman/tutorials/patroni-proxy.html) for details.\n\n## Documentation\n\nFull documentation, configuration reference, and tutorials: **[ozontech.github.io/pg_doorman](https://ozontech.github.io/pg_doorman/)**\n\n## Contributing\n\n```bash\nmake pull       # pull test image\nmake test-bdd   # run all integration tests (Docker-based, fully reproducible)\n```\n\nSee the [Contributing Guide](https://ozontech.github.io/pg_doorman/tutorials/contributing.html) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fozontech%2Fpg_doorman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fozontech%2Fpg_doorman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fozontech%2Fpg_doorman/lists"}