Releases: ozontech/pg_doorman
v3.3.1 (Feb 26, 2026)
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 existingsystemdunits. -
Minimum pool size enforcement (
min_pool_size) (#135): Themin_pool_sizeuser 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_sizewas accepted in config but never applied — pools started empty and could drop to 0 connections even withmin_pool_sizeset. Replenishment stops on the first connection failure to avoid hammering an unavailable server.
New Features
-
SIGUSR2 for binary upgrade (#137): New dedicated signal
SIGUSR2triggers binary upgrade + graceful shutdown in all modes (daemon and foreground). This is now the recommended signal for binary upgrades. Thesystemdservice file has been updated to useSIGUSR2forExecReload. -
UPGRADEadmin command (#137): New admin console command that triggers binary upgrade via SIGUSR2. Use it frompsqlconnected to the admin database:UPGRADE;.
Improvements
-
Pool prewarm at startup (#135): When
min_pool_sizeis 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
generalsettingsscaling_warm_pool_ratio,scaling_fast_retries, andscaling_cooldown_sleepallow tuning connection pool scaling behavior. All three can be overridden at the pool level.scaling_cooldown_sleepuses the human-readableDurationtype (e.g."10ms","1s") consistent with other timeout fields. -
max_concurrent_createssetting (#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)
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, orSECURITY DEFINERfunctions) at connection time. The query must return a column namedpasswd,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_passwordare
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_userset) — 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_QUERYadmin 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 (
passwd→password→ single-column fallback).
Improvements
-
server_username/server_passwordnow 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.yamlis 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: 40v3.2.4 (Feb 21, 2026)
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)
Bug Fixes:
-
Fixed ParseComplete desynchronization in pipeline on errors: Fixed a protocol desynchronization issue (especially noticeable in .NET Npgsql driver) where synthetic
ParseCompletemessages were not being inserted if an error occurred during a pipelined batch. When the pooler caches a prepared statement and skips sendingParseto the server, it must still provide aParseCompleteto the client. If an error occurs before subsequent commands are processed, the server skips them, and the pooler now ensures all missing syntheticParseCompletemessages are inserted into the response stream upon receiving anErrorResponseorReadyForQuery. -
Fixed incorrect
use_savepointstate persistence: Fixed a bug where theuse_savepointflag (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)
Memory Optimization:
-
DEALLOCATE now clears client prepared statements cache: When a client sends
DEALLOCATE <name>orDEALLOCATE ALLvia 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_sizeconfiguration parameter: Added protection against malicious or misbehaving clients that don't callDEALLOCATEand could exhaust server memory by creating unlimited prepared statements. When the per-client cache limit is reached, the oldest entry is evicted automatically. Set to0for unlimited (default, relies on client callingDEALLOCATE). Example:client_prepared_statements_cache_size: 1024limits 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)
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_attimestamp 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). Nowquery_start_atis updated for each new message in the transaction loop, ensuring accurate per-query timing.
New Features:
-
New
clock_resolution_statisticsconfiguration parameter: Addedgeneral.clock_resolution_statisticsparameter (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
ussuffix 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
- New
v3.1.5 (Jan 27, 2026)
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
generatecommand 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", ... }]
- The
- 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 ofconnect_timeout: 3000
- Duration:
- Foreground mode binary upgrade: Added support for binary upgrade in foreground mode by passing the listener socket to the new process via
--inherit-fdargument. 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 newmax_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;orbegin;query (simple query protocol), the pooler now defers acquiring a server connection until the next message arrives. SinceBEGINitself 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) afterBEGIN, no server connection is acquired at all - The deferred
BEGINis automatically sent to the server before the actual query
- Statistics module optimization: Major refactoring of the
src/statsmodule for improved performance:- Replaced
VecDequewith HDR histograms (hdrhistogramcrate) 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.
- Replaced
v3.0.5 (Jan 16, 2026)
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 withClientBadStartuperror.
Testing:
- Integration fuzz testing framework: Added comprehensive BDD-based fuzz tests (
@fuzztag) 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)
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)
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.