Skip to content

Releases: ozontech/pg_doorman

v3.3.1 (Feb 26, 2026)

26 Feb 09:06
4ed5ac4

Choose a tag to compare

Bug Fixes

  • Fix Ctrl+C in foreground mode (#137): Pressing Ctrl+C in foreground mode (with TTY attached) now performs a clean graceful shutdown instead of triggering a binary upgrade. Previously, each Ctrl+C would spawn a new pg_doorman process via --inherit-fd, leaving orphan processes accumulating. SIGINT in daemon mode (no TTY) retains its legacy binary upgrade behavior for backward compatibility with existing systemd units.

  • Minimum pool size enforcement (min_pool_size) (#135): The min_pool_size user setting is now enforced at runtime. After each connection retain cycle, pg_doorman checks pool sizes and creates new connections to maintain the configured minimum. Previously, min_pool_size was accepted in config but never applied — pools started empty and could drop to 0 connections even with min_pool_size set. Replenishment stops on the first connection failure to avoid hammering an unavailable server.

New Features

  • SIGUSR2 for binary upgrade (#137): New dedicated signal SIGUSR2 triggers binary upgrade + graceful shutdown in all modes (daemon and foreground). This is now the recommended signal for binary upgrades. The systemd service file has been updated to use SIGUSR2 for ExecReload.

  • UPGRADE admin command (#137): New admin console command that triggers binary upgrade via SIGUSR2. Use it from psql connected to the admin database: UPGRADE;.

Improvements

  • Pool prewarm at startup (#135): When min_pool_size is configured, pg_doorman now creates the minimum number of connections immediately at startup, before the first retain cycle. Previously, pools started empty and connections were only created lazily on first client request or after the first retain interval (default 60s). This eliminates cold-start latency for the first clients connecting after pg_doorman restart.

  • Configurable connection scaling parameters (#134): New general settings scaling_warm_pool_ratio, scaling_fast_retries, and scaling_cooldown_sleep allow tuning connection pool scaling behavior. All three can be overridden at the pool level. scaling_cooldown_sleep uses the human-readable Duration type (e.g. "10ms", "1s") consistent with other timeout fields.

  • max_concurrent_creates setting (#134): Controls the maximum number of server connections that can be created concurrently per pool. Uses a semaphore instead of a mutex for parallel connection creation.

Signal Reference

Signal Daemon / no TTY Foreground + TTY (Ctrl+C)
SIGUSR2 Binary upgrade + graceful shutdown (recommended) Binary upgrade + graceful shutdown (recommended)
SIGINT Binary upgrade + graceful shutdown (legacy) Graceful shutdown only (fixed)
SIGTERM Immediate shutdown Immediate shutdown
SIGHUP Reload configuration Reload configuration

Full Changelog: v3.3.0...v3.3.1

v3.3.0 (Feb 23, 2026)

23 Feb 16:29
fc693e4

Choose a tag to compare

Dynamic user authentication & passthrough auth

This release adds auth_query — dynamic password lookup from PostgreSQL at connection time, eliminating the need to list every user in the config. It also introduces passthrough authentication as the default mode for both static and dynamic users: PgDoorman reuses the client's cryptographic proof
(MD5 hash or SCRAM ClientKey) to authenticate to the backend, so server_password is no longer needed in most setups.

New Features

  • auth_query — dynamic user authentication: Authenticate users by querying PostgreSQL (pg_shadow, custom tables, or SECURITY DEFINER functions) at connection time. The query must return a column named passwd, password, or any single column containing an MD5 or SCRAM-SHA-256 hash.

  • Passthrough authentication (default): PgDoorman reuses the client's cryptographic proof to authenticate to the backend automatically — no plaintext passwords in config. Works for both static users (hash in config) and dynamic users (hash from auth_query). server_username/server_password are
    only needed when the backend user differs from the pool user.

  • Two auth_query modes:

    • Passthrough (default) — each dynamic user gets their own backend connection pool and authenticates as themselves
    • Dedicated (server_user set) — all dynamic users share a single backend pool under one PostgreSQL role
  • Auth query caching: DashMap-based cache with configurable TTL, double-checked locking, rate-limited refetch, and request coalescing. Separate TTLs for successful and failed lookups.

  • SHOW AUTH_QUERY admin command: Per-pool metrics — cache entries/hits/misses, auth success/failure counters, executor stats, dynamic pool count.

  • Prometheus metrics: New metric families pg_doorman_auth_query_cache, pg_doorman_auth_query_auth, pg_doorman_auth_query_executor, pg_doorman_auth_query_dynamic_pools.

  • Idle dynamic pool GC: Background task cleans up expired dynamic pools. Zero overhead for static-only configs.

  • Smart password column lookup: Column resolved by name (passwdpassword → single-column fallback).

Improvements

  • server_username/server_password now optional: Previously documented as required for MD5/SCRAM hash configs. Now only needed for username mapping or JWT auth.

  • Data-driven config & docs generation: fields.yaml is the single source of truth for all config parameter descriptions (EN/RU). Reference docs, annotated configs, and inline comments are all generated from it.

Quick start

pools:
  mydb:
    server_host: "127.0.0.1"
    server_port: 5432
    pool_mode: "transaction"
    auth_query:
      query: "SELECT passwd FROM pg_shadow WHERE usename = $1"
      user: "doorman_auth"
      password: "auth_password"

Or with static users (no server_password needed):

users:
  - username: "app"
    password: "md5..."    # hash from pg_authid
    pool_size: 40

v3.2.4 (Feb 21, 2026)

20 Feb 22:10
db8067e

Choose a tag to compare

New Features

Generate command improvements:

  • Annotated config generation — pg_doorman generate now produces well-documented configs with inline comments for every parameter (default behavior)
  • --reference — generate a complete reference config with example values, no PostgreSQL connection needed
  • --format (-f) — explicitly choose output format (yaml/toml), default changed to YAML
  • --russian-comments (--ru) — generate comments in Russian for quick start
  • --no-comments — plain config without inline comments (old behavior)
  • Root pg_doorman.toml and pg_doorman.yaml are now auto-generated from code, ensuring they always stay in sync

Connection management:

  • Configuration test mode (-t / --test-config) — validate config and exit without starting the server, useful for CI/CD
  • Configuration validation before binary upgrade — SIGINT now validates the new binary's config before proceeding; cancels shutdown on failure
  • server_idle_check_timeout — check idle server connections before giving to clients (default: 30s), detects dead connections from PG restarts or network issues
  • tcp_user_timeout — TCP_USER_TIMEOUT for client connections (default: 60s, Linux only), prevents 15-16 minute delays on dead connections
  • retain_connections_max — control max idle connections closed per retain cycle (default: 3), with oldest-first closure order
  • server_lifetime jitter (±20%) — prevents mass connection expirations under load

Simplifications:

  • Removed wait_rollback mechanism — was causing protocol desync with async clients
  • Removed savepoint tracking — savepoints are now handled as regular PostgreSQL commands

Performance

  • Removed timeout-based waiting in async protocol — response tracking by batch operations, eliminates unnecessary latency in pipeline/async workloads

Bug Fixes

  • Fixed protocol desync on prepared statement cache eviction in async mode — CloseComplete and ReadyForQuery were left unread in TCP buffer during LRU eviction with Flush-based pipelines (asyncpg/SQLAlchemy)
  • Fixed protocol violation on flush timeout — clients now receive proper ErrorResponse (SQLSTATE 58006) instead of TCP connection drop
  • Fixed protocol desync in async mode with simple prepared statements — batch operations are now tracked regardless of prepared_statements setting

v3.1.8 (Jan 31, 2026)

31 Jan 19:47
f1ac787

Choose a tag to compare

Bug Fixes:

  • Fixed ParseComplete desynchronization in pipeline on errors: Fixed a protocol desynchronization issue (especially noticeable in .NET Npgsql driver) where synthetic ParseComplete messages were not being inserted if an error occurred during a pipelined batch. When the pooler caches a prepared statement and skips sending Parse to the server, it must still provide a ParseComplete to the client. If an error occurs before subsequent commands are processed, the server skips them, and the pooler now ensures all missing synthetic ParseComplete messages are inserted into the response stream upon receiving an ErrorResponse or ReadyForQuery.

  • Fixed incorrect use_savepoint state persistence: Fixed a bug where the use_savepoint flag (which disables automatic rollback on connection return if a savepoint was used) was not reset after a transaction ended.

v3.1.7 (Jan 29, 2026)

29 Jan 13:06
698b3a0

Choose a tag to compare

Memory Optimization:

  • DEALLOCATE now clears client prepared statements cache: When a client sends DEALLOCATE <name> or DEALLOCATE ALL via simple query protocol, the pooler now properly clears the corresponding entries from the client's internal prepared statements cache. Previously, synthetic OK responses were sent but the client cache was not cleared, causing memory to grow indefinitely for long-running connections using many unique prepared statements. This fix allows memory to be reclaimed when clients properly deallocate their statements.

  • New client_prepared_statements_cache_size configuration parameter: Added protection against malicious or misbehaving clients that don't call DEALLOCATE and could exhaust server memory by creating unlimited prepared statements. When the per-client cache limit is reached, the oldest entry is evicted automatically. Set to 0 for unlimited (default, relies on client calling DEALLOCATE). Example: client_prepared_statements_cache_size: 1024 limits each client to 1024 cached prepared statements.

The clock_resolution_statistics parameter has been removed as a premature optimization.

v3.1.6 (Jan 27, 2026)

27 Jan 19:08
2f5b7dd

Choose a tag to compare

Bug Fixes:

  • Fixed incorrect timing statistics (xact_time, wait_time, percentiles): The statistics module was using recent() (cached clock) without proper clock cache updates, causing transaction time, wait time, and their percentiles to show extremely large incorrect values (e.g., 100+ seconds instead of actual milliseconds).

  • Fixed query time accumulation bug in transaction loop: Query times were incorrectly accumulated when multiple queries were executed within a single transaction. The query_start_at timestamp was only set once at the beginning of the transaction, causing each subsequent query's elapsed time to include all previous queries' durations (e.g., 10 queries of 100ms each would report the last query as ~1 second instead of 100ms). Now query_start_at is updated for each new message in the transaction loop, ensuring accurate per-query timing.

New Features:

  • New clock_resolution_statistics configuration parameter: Added general.clock_resolution_statistics parameter (default: 0.1ms = 100 microseconds) that controls how often the internal clock cache is updated. Lower values provide more accurate timing measurements for query/transaction percentiles, while higher values reduce CPU overhead. This parameter affects the accuracy of all timing statistics reported in the admin console and Prometheus metrics.

  • Sub-millisecond precision for Duration values: Duration configuration parameters now support sub-millisecond precision:

    • New us suffix for microseconds (e.g., "100us" = 100 microseconds)
    • Decimal milliseconds support (e.g., "0.1ms" = 100 microseconds)
    • Internal representation changed from milliseconds to microseconds for higher precision
    • Full backward compatibility maintained: plain numbers are still interpreted as milliseconds

v3.1.5 (Jan 27, 2026)

27 Jan 07:10
5991e77

Choose a tag to compare

Bug Fixes:

  • Fixed PROTOCOL VIOLATION with batch PrepareAsync
  • Rewritten ParseComplete insertion algorithm

New Features:

  • YAML configuration support: Added support for YAML configuration files (.yaml, .yml) as the primary and recommended format. The format is automatically detected based on file extension. TOML format remains fully supported for backward compatibility.
    • The generate command now outputs YAML or TOML based on the output file extension.
    • Include files can mix YAML and TOML formats.
    • New array syntax for users in YAML: users: [{ username: "user1", ... }]
  • TOML backward compatibility: Full backward compatibility with legacy TOML format [pools.*.users.0] is maintained. Both the legacy map format and the new array format [[pools.*.users]] are supported.
  • Username uniqueness validation: Added validation to reject duplicate usernames within a pool, ensuring configuration correctness.
  • Human-readable configuration values: Duration and byte size parameters now support human-readable formats while maintaining backward compatibility with numeric values:
    • Duration: "3s", "5m", "1h", "1d" (or milliseconds: 3000)
    • Byte size: "1MB", "256M", "1GB" (or bytes: 1048576)
    • Example: connect_timeout: "3s" instead of connect_timeout: 3000
  • Foreground mode binary upgrade: Added support for binary upgrade in foreground mode by passing the listener socket to the new process via --inherit-fd argument. This enables zero-downtime upgrades without requiring daemon mode.
  • Optional tokio runtime parameters: The following tokio runtime parameters are now optional and default to None (using tokio's built-in defaults): tokio_global_queue_interval, tokio_event_interval, worker_stack_size, and the new max_blocking_threads. Modern tokio versions handle these parameters well by default, so explicit configuration is no longer required in most cases.
  • Improved graceful shutdown behavior:
    • During graceful shutdown, only clients with active transactions are now counted (instead of all connected clients), allowing faster shutdown when clients are idle.
    • After a client completes their transaction during shutdown, they receive a proper PostgreSQL protocol error (58006 - pooler is shut down now) instead of a connection reset.
    • Server connections are immediately released (marked as bad) after transaction completion during shutdown to conserve PostgreSQL connections.
    • All idle connections are immediately drained from pools when graceful shutdown starts, releasing PostgreSQL connections faster.

Performance:

  • Deferred connection acquisition for standalone BEGIN: When a client sends a standalone BEGIN; or begin; query (simple query protocol), the pooler now defers acquiring a server connection until the next message arrives. Since BEGIN itself doesn't perform any actual database operations, this optimization reduces connection pool contention when clients are slow to send their next query after starting a transaction.
    • Micro-optimized detection: first checks message size (12 bytes), then content using case-insensitive comparison
    • If client sends Terminate (X) after BEGIN, no server connection is acquired at all
    • The deferred BEGIN is automatically sent to the server before the actual query
  • Statistics module optimization: Major refactoring of the src/stats module for improved performance:
    • Replaced VecDeque with HDR histograms (hdrhistogram crate) for percentile calculations — O(1) percentile queries instead of O(n log n) sorting, ~95% memory reduction for latency tracking.
    • Histograms are now reset after each stats period (15 seconds) to provide accurate rolling window percentiles.

v3.0.5 (Jan 16, 2026)

16 Jan 15:09

Choose a tag to compare

Bug Fixes:

  • Fixed panic (capacity overflow) in startup message handling when receiving malformed messages with invalid length (less than 8 bytes or exceeding 10MB). Now gracefully rejects such connections with ClientBadStartup error.

Testing:

  • Integration fuzz testing framework: Added comprehensive BDD-based fuzz tests (@fuzz tag) that verify pg_doorman's resilience to malformed PostgreSQL protocol messages.
  • All fuzz tests connect and authenticate first, then send malformed data to test post-authentication resilience.

v3.0.4 (Jan 16, 2026)

16 Jan 13:52
0ecd01f

Choose a tag to compare

New Features:

  • Enhanced DEBUG logging for PostgreSQL protocol messages: Added grouped debug logging that displays message types in a compact format (e.g., [P(stmt1),B,D,E,S] or [3xD,C,Z]). Messages are buffered and flushed every 100ms or 100 messages to reduce log noise.
  • Protocol violation detection: Added real-time protocol state tracking that detects and warns about protocol violations (e.g., receiving ParseComplete when no Parse was pending). Helps diagnose client-server synchronization issues.

Bug Fixes:

  • Fixed potential protocol violation when client disconnects during batch operations with cached prepared statements: disabled fast_release optimization when there are pending prepared statement operations.
  • Fixed ParseComplete insertion for Describe flow: now correctly inserts one ParseComplete before each ParameterDescription ('t') or NoData ('n') message instead of inserting all at once.

v3.0.3 (Jan 15, 2026)

15 Jan 05:26
66af73f

Choose a tag to compare

Bug Fixes:

  • Improved handling of Describe flow for cached prepared statements: added a separate counter (pending_parse_complete_for_describe) to correctly insert ParseComplete messages before ParameterDescription or NoData responses when Parse was skipped due to caching.