diff --git a/documentation/en/src/changelog.md b/documentation/en/src/changelog.md index 2374278..b514083 100644 --- a/documentation/en/src/changelog.md +++ b/documentation/en/src/changelog.md @@ -26,7 +26,7 @@ - **PAUSE, RESUME, RECONNECT admin commands**: New admin console commands for managing connection pools. `PAUSE [db]` blocks new backend connection acquisition (active transactions continue). `RESUME [db]` lifts the pause and unblocks waiting clients. `RECONNECT [db]` forces connection rotation by incrementing the pool epoch — idle connections are immediately closed and active connections are discarded when returned to the pool. Without arguments, all pools are affected; with a database name, only matching pools. Specifying a nonexistent database returns an error. Use `SHOW POOLS` to see the `paused` status column. -- **`default_min_pool_size` for dynamic auth_query passthrough pools**: New `auth_query.default_min_pool_size` setting controls the minimum number of backend connections maintained per dynamic user pool in passthrough mode. Connections are prewarmed in the background when the pool is first created and replenished by the retain cycle after `server_lifetime` expiry. Pools with `default_min_pool_size > 0` are never garbage-collected. Default is `0` (no prewarm — backward compatible). Note: total backend connections scale as `active_users × default_min_pool_size`. +- **`min_pool_size` for dynamic auth_query passthrough pools**: New `auth_query.min_pool_size` setting controls the minimum number of backend connections maintained per dynamic user pool in passthrough mode. Connections are prewarmed in the background when the pool is first created and replenished by the retain cycle after `server_lifetime` expiry. Pools with `min_pool_size > 0` are never garbage-collected. Default is `0` (no prewarm — backward compatible). Note: total backend connections scale as `active_users × min_pool_size`. ### 3.3.1 Feb 26, 2026 diff --git a/pg_doorman.toml b/pg_doorman.toml index f109228..900122d 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -533,7 +533,7 @@ pool_size = 40 # auth_query.server_user = "app" # auth_query.server_password = "secret" # auth_query.default_pool_size = 40 -# auth_query.default_min_pool_size = 0 +# auth_query.min_pool_size = 0 # auth_query.cache_ttl = 3600000 # auth_query.cache_failure_ttl = 30000 # auth_query.min_interval = 1000 diff --git a/pg_doorman.yaml b/pg_doorman.yaml index 7538f52..5462115 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -578,7 +578,7 @@ pools: # server_user: "app" # server_password: "secret" # default_pool_size: 40 - # default_min_pool_size: 0 + # min_pool_size: 0 # cache_ttl: "1h" # cache_failure_ttl: "30s" # min_interval: "1s" diff --git a/src/app/generate/annotated.rs b/src/app/generate/annotated.rs index dc8f178..f71df6a 100644 --- a/src/app/generate/annotated.rs +++ b/src/app/generate/annotated.rs @@ -1510,7 +1510,7 @@ fn write_auth_query_commented_example(w: &mut ConfigWriter) { w.comment(fi, "auth_query.server_user = \"app\""); w.comment(fi, "auth_query.server_password = \"secret\""); w.comment(fi, "auth_query.default_pool_size = 40"); - w.comment(fi, "auth_query.default_min_pool_size = 0"); + w.comment(fi, "auth_query.min_pool_size = 0"); w.comment(fi, "auth_query.cache_ttl = 3600000"); w.comment(fi, "auth_query.cache_failure_ttl = 30000"); w.comment(fi, "auth_query.min_interval = 1000"); @@ -1528,7 +1528,7 @@ fn write_auth_query_commented_example(w: &mut ConfigWriter) { w.comment(fi, " server_user: \"app\""); w.comment(fi, " server_password: \"secret\""); w.comment(fi, " default_pool_size: 40"); - w.comment(fi, " default_min_pool_size: 0"); + w.comment(fi, " min_pool_size: 0"); w.comment(fi, " cache_ttl: \"1h\""); w.comment(fi, " cache_failure_ttl: \"30s\""); w.comment(fi, " min_interval: \"1s\""); @@ -1973,7 +1973,7 @@ mod tests { "server_user", "server_password", "default_pool_size", - "default_min_pool_size", + "min_pool_size", "cache_ttl", "cache_failure_ttl", "min_interval", diff --git a/src/app/generate/docs.rs b/src/app/generate/docs.rs index 7b661f4..e41292a 100644 --- a/src/app/generate/docs.rs +++ b/src/app/generate/docs.rs @@ -311,7 +311,7 @@ fn write_auth_query_section(out: &mut String) { "server_user", "server_password", "default_pool_size", - "default_min_pool_size", + "min_pool_size", "cache_ttl", "cache_failure_ttl", "min_interval", diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index 9a42c62..712aaaf 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -1219,11 +1219,11 @@ fields: doc: "Pool size for dynamic user data connections (per-user in passthrough mode, shared in dedicated mode)." default: "40" - default_min_pool_size: + min_pool_size: config: en: "Minimum connections to maintain per dynamic user pool (passthrough mode only)." ru: "Минимальное количество поддерживаемых соединений для каждого динамического пула (только passthrough-режим)." - doc: "Minimum number of backend connections to maintain per dynamic user pool in passthrough mode. Connections are prewarmed when the pool is first created and replenished by the retain cycle. Set to `0` to disable (default). Note: pools with `default_min_pool_size > 0` are never garbage-collected, and total backend connections scale as `active_users × default_min_pool_size`." + doc: "Minimum number of backend connections to maintain per dynamic user pool in passthrough mode. Connections are prewarmed when the pool is first created and replenished by the retain cycle. Set to `0` to disable (default). Note: pools with `min_pool_size > 0` are never garbage-collected, and total backend connections scale as `active_users × min_pool_size`." default: "0" cache_ttl: diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index 4c858eb..e26a201 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -692,7 +692,7 @@ mod tests { server_user: None, server_password: None, default_pool_size: 40, - default_min_pool_size: 0, + min_pool_size: 0, cache_ttl: Duration::from_hours(1), cache_failure_ttl: Duration::from_secs(30), min_interval: Duration::from_secs(1), diff --git a/src/config/pool.rs b/src/config/pool.rs index 1ad4f32..287382e 100644 --- a/src/config/pool.rs +++ b/src/config/pool.rs @@ -213,9 +213,9 @@ impl Pool { if aq.pool_size == 0 { return Err(Error::BadConfig("auth_query.pool_size must be > 0".into())); } - if aq.default_min_pool_size > aq.default_pool_size { + if aq.min_pool_size > aq.default_pool_size { return Err(Error::BadConfig( - "auth_query: default_min_pool_size must be <= default_pool_size".into(), + "auth_query: min_pool_size must be <= default_pool_size".into(), )); } } @@ -285,7 +285,7 @@ pub struct AuthQueryConfig { /// Minimum connections to maintain per dynamic user pool (default: 0 = no prewarm). /// Only applies in passthrough mode (when server_user is not set). #[serde(default)] - pub default_min_pool_size: u32, + pub min_pool_size: u32, /// Max cache age for positive entries (default: "1h"). #[serde(default = "AuthQueryConfig::default_cache_ttl")] diff --git a/src/config/tests.rs b/src/config/tests.rs index acada3b..656cc46 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -1014,7 +1014,7 @@ async fn test_auth_query_validate_empty_query() { server_user: None, server_password: None, default_pool_size: 40, - default_min_pool_size: 0, + min_pool_size: 0, cache_ttl: Duration::from_hours(1), cache_failure_ttl: Duration::from_secs(30), min_interval: Duration::from_secs(1), @@ -1040,7 +1040,7 @@ async fn test_auth_query_validate_empty_user() { server_user: None, server_password: None, default_pool_size: 40, - default_min_pool_size: 0, + min_pool_size: 0, cache_ttl: Duration::from_hours(1), cache_failure_ttl: Duration::from_secs(30), min_interval: Duration::from_secs(1), @@ -1066,7 +1066,7 @@ async fn test_auth_query_validate_server_password_without_server_user() { server_user: None, server_password: Some("orphan_password".to_string()), default_pool_size: 40, - default_min_pool_size: 0, + min_pool_size: 0, cache_ttl: Duration::from_hours(1), cache_failure_ttl: Duration::from_secs(30), min_interval: Duration::from_secs(1), @@ -1092,7 +1092,7 @@ async fn test_auth_query_validate_server_user_without_password_ok() { server_user: Some("backend_user".to_string()), server_password: None, default_pool_size: 40, - default_min_pool_size: 0, + min_pool_size: 0, cache_ttl: Duration::from_hours(1), cache_failure_ttl: Duration::from_secs(30), min_interval: Duration::from_secs(1), @@ -1115,7 +1115,7 @@ async fn test_auth_query_validate_pool_size_zero() { server_user: None, server_password: None, default_pool_size: 40, - default_min_pool_size: 0, + min_pool_size: 0, cache_ttl: Duration::from_hours(1), cache_failure_ttl: Duration::from_secs(30), min_interval: Duration::from_secs(1), @@ -1141,7 +1141,7 @@ async fn test_auth_query_validate_empty_password_ok() { server_user: None, server_password: None, default_pool_size: 40, - default_min_pool_size: 0, + min_pool_size: 0, cache_ttl: Duration::from_hours(1), cache_failure_ttl: Duration::from_secs(30), min_interval: Duration::from_secs(1), diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 67a3d3c..5aa062a 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -1089,8 +1089,8 @@ pub fn create_dynamic_pool( username: username.to_string(), password: String::new(), pool_size: aq_config.default_pool_size, - min_pool_size: if aq_config.default_min_pool_size > 0 { - Some(aq_config.default_min_pool_size) + min_pool_size: if aq_config.min_pool_size > 0 { + Some(aq_config.min_pool_size) } else { None }, @@ -1209,21 +1209,21 @@ pub fn create_dynamic_pool( POOLS.store(Arc::new(new_pools)); register_dynamic_pool(&identifier); - // Prewarm: spawn background task to create default_min_pool_size connections - if aq_config.default_min_pool_size > 0 { + // Prewarm: spawn background task to create min_pool_size connections + if aq_config.min_pool_size > 0 { let pool_clone = conn_pool.clone(); - let min = aq_config.default_min_pool_size as usize; + let min = aq_config.min_pool_size as usize; let pn = pool_name.to_string(); let un = username.to_string(); tokio::spawn(async move { let created = pool_clone.database.replenish(min).await; if created > 0 { info!( - "[pool: {pn}][user: {un}] prewarmed {created} dynamic connection(s) (default_min_pool_size: {min})" + "[pool: {pn}][user: {un}] prewarmed {created} dynamic connection(s) (min_pool_size: {min})" ); } else { warn!( - "[pool: {pn}][user: {un}] dynamic prewarm failed (default_min_pool_size: {min})" + "[pool: {pn}][user: {un}] dynamic prewarm failed (min_pool_size: {min})" ); } }); diff --git a/tests/bdd/auth_query_helper.rs b/tests/bdd/auth_query_helper.rs index 085278e..16a2d00 100644 --- a/tests/bdd/auth_query_helper.rs +++ b/tests/bdd/auth_query_helper.rs @@ -22,7 +22,7 @@ fn build_test_config(query: &str, pool_size: u32, database: Option) -> A server_user: None, server_password: None, default_pool_size: 40, - default_min_pool_size: 0, + min_pool_size: 0, cache_ttl: Duration::from_hours(1), cache_failure_ttl: Duration::from_secs(30), min_interval: Duration::from_secs(1), diff --git a/tests/bdd/features/auth-query-min-pool-size.feature b/tests/bdd/features/auth-query-min-pool-size.feature index 9adbb7b..7ed0dc5 100644 --- a/tests/bdd/features/auth-query-min-pool-size.feature +++ b/tests/bdd/features/auth-query-min-pool-size.feature @@ -1,10 +1,10 @@ @auth-query @auth-query-min-pool-size -Feature: Auth query default_min_pool_size for dynamic passthrough pools +Feature: Auth query min_pool_size for dynamic passthrough pools - Tests that default_min_pool_size prewarmed connections are created and maintained + Tests that min_pool_size prewarmed connections are created and maintained for dynamic auth_query passthrough pools. - Scenario: Dynamic pool prewarm with default_min_pool_size + Scenario: Dynamic pool prewarm with min_pool_size Given PostgreSQL started with pg_hba.conf: """ local all all trust @@ -49,7 +49,7 @@ Feature: Auth query default_min_pool_size for dynamic passthrough pools password: "" pool_size: 1 default_pool_size: 5 - default_min_pool_size: 2 + min_pool_size: 2 cache_ttl: "1h" cache_failure_ttl: "30s" min_interval: "0s" @@ -59,12 +59,12 @@ Feature: Auth query default_min_pool_size for dynamic passthrough pools And we send SimpleQuery "SELECT 1" to session "md5_session" # Wait for prewarm + retain cycles to establish min_pool_size connections When we sleep for 2000 milliseconds - # Check server connections — should have at least 2 (default_min_pool_size=2) + # Check server connections — should have at least 2 (min_pool_size=2) When we create admin session "admin1" to pg_doorman as "admin" with password "admin" And we execute "SHOW SERVERS" on admin session "admin1" and store row count Then admin session "admin1" row count should be greater than or equal to 2 - Scenario: Dynamic pool maintains default_min_pool_size after server_lifetime expiry + Scenario: Dynamic pool maintains min_pool_size after server_lifetime expiry Given PostgreSQL started with pg_hba.conf: """ local all all trust @@ -110,7 +110,7 @@ Feature: Auth query default_min_pool_size for dynamic passthrough pools password: "" pool_size: 1 default_pool_size: 5 - default_min_pool_size: 2 + min_pool_size: 2 cache_ttl: "1h" cache_failure_ttl: "30s" min_interval: "0s" diff --git a/tests/bdd/features/pool-lifecycle-e2e.feature b/tests/bdd/features/pool-lifecycle-e2e.feature index dff2442..eeb1deb 100644 --- a/tests/bdd/features/pool-lifecycle-e2e.feature +++ b/tests/bdd/features/pool-lifecycle-e2e.feature @@ -74,7 +74,7 @@ Feature: Pool lifecycle end-to-end (prewarm → scale up → shrink) @e2e-auth-query-lifecycle Scenario: Auth_query pool lifecycle: scale up under load → shrink to 0 after retain # auth_query dynamic pool with default_pool_size=5, pool-level server_lifetime=1000ms. - # default_min_pool_size is not set (defaults to 0), so no prewarm. + # min_pool_size is not set (defaults to 0), so no prewarm. # Phase 1: Open 5 concurrent transactions via auth_query user → pool scales to 5. # Phase 2: Release all, wait for lifetime+retain → pool shrinks to 0. Given PostgreSQL started with pg_hba.conf: @@ -144,6 +144,6 @@ Feature: Pool lifecycle end-to-end (prewarm → scale up → shrink) And we send SimpleQuery "COMMIT" to session "s5" # Wait for pool-level server_lifetime (1000ms ±20%) + retain cycles When we sleep for 4000 milliseconds - # All connections should be closed (default_min_pool_size=0, no prewarm) + # All connections should be closed (min_pool_size=0, no prewarm) And we execute "SHOW SERVERS" on admin session "admin1" and store row count Then admin session "admin1" row count should be 0