From 9bdf028dcb1c59f9c945deff4c2ac145ffdb3df2 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 20:15:23 +0300 Subject: [PATCH 01/72] chore: ignore docs/superpowers (private session artifacts) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b44bddaac..d35026c8d 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,9 @@ flamegraph-output/ # Superpowers brainstorm session workdir (VC mockups, server state) .superpowers/ +# Superpowers spec drafts (private session artifacts) +/docs/superpowers/ + # Per-session agent handoff scratchpads — not part of public history. .local/ From b74df6b0b90d7bf92873604df88ef09036987b5a Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 20:58:25 +0300 Subject: [PATCH 02/72] feat(config): startup_parameters field on general and pool with validation Adds operator-supplied PostgreSQL GUC injection at config level (general and per-pool). Field is BTreeMap, validated for reserved keys (user/database/replication/options/_pq_.*), syntax (PG GUC naming including namespaced like auto_explain.log_min_duration), null-bytes in values, and overall 15 KiB operator budget within PG's 16 KiB StartupMessage cap. No runtime behavior change yet: nothing reads the field. Wire-format integration and resolution land in subsequent commits on this branch. --- pg_doorman.toml | 20 ++++ pg_doorman.yaml | 23 ++++ src/app/generate/annotated.rs | 37 +++++++ src/app/generate/fields.yaml | 43 ++++++++ src/app/generate/mod.rs | 1 + src/app/generate/tests.rs | 1 + src/config/general.rs | 8 ++ src/config/mod.rs | 8 ++ src/config/pool.rs | 13 +++ src/config/startup_parameters.rs | 181 +++++++++++++++++++++++++++++++ src/config/tests.rs | 40 +++++++ 11 files changed, 375 insertions(+) create mode 100644 src/config/startup_parameters.rs diff --git a/pg_doorman.toml b/pg_doorman.toml index 4a64f3861..e4018aa8f 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -406,6 +406,19 @@ hba = [] # host all all 0.0.0.0/0 reject # """ +# -------------------------------------------------------------------------- +# PostgreSQL Startup Parameters (operator-defined GUCs) +# -------------------------------------------------------------------------- + +# Operator-supplied PostgreSQL startup parameters (GUCs) injected +# into every backend's StartupMessage as session defaults. +# They become pg_settings.reset_val and survive RESET ALL / DISCARD ALL. +# Per-pool startup_parameters override matching keys from general. +# Reserved keys (user, database, replication, options, _pq_.*) are rejected. +# Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } +# Default: {} (empty) +# startup_parameters = { plan_cache_mode = "force_custom_plan", work_mem = "64MB" } + # ############################################################################ # WEB UI / METRICS # ############################################################################ @@ -601,6 +614,13 @@ cleanup_server_connections = true # Default: false log_client_parameter_status_changes = false +# Operator-supplied PostgreSQL startup parameters (GUCs) injected +# into every backend's StartupMessage for this pool. Overrides +# matching keys from general.startup_parameters. +# Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } +# Default: {} (empty) +# startup_parameters = { plan_cache_mode = "force_custom_plan" } + # -------------------------------------------------------------------------- # Users Configuration (TOML uses indexed format) # -------------------------------------------------------------------------- diff --git a/pg_doorman.yaml b/pg_doorman.yaml index f5edc640d..2dbe094da 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -446,6 +446,21 @@ general: # # Reject all other connections # host all all 0.0.0.0/0 reject + # -------------------------------------------------------------------------- + # PostgreSQL Startup Parameters (operator-defined GUCs) + # -------------------------------------------------------------------------- + + # Operator-supplied PostgreSQL startup parameters (GUCs) injected + # into every backend's StartupMessage as session defaults. + # They become pg_settings.reset_val and survive RESET ALL / DISCARD ALL. + # Per-pool startup_parameters override matching keys from general. + # Reserved keys (user, database, replication, options, _pq_.*) are rejected. + # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } + # Default: {} (empty) + # startup_parameters: + # plan_cache_mode: force_custom_plan + # work_mem: 64MB + # ############################################################################ # WEB UI / METRICS # ############################################################################ @@ -645,6 +660,14 @@ pools: # Default: false log_client_parameter_status_changes: false + # Operator-supplied PostgreSQL startup parameters (GUCs) injected + # into every backend's StartupMessage for this pool. Overrides + # matching keys from general.startup_parameters. + # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } + # Default: {} (empty) + # startup_parameters: + # plan_cache_mode: force_custom_plan + # -------------------------------------------------------------------------- # Users Configuration # -------------------------------------------------------------------------- diff --git a/src/app/generate/annotated.rs b/src/app/generate/annotated.rs index 18490e5d8..a01176b7e 100644 --- a/src/app/generate/annotated.rs +++ b/src/app/generate/annotated.rs @@ -175,6 +175,7 @@ pub fn generate_reference_config(format: ConfigFormat, russian: bool) -> String server_tls_certificate: None, server_tls_private_key: None, auth_query: None, + startup_parameters: std::collections::BTreeMap::new(), users: vec![User { username: "app_user".to_string(), password: "md5dd9a0f26a4302744db881776a09bbfad".to_string(), @@ -1027,6 +1028,26 @@ fn write_general_section(w: &mut ConfigWriter, config: &Config) { w.comment(fi, ""); write_pg_hba_rule_examples(w, fi); w.blank(); + + // --- PostgreSQL Startup Parameters (operator-defined GUCs) --- + w.separator(fi, f.section_title("startup_parameters").get(w.russian)); + w.blank(); + + write_field_comment(w, fi, "general", "startup_parameters"); + match w.format { + ConfigFormat::Toml => { + w.comment( + fi, + "startup_parameters = { plan_cache_mode = \"force_custom_plan\", work_mem = \"64MB\" }", + ); + } + ConfigFormat::Yaml => { + w.comment(fi, "startup_parameters:"); + w.comment(fi, " plan_cache_mode: force_custom_plan"); + w.comment(fi, " work_mem: 64MB"); + } + } + w.blank(); } fn write_pg_hba_examples(w: &mut ConfigWriter, fi: usize) { @@ -1487,6 +1508,22 @@ fn write_single_pool(w: &mut ConfigWriter, pool_name: &str, pool: &Pool) { ); w.blank(); + // --- Per-pool Startup Parameters --- + write_field_comment(w, fi, "pool", "startup_parameters"); + match w.format { + ConfigFormat::Toml => { + w.comment( + fi, + "startup_parameters = { plan_cache_mode = \"force_custom_plan\" }", + ); + } + ConfigFormat::Yaml => { + w.comment(fi, "startup_parameters:"); + w.comment(fi, " plan_cache_mode: force_custom_plan"); + } + } + w.blank(); + write_pool_users(w, pool_name, &pool.users); write_auth_query_commented_example(w); } diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index fd4d8dd4c..7d0fa7454 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -53,6 +53,9 @@ sections: hba: en: "Access Control (pg_hba - Recommended)" ru: "Контроль доступа (pg_hba — рекомендуется)" + startup_parameters: + en: "PostgreSQL Startup Parameters (operator-defined GUCs)" + ru: "PostgreSQL Startup-параметры (операторские GUC)" pool_server: en: "Server Connection Settings" ru: "Настройки подключения к серверу" @@ -1146,6 +1149,30 @@ fields: - For authentication methods other than `trust`, PgDoorman performs the corresponding challenge/response with the client. - For Talos/JWT/PAM flows configured at the pool/user level, `trust` still bypasses the client password prompt; however, those modes may be used when `trust` does not match. + startup_parameters: + config: + en: | + Operator-supplied PostgreSQL startup parameters (GUCs) injected + into every backend's StartupMessage as session defaults. + They become pg_settings.reset_val and survive RESET ALL / DISCARD ALL. + Per-pool startup_parameters override matching keys from general. + Reserved keys (user, database, replication, options, _pq_.*) are rejected. + Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } + ru: | + PostgreSQL startup-параметры (GUC), задаваемые оператором. + Передаются в StartupMessage каждого backend как session defaults, + становятся pg_settings.reset_val и переживают RESET ALL / DISCARD ALL. + Параметры на уровне пула перекрывают совпадающие ключи из general. + Зарезервированные ключи (user, database, replication, options, _pq_.*) отклоняются. + Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } + doc: | + A map of PostgreSQL GUC names to string values, applied as session defaults at backend startup via the protocol-level `StartupMessage`. Because they enter the session at the same precedence level as `-c`/`PGOPTIONS`, the values become each parameter's `reset_val` in `pg_settings` and are restored automatically after a client `RESET ALL` or `DISCARD ALL`. + + Common use cases: `plan_cache_mode`, `statement_timeout`, `lock_timeout`, `idle_in_transaction_session_timeout`, namespaced GUCs like `auto_explain.log_min_duration`. + + Validation refuses reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`), keys that violate PG GUC naming (`^[A-Za-z_][A-Za-z0-9_.]*$`), values containing null bytes, and maps whose serialised size would exceed the 15 KiB operator budget within PG's 16 KiB `StartupMessage` cap. + default: "{} (empty)" + pool: server_host: config: @@ -1445,6 +1472,22 @@ fields: doc: "Per-pool override of `server_tls_private_key`." default: "None (uses global setting)" + startup_parameters: + config: + en: | + Operator-supplied PostgreSQL startup parameters (GUCs) injected + into every backend's StartupMessage for this pool. Overrides + matching keys from general.startup_parameters. + Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } + ru: | + PostgreSQL startup-параметры (GUC) для этого пула, передаются + каждому backend через StartupMessage. Перекрывают совпадающие + ключи из general.startup_parameters. + Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } + doc: | + Per-pool override of [`general.startup_parameters`](#startup-parameters). Keys present in both maps take the per-pool value; keys present only in `general` still apply. See the general-level docs for validation rules and wire-level semantics. + default: "{} (empty)" + user: username: config: diff --git a/src/app/generate/mod.rs b/src/app/generate/mod.rs index 2eb45acfc..0203400e1 100644 --- a/src/app/generate/mod.rs +++ b/src/app/generate/mod.rs @@ -168,6 +168,7 @@ pub fn generate_config_with_client( server_tls_certificate: None, server_tls_private_key: None, auth_query: None, + startup_parameters: std::collections::BTreeMap::new(), users: users.clone(), }, ); diff --git a/src/app/generate/tests.rs b/src/app/generate/tests.rs index c78099637..1f4d0713a 100644 --- a/src/app/generate/tests.rs +++ b/src/app/generate/tests.rs @@ -91,6 +91,7 @@ pub fn generate_config_with_client< patroni_api_timeout: None, fallback_connect_timeout: None, fallback_lifetime: None, + startup_parameters: std::collections::BTreeMap::new(), users: users_vec.clone(), }, ); diff --git a/src/config/general.rs b/src/config/general.rs index d4154da9a..0f7cdaed0 100644 --- a/src/config/general.rs +++ b/src/config/general.rs @@ -268,6 +268,13 @@ pub struct General { // New pg_hba rules: either inline content or a file path (see `PgHba` deserialization). #[serde(default, skip_serializing)] pub pg_hba: Option, + + /// Operator-supplied PostgreSQL startup parameters injected into every + /// backend's `StartupMessage`. Becomes `pg_settings.reset_val` and + /// survives `RESET ALL`/`DISCARD ALL`. Cascade: `general` -> `pool` -> + /// `auth_query` (per-key override, more specific wins). + #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] + pub startup_parameters: std::collections::BTreeMap, } impl General { @@ -585,6 +592,7 @@ impl Default for General { Self::default_query_interner_anon_idle_ttl_seconds(), hba: Self::default_hba(), pg_hba: None, + startup_parameters: std::collections::BTreeMap::new(), daemon_pid_file: Self::default_daemon_pid_file(), syslog_prog_name: None, pooler_check_query: Self::default_pooler_check_query(), diff --git a/src/config/mod.rs b/src/config/mod.rs index f4bb9c34a..567e49cae 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -27,6 +27,7 @@ mod duration; mod general; mod include; mod pool; +pub mod startup_parameters; mod talos; pub mod tls; mod user; @@ -421,6 +422,13 @@ impl Config { // Validate Talos self.talos.validate().await?; + // Validate operator-supplied PostgreSQL startup parameters at the + // general level; per-pool maps are validated inside `Pool::validate`. + startup_parameters::validate( + &self.general.startup_parameters, + "general.startup_parameters", + )?; + if self.general.tls_rate_limit_per_second < 100 && self.general.tls_rate_limit_per_second != 0 { diff --git a/src/config/pool.rs b/src/config/pool.rs index 325cd5345..4a5c00a1b 100644 --- a/src/config/pool.rs +++ b/src/config/pool.rs @@ -176,6 +176,13 @@ pub struct Pool { #[serde(skip_serializing_if = "Option::is_none")] pub auth_query: Option, + /// Operator-supplied PostgreSQL startup parameters injected into every + /// backend's `StartupMessage`. Becomes `pg_settings.reset_val` and + /// survives `RESET ALL`/`DISCARD ALL`. Overrides matching keys from + /// `general.startup_parameters`. + #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] + pub startup_parameters: std::collections::BTreeMap, + #[serde( default = "Pool::default_users", deserialize_with = "deserialize_users" @@ -232,6 +239,11 @@ impl Pool { } pub async fn validate(&mut self) -> Result<(), Error> { + crate::config::startup_parameters::validate( + &self.startup_parameters, + "pool.startup_parameters", + )?; + // Validate scaling_warm_pool_ratio if let Some(ratio) = self.scaling_warm_pool_ratio { if ratio > 100 { @@ -457,6 +469,7 @@ impl Default for Pool { server_tls_certificate: None, server_tls_private_key: None, auth_query: None, + startup_parameters: std::collections::BTreeMap::new(), } } } diff --git a/src/config/startup_parameters.rs b/src/config/startup_parameters.rs new file mode 100644 index 000000000..cd3b8f70f --- /dev/null +++ b/src/config/startup_parameters.rs @@ -0,0 +1,181 @@ +//! Validation of operator-supplied PostgreSQL startup parameters. +//! +//! Used by [`crate::config::General`] and [`crate::config::Pool`] to refuse +//! configs that try to inject reserved protocol keys (user, database, +//! replication, options, `_pq_.*`), or that would exceed PG's 16 KiB +//! StartupMessage budget once concatenated with `user`+`database`+`application_name`. + +use std::collections::BTreeMap; + +use crate::errors::Error; + +/// PG `StartupMessage` length budget (length prefix + body). We reserve a +/// generous slice (1 KiB) for `user`/`database`/`application_name` to keep +/// validation predictable even when long usernames or database names are in +/// play; the remainder is available to operator-supplied parameters. +pub const MAX_STARTUP_PACKET_SIZE: usize = 16 * 1024; +pub const RESERVED_HEADROOM: usize = 1024; +pub const MAX_OPERATOR_BUDGET: usize = MAX_STARTUP_PACKET_SIZE - RESERVED_HEADROOM; + +/// Keys pg_doorman manages itself or that PG treats specially in the startup +/// packet. Operator must not put them in `startup_parameters`. +pub const RESERVED_KEYS: &[&str] = &["user", "database", "replication", "options"]; +pub const RESERVED_PREFIX: &str = "_pq_."; + +/// Allowed GUC name shape: ASCII letter / underscore, then letters / +/// digits / underscores / dots (for namespaced GUC like +/// `auto_explain.log_min_duration`). Equivalent to the regex +/// `^[A-Za-z_][A-Za-z0-9_.]*$`; hand-rolled to keep `regex` out of the +/// runtime dependency set. +fn is_valid_guc_name(key: &str) -> bool { + let mut bytes = key.bytes(); + let Some(first) = bytes.next() else { + return false; + }; + if !(first.is_ascii_alphabetic() || first == b'_') { + return false; + } + bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'.') +} + +/// Validate one map (general or per-pool). +/// +/// * `scope` — human-friendly label used in error messages, e.g. +/// `"general.startup_parameters"` or `"pool.startup_parameters"`. +pub fn validate(map: &BTreeMap, scope: &str) -> Result<(), Error> { + for (k, v) in map { + validate_key(k, scope)?; + validate_value(k, v, scope)?; + } + validate_total_size(map, scope) +} + +fn validate_key(key: &str, scope: &str) -> Result<(), Error> { + if key.is_empty() { + return Err(Error::BadConfig(format!("{scope}: empty key"))); + } + if RESERVED_KEYS.iter().any(|r| r.eq_ignore_ascii_case(key)) { + return Err(Error::BadConfig(format!( + "{scope}: '{key}' is reserved and managed by pg_doorman" + ))); + } + if key.starts_with(RESERVED_PREFIX) { + return Err(Error::BadConfig(format!( + "{scope}: '{key}' uses the reserved '_pq_.' prefix" + ))); + } + if !is_valid_guc_name(key) { + return Err(Error::BadConfig(format!( + "{scope}: '{key}' is not a valid GUC name (expected [A-Za-z_][A-Za-z0-9_.]*)" + ))); + } + Ok(()) +} + +fn validate_value(key: &str, value: &str, scope: &str) -> Result<(), Error> { + if value.as_bytes().contains(&0) { + return Err(Error::BadConfig(format!( + "{scope}: value for '{key}' contains a null byte" + ))); + } + Ok(()) +} + +fn validate_total_size(map: &BTreeMap, scope: &str) -> Result<(), Error> { + // PG wire layout for each parameter: key\0value\0 — two trailing NULs per pair. + let total: usize = map.iter().map(|(k, v)| k.len() + 1 + v.len() + 1).sum(); + if total > MAX_OPERATOR_BUDGET { + return Err(Error::BadConfig(format!( + "{scope}: serialized size {total} bytes exceeds operator budget {MAX_OPERATOR_BUDGET} \ + (StartupMessage cap is {MAX_STARTUP_PACKET_SIZE}, {RESERVED_HEADROOM} reserved for \ + pg_doorman-managed keys)" + ))); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn m(pairs: &[(&str, &str)]) -> BTreeMap { + pairs + .iter() + .map(|(k, v)| ((*k).to_string(), (*v).to_string())) + .collect() + } + + #[test] + fn empty_map_is_valid() { + assert!(validate(&BTreeMap::new(), "general.startup_parameters").is_ok()); + } + + #[test] + fn plain_guc_is_valid() { + let map = m(&[ + ("plan_cache_mode", "force_custom_plan"), + ("work_mem", "64MB"), + ]); + assert!(validate(&map, "general.startup_parameters").is_ok()); + } + + #[test] + fn namespaced_guc_is_valid() { + let map = m(&[("auto_explain.log_min_duration", "100ms")]); + assert!(validate(&map, "pools.foo.startup_parameters").is_ok()); + } + + #[test] + fn reserved_user_rejected() { + let err = validate(&m(&[("user", "x")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); + } + + #[test] + fn reserved_database_rejected_case_insensitive() { + let err = validate(&m(&[("DATABASE", "x")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); + } + + #[test] + fn pq_prefix_rejected() { + let err = validate(&m(&[("_pq_.fancy_ext", "x")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(_))); + } + + #[test] + fn empty_key_rejected() { + let err = validate(&m(&[("", "x")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref m) if m.contains("empty key"))); + } + + #[test] + fn weird_chars_rejected() { + let err = validate(&m(&[("bad name", "x")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(_))); + } + + #[test] + fn null_byte_in_value_rejected() { + let err = validate(&m(&[("work_mem", "64\0MB")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref m) if m.contains("null byte"))); + } + + #[test] + fn oversize_rejected() { + // 16 keys × 1 KiB value > 15 KiB operator budget. + let big: BTreeMap = (0..16) + .map(|i| (format!("key{i}"), "a".repeat(1024))) + .collect(); + let err = validate(&big, "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref m) if m.contains("exceeds operator budget"))); + } + + #[test] + fn application_name_is_not_reserved() { + // Operator-wins (B2): explicitly allowed; pg_doorman default merges + // happen in the wire layer, not validation. + let map = m(&[("application_name", "my_app")]); + assert!(validate(&map, "scope").is_ok()); + } +} diff --git a/src/config/tests.rs b/src/config/tests.rs index edc014187..8924cffbc 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -2034,3 +2034,43 @@ sso_allowed_users = ["alice", "bob"] vec!["alice".to_string(), "bob".to_string()] ); } + +#[tokio::test] +async fn reject_reserved_in_general_startup_parameters() { + let mut cfg = Config::default(); + cfg.general + .startup_parameters + .insert("user".to_string(), "x".to_string()); + let err = cfg.validate().await.unwrap_err(); + match err { + Error::BadConfig(msg) => assert!( + msg.contains("general.startup_parameters") && msg.contains("reserved"), + "unexpected message: {msg}" + ), + other => panic!("expected BadConfig, got {other:?}"), + } +} + +#[tokio::test] +async fn reject_reserved_in_pool_startup_parameters() { + let mut cfg = Config::default(); + cfg.general.tls_rate_limit_per_second = 0; + let mut pool = Pool::default(); + pool.startup_parameters + .insert("database".to_string(), "x".to_string()); + pool.users.push(User { + username: "u".to_string(), + password: "p".to_string(), + pool_size: 1, + ..User::default() + }); + cfg.pools.insert("p".to_string(), pool); + let err = cfg.validate().await.unwrap_err(); + match err { + Error::BadConfig(msg) => assert!( + msg.contains("pool.startup_parameters") && msg.contains("reserved"), + "unexpected message: {msg}" + ), + other => panic!("expected BadConfig, got {other:?}"), + } +} From b95987b78e434266637c7eeed049492ce67a5a8e Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 21:18:02 +0300 Subject: [PATCH 03/72] fix(config): align startup_parameters budget with PG StartupMessage cap The previous commit chose a 15 KiB operator budget anchored on an incorrect 16 KiB ceiling. PostgreSQL's actual cap is 10 000 bytes (MAX_STARTUP_PACKET_LENGTH, src/include/libpq/pqcomm.h), enforced as an anti-DoS measure on every connection. A config that previously passed validation with parameters totalling 9.5-15 KiB would have been accepted by pg_doorman and silently rejected on the wire at every backend startup; the budget now reflects the real ceiling. Also pulls back operator-visible doc strings in the auto-generated reference config and on the General/Pool fields. The earlier text promised that parameters become 'pg_settings.reset_val' and cascade through auth_query - both true of the eventual feature but neither shipped by this commit. The docs now describe what is in fact delivered today: validation at config load. --- pg_doorman.toml | 19 +++++++------ pg_doorman.yaml | 19 +++++++------ src/app/generate/fields.yaml | 49 +++++++++++++++++++------------- src/config/general.rs | 10 ++++--- src/config/pool.rs | 10 ++++--- src/config/startup_parameters.rs | 44 ++++++++++++++++++++-------- 6 files changed, 95 insertions(+), 56 deletions(-) diff --git a/pg_doorman.toml b/pg_doorman.toml index e4018aa8f..768291b2c 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -410,11 +410,11 @@ hba = [] # PostgreSQL Startup Parameters (operator-defined GUCs) # -------------------------------------------------------------------------- -# Operator-supplied PostgreSQL startup parameters (GUCs) injected -# into every backend's StartupMessage as session defaults. -# They become pg_settings.reset_val and survive RESET ALL / DISCARD ALL. -# Per-pool startup_parameters override matching keys from general. -# Reserved keys (user, database, replication, options, _pq_.*) are rejected. +# Operator-supplied PostgreSQL configuration parameters. +# Reserved keys (user, database, replication, options, _pq_.*) +# and the StartupMessage budget are validated at config load. +# Wire injection ships in a later commit on the +# feat/startup-parameters branch. # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters = { plan_cache_mode = "force_custom_plan", work_mem = "64MB" } @@ -614,9 +614,12 @@ cleanup_server_connections = true # Default: false log_client_parameter_status_changes = false -# Operator-supplied PostgreSQL startup parameters (GUCs) injected -# into every backend's StartupMessage for this pool. Overrides -# matching keys from general.startup_parameters. +# Operator-supplied PostgreSQL configuration parameters for this +# pool. Reserved keys (user, database, replication, options, +# _pq_.*) and the StartupMessage budget are validated at config +# load. Per-pool overlay; the eventual cascade +# (general → pool → auth_query) is not yet active. Wire injection +# ships in a later commit on the feat/startup-parameters branch. # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters = { plan_cache_mode = "force_custom_plan" } diff --git a/pg_doorman.yaml b/pg_doorman.yaml index 2dbe094da..c686e2253 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -450,11 +450,11 @@ general: # PostgreSQL Startup Parameters (operator-defined GUCs) # -------------------------------------------------------------------------- - # Operator-supplied PostgreSQL startup parameters (GUCs) injected - # into every backend's StartupMessage as session defaults. - # They become pg_settings.reset_val and survive RESET ALL / DISCARD ALL. - # Per-pool startup_parameters override matching keys from general. - # Reserved keys (user, database, replication, options, _pq_.*) are rejected. + # Operator-supplied PostgreSQL configuration parameters. + # Reserved keys (user, database, replication, options, _pq_.*) + # and the StartupMessage budget are validated at config load. + # Wire injection ships in a later commit on the + # feat/startup-parameters branch. # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters: @@ -660,9 +660,12 @@ pools: # Default: false log_client_parameter_status_changes: false - # Operator-supplied PostgreSQL startup parameters (GUCs) injected - # into every backend's StartupMessage for this pool. Overrides - # matching keys from general.startup_parameters. + # Operator-supplied PostgreSQL configuration parameters for this + # pool. Reserved keys (user, database, replication, options, + # _pq_.*) and the StartupMessage budget are validated at config + # load. Per-pool overlay; the eventual cascade + # (general → pool → auth_query) is not yet active. Wire injection + # ships in a later commit on the feat/startup-parameters branch. # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters: diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index 7d0fa7454..645c61507 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -1152,25 +1152,25 @@ fields: startup_parameters: config: en: | - Operator-supplied PostgreSQL startup parameters (GUCs) injected - into every backend's StartupMessage as session defaults. - They become pg_settings.reset_val and survive RESET ALL / DISCARD ALL. - Per-pool startup_parameters override matching keys from general. - Reserved keys (user, database, replication, options, _pq_.*) are rejected. + Operator-supplied PostgreSQL configuration parameters. + Reserved keys (user, database, replication, options, _pq_.*) + and the StartupMessage budget are validated at config load. + Wire injection ships in a later commit on the + feat/startup-parameters branch. Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } ru: | - PostgreSQL startup-параметры (GUC), задаваемые оператором. - Передаются в StartupMessage каждого backend как session defaults, - становятся pg_settings.reset_val и переживают RESET ALL / DISCARD ALL. - Параметры на уровне пула перекрывают совпадающие ключи из general. - Зарезервированные ключи (user, database, replication, options, _pq_.*) отклоняются. + Конфигурационные параметры PostgreSQL, заданные оператором. + Зарезервированные ключи (user, database, replication, options, + _pq_.*) и бюджет StartupMessage проверяются на этапе загрузки + конфига. Передача параметров в backend появится в одном из + следующих коммитов ветки feat/startup-parameters. Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } doc: | - A map of PostgreSQL GUC names to string values, applied as session defaults at backend startup via the protocol-level `StartupMessage`. Because they enter the session at the same precedence level as `-c`/`PGOPTIONS`, the values become each parameter's `reset_val` in `pg_settings` and are restored automatically after a client `RESET ALL` or `DISCARD ALL`. + A map of PostgreSQL configuration parameter names to string values, intended as session defaults for every backend pg_doorman opens for this pool's clients. - Common use cases: `plan_cache_mode`, `statement_timeout`, `lock_timeout`, `idle_in_transaction_session_timeout`, namespaced GUCs like `auto_explain.log_min_duration`. + Validation enforced at config load: reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`) are refused; keys must match PG GUC naming (`^[A-Za-z_][A-Za-z0-9_.]*$`); values must not contain null bytes; the serialised map must fit within the operator budget carved out of PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) `StartupMessage` cap. - Validation refuses reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`), keys that violate PG GUC naming (`^[A-Za-z_][A-Za-z0-9_.]*$`), values containing null bytes, and maps whose serialised size would exceed the 15 KiB operator budget within PG's 16 KiB `StartupMessage` cap. + Wire-level injection into the backend `StartupMessage` and the `general` → `pool` → `auth_query` cascade resolution ship in subsequent commits on the `feat/startup-parameters` branch. The current commit only validates this map at config load. default: "{} (empty)" pool: @@ -1475,17 +1475,26 @@ fields: startup_parameters: config: en: | - Operator-supplied PostgreSQL startup parameters (GUCs) injected - into every backend's StartupMessage for this pool. Overrides - matching keys from general.startup_parameters. + Operator-supplied PostgreSQL configuration parameters for this + pool. Reserved keys (user, database, replication, options, + _pq_.*) and the StartupMessage budget are validated at config + load. Per-pool overlay; the eventual cascade + (general → pool → auth_query) is not yet active. Wire injection + ships in a later commit on the feat/startup-parameters branch. Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } ru: | - PostgreSQL startup-параметры (GUC) для этого пула, передаются - каждому backend через StartupMessage. Перекрывают совпадающие - ключи из general.startup_parameters. + Конфигурационные параметры PostgreSQL для этого пула, заданные + оператором. Зарезервированные ключи (user, database, + replication, options, _pq_.*) и бюджет StartupMessage + проверяются на этапе загрузки конфига. Перекрытие на уровне + пула; каскад general → pool → auth_query пока не активен. + Передача параметров в backend появится в одном из следующих + коммитов ветки feat/startup-parameters. Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } doc: | - Per-pool override of [`general.startup_parameters`](#startup-parameters). Keys present in both maps take the per-pool value; keys present only in `general` still apply. See the general-level docs for validation rules and wire-level semantics. + Per-pool map of PostgreSQL configuration parameters. Validation rules match those documented for [`general.startup_parameters`](#startup-parameters): reserved keys, GUC naming, null bytes, and the operator budget within PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) `StartupMessage` cap. + + Per-pool overlay semantics — the cascade `general` → `pool` → `auth_query` that selects the effective value for each key — and wire injection into the backend `StartupMessage` ship in subsequent commits on the `feat/startup-parameters` branch. The current commit only validates this map at config load. default: "{} (empty)" user: diff --git a/src/config/general.rs b/src/config/general.rs index 0f7cdaed0..771fe6640 100644 --- a/src/config/general.rs +++ b/src/config/general.rs @@ -269,10 +269,12 @@ pub struct General { #[serde(default, skip_serializing)] pub pg_hba: Option, - /// Operator-supplied PostgreSQL startup parameters injected into every - /// backend's `StartupMessage`. Becomes `pg_settings.reset_val` and - /// survives `RESET ALL`/`DISCARD ALL`. Cascade: `general` -> `pool` -> - /// `auth_query` (per-key override, more specific wins). + /// Operator-supplied PostgreSQL configuration parameters. Reserved + /// keys (`user`, `database`, `replication`, `options`, `_pq_.*`) and + /// the StartupMessage budget are validated at config load. Wire + /// injection into backend `StartupMessage` and the + /// general → pool → auth_query cascade resolution ship in subsequent + /// commits on the feat/startup-parameters branch. #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] pub startup_parameters: std::collections::BTreeMap, } diff --git a/src/config/pool.rs b/src/config/pool.rs index 4a5c00a1b..87551b154 100644 --- a/src/config/pool.rs +++ b/src/config/pool.rs @@ -176,10 +176,12 @@ pub struct Pool { #[serde(skip_serializing_if = "Option::is_none")] pub auth_query: Option, - /// Operator-supplied PostgreSQL startup parameters injected into every - /// backend's `StartupMessage`. Becomes `pg_settings.reset_val` and - /// survives `RESET ALL`/`DISCARD ALL`. Overrides matching keys from - /// `general.startup_parameters`. + /// Operator-supplied PostgreSQL configuration parameters. Reserved + /// keys (`user`, `database`, `replication`, `options`, `_pq_.*`) and + /// the StartupMessage budget are validated at config load. Wire + /// injection into backend `StartupMessage` and the + /// general → pool → auth_query cascade resolution ship in subsequent + /// commits on the feat/startup-parameters branch. #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] pub startup_parameters: std::collections::BTreeMap, diff --git a/src/config/startup_parameters.rs b/src/config/startup_parameters.rs index cd3b8f70f..b5ea67137 100644 --- a/src/config/startup_parameters.rs +++ b/src/config/startup_parameters.rs @@ -2,19 +2,22 @@ //! //! Used by [`crate::config::General`] and [`crate::config::Pool`] to refuse //! configs that try to inject reserved protocol keys (user, database, -//! replication, options, `_pq_.*`), or that would exceed PG's 16 KiB -//! StartupMessage budget once concatenated with `user`+`database`+`application_name`. +//! replication, options, `_pq_.*`), or that would exceed PG's +//! `MAX_STARTUP_PACKET_LENGTH` (10 000 bytes) StartupMessage body cap once +//! concatenated with `user`+`database`+`application_name`. use std::collections::BTreeMap; use crate::errors::Error; -/// PG `StartupMessage` length budget (length prefix + body). We reserve a -/// generous slice (1 KiB) for `user`/`database`/`application_name` to keep -/// validation predictable even when long usernames or database names are in -/// play; the remainder is available to operator-supplied parameters. -pub const MAX_STARTUP_PACKET_SIZE: usize = 16 * 1024; -pub const RESERVED_HEADROOM: usize = 1024; +/// PostgreSQL caps the StartupMessage body at 10 000 bytes +/// (`MAX_STARTUP_PACKET_LENGTH` in `src/include/libpq/pqcomm.h`) to prevent +/// memory-exhaustion attacks via oversize packets. pg_doorman reserves a +/// modest slice for its own `user`/`database`/`application_name` triple +/// and the protocol's per-pair NUL terminators; the rest is the budget +/// available to operator-supplied parameters. +pub const MAX_STARTUP_PACKET_SIZE: usize = 10_000; +pub const RESERVED_HEADROOM: usize = 512; pub const MAX_OPERATOR_BUDGET: usize = MAX_STARTUP_PACKET_SIZE - RESERVED_HEADROOM; /// Keys pg_doorman manages itself or that PG treats specially in the startup @@ -73,7 +76,7 @@ fn validate_key(key: &str, scope: &str) -> Result<(), Error> { } fn validate_value(key: &str, value: &str, scope: &str) -> Result<(), Error> { - if value.as_bytes().contains(&0) { + if value.as_bytes().contains(&b'\0') { return Err(Error::BadConfig(format!( "{scope}: value for '{key}' contains a null byte" ))); @@ -82,12 +85,13 @@ fn validate_value(key: &str, value: &str, scope: &str) -> Result<(), Error> { } fn validate_total_size(map: &BTreeMap, scope: &str) -> Result<(), Error> { - // PG wire layout for each parameter: key\0value\0 — two trailing NULs per pair. + // Per the PG wire layout each pair contributes key, NUL, value, NUL. let total: usize = map.iter().map(|(k, v)| k.len() + 1 + v.len() + 1).sum(); if total > MAX_OPERATOR_BUDGET { return Err(Error::BadConfig(format!( "{scope}: serialized size {total} bytes exceeds operator budget {MAX_OPERATOR_BUDGET} \ - (StartupMessage cap is {MAX_STARTUP_PACKET_SIZE}, {RESERVED_HEADROOM} reserved for \ + (PG StartupMessage cap is {MAX_STARTUP_PACKET_SIZE} bytes per \ + MAX_STARTUP_PACKET_LENGTH; {RESERVED_HEADROOM} reserved for \ pg_doorman-managed keys)" ))); } @@ -163,7 +167,7 @@ mod tests { #[test] fn oversize_rejected() { - // 16 keys × 1 KiB value > 15 KiB operator budget. + // 16 keys × 1 KiB value still overruns the 9 488-byte operator budget. let big: BTreeMap = (0..16) .map(|i| (format!("key{i}"), "a".repeat(1024))) .collect(); @@ -178,4 +182,20 @@ mod tests { let map = m(&[("application_name", "my_app")]); assert!(validate(&map, "scope").is_ok()); } + + #[test] + fn budget_matches_pg_startup_packet_cap() { + // Locks the constants in place — PG's MAX_STARTUP_PACKET_LENGTH + // (src/include/libpq/pqcomm.h) is 10 000; pg_doorman reserves + // 512 bytes for its own keys, leaving 9 488 for the operator. + // A future careless edit that drifts back to a 16 KiB ceiling + // would re-introduce silently-rejected configs on every backend + // startup; this assertion is the trip-wire. + assert_eq!(MAX_STARTUP_PACKET_SIZE, 10_000); + assert_eq!(RESERVED_HEADROOM, 512); + assert_eq!( + MAX_OPERATOR_BUDGET, + MAX_STARTUP_PACKET_SIZE - RESERVED_HEADROOM + ); + } } From 320e3c02e8d7453349da4c6ba523f532afb4e236 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 21:36:18 +0300 Subject: [PATCH 04/72] feat(protocol): startup() accepts operator-supplied extra parameters Extends the StartupMessage builder to carry an additional set of operator-supplied key/value pairs. Keys are serialized in iteration order after the required user/application_name/database triple, with one trailing NUL terminator. When the supplied map contains application_name, that value wins over the pg_doorman-managed default (D5/B2 of the design - operator-overrideable). Wire-format change only: every current caller passes an empty map, so on-wire output is byte-identical to the prior format. Subsequent commits on this branch wire the resolver and per-pool quarantine in. --- src/messages/protocol.rs | 134 ++++++++++++++++++++++++++++++++++- src/server/server_backend.rs | 1 + 2 files changed, 132 insertions(+), 3 deletions(-) diff --git a/src/messages/protocol.rs b/src/messages/protocol.rs index 403de58d0..639ef92a1 100644 --- a/src/messages/protocol.rs +++ b/src/messages/protocol.rs @@ -160,11 +160,19 @@ pub fn simple_query(query: &str) -> BytesMut { } /// Send startup message to the server. +/// +/// Required parameters (`user`, `application_name`, `database`) are written +/// first in their historical wire order, so the byte stream is unchanged when +/// `extra_params` is empty. Operator-supplied values from `extra_params` are +/// appended in the map's iteration order (BTreeMap → lexicographic). If +/// `extra_params` contains an `application_name` key, it overrides the +/// `application_name` argument per the design (D5/B2: operator-wins). pub async fn startup( stream: &mut S, user: String, database: &str, application_name: String, + extra_params: &std::collections::BTreeMap, ) -> Result<(), Error> where S: tokio::io::AsyncWrite + std::marker::Unpin, @@ -179,16 +187,33 @@ where bytes.put_slice(user.as_bytes()); bytes.put_u8(0); - // Application name + // Application name. Operator-supplied value in `extra_params` wins over + // the pg_doorman-managed default per design D5/B2. + let effective_app_name = extra_params + .get("application_name") + .map(String::as_str) + .unwrap_or(application_name.as_str()); bytes.put(&b"application_name\0"[..]); - bytes.put_slice(application_name.as_bytes()); + bytes.put_slice(effective_app_name.as_bytes()); bytes.put_u8(0); // Database bytes.put(&b"database\0"[..]); bytes.put_slice(database.as_bytes()); bytes.put_u8(0); - bytes.put_u8(0); // Null terminator + + // Operator-supplied extras (already-handled application_name is skipped). + for (key, value) in extra_params { + if key == "application_name" { + continue; + } + bytes.put_slice(key.as_bytes()); + bytes.put_u8(0); + bytes.put_slice(value.as_bytes()); + bytes.put_u8(0); + } + + bytes.put_u8(0); // Parameter list terminator let len = bytes.len() as i32 + 4i32; @@ -938,3 +963,106 @@ pub fn insert_close_complete_before_ready_for_query(mut buffer: BytesMut, count: buffer } } + +#[cfg(test)] +mod startup_tests { + use super::*; + use std::collections::BTreeMap; + + #[tokio::test] + async fn startup_with_extra_params_includes_them_in_order() { + let mut buf: Vec = Vec::new(); + let mut params = BTreeMap::new(); + params.insert( + "plan_cache_mode".to_string(), + "force_custom_plan".to_string(), + ); + params.insert("work_mem".to_string(), "64MB".to_string()); + + startup(&mut buf, "alice".into(), "appdb", "myapp".into(), ¶ms) + .await + .expect("startup"); + + // Skip the 4-byte length prefix and 4-byte protocol version. + let body = &buf[8..]; + let body_str = String::from_utf8_lossy(body); + assert!( + body_str.contains("user\0alice"), + "user pair not on wire: {body_str:?}" + ); + assert!( + body_str.contains("database\0appdb"), + "database pair not on wire: {body_str:?}" + ); + assert!( + body_str.contains("application_name\0myapp"), + "default application_name not on wire: {body_str:?}" + ); + assert!(body_str.contains("plan_cache_mode\0force_custom_plan")); + // `\x00` (explicit hex) instead of `\0` here because the following + // digits `64` would otherwise look like an octal escape to clippy. + assert!(body_str.contains("work_mem\x0064MB")); + // The parameter list terminates with a single NUL byte right before the + // end of the packet body. Buffer always ends with that terminator. + assert_eq!(*buf.last().expect("non-empty buffer"), 0); + } + + #[tokio::test] + async fn startup_with_empty_params_keeps_pre_feature_format() { + let mut buf: Vec = Vec::new(); + startup( + &mut buf, + "alice".into(), + "appdb", + "myapp".into(), + &BTreeMap::new(), + ) + .await + .expect("startup"); + let body = &buf[8..]; + let s = String::from_utf8_lossy(body); + assert!(s.contains("user\0alice")); + assert!(s.contains("database\0appdb")); + assert!(s.contains("application_name\0myapp")); + } + + #[tokio::test] + async fn startup_application_name_operator_override_wins() { + let mut buf: Vec = Vec::new(); + let mut params = BTreeMap::new(); + params.insert("application_name".to_string(), "operator_app".to_string()); + + startup(&mut buf, "alice".into(), "appdb", "ignored".into(), ¶ms) + .await + .expect("startup"); + + let body = &buf[8..]; + let s = String::from_utf8_lossy(body); + // Operator-supplied wins over the pg_doorman-managed default. + assert!(s.contains("application_name\0operator_app")); + // pg_doorman's own application_name argument value must not be on the wire. + assert!(!s.contains("application_name\0ignored")); + } + + /// Belt-and-suspenders: ensure the length prefix matches the byte count + /// PG reads — anything off here breaks every connection. + #[tokio::test] + async fn startup_length_prefix_matches_body() { + let mut buf: Vec = Vec::new(); + let mut params = BTreeMap::new(); + params.insert("k1".to_string(), "v1".to_string()); + params.insert("k2".to_string(), "v2".to_string()); + + startup(&mut buf, "u".into(), "d", "a".into(), ¶ms) + .await + .expect("startup"); + + let claimed_len = i32::from_be_bytes(buf[0..4].try_into().expect("4 bytes")); + assert_eq!( + claimed_len as usize, + buf.len(), + "claimed length {claimed_len} != actual {} bytes", + buf.len() + ); + } +} diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index 2ee998ab9..888992d31 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -802,6 +802,7 @@ impl Server { username.clone(), database, application_name.clone(), + &std::collections::BTreeMap::new(), ) .await?; From 7230ced755a1628cc8b69eb5d98fcb490cec8251 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 21:52:25 +0300 Subject: [PATCH 05/72] feat(server, pool): cascade resolver and per-pool quarantine state Two leaf modules with no integration yet: * src/pool/startup_resolver.rs: pure function that merges the general/pool/auth_query maps per the D1 cascade (union by key, more specific wins, auth_query > pool > general). * src/server/quarantine.rs: per-pool consecutive-rejection counter with TTL release. record_rejection returns Counting/JustQuarantined/ AlreadyQuarantined so callers can drive logs and metrics; release is TTL-only - keys that get quarantined are skipped in the StartupMessage, so a later success says nothing about whether the operator-supplied value was fixed. Adds two general-level knobs: startup_parameter_quarantine_threshold (default 3) and startup_parameter_quarantine_ttl (default 300000 ms). --- pg_doorman.toml | 12 ++ pg_doorman.yaml | 12 ++ src/app/generate/annotated.rs | 16 +++ src/app/generate/fields.yaml | 33 ++++++ src/config/general.rs | 29 +++++ src/pool/mod.rs | 1 + src/pool/startup_resolver.rs | 94 +++++++++++++++ src/server/mod.rs | 1 + src/server/quarantine.rs | 216 ++++++++++++++++++++++++++++++++++ 9 files changed, 414 insertions(+) create mode 100644 src/pool/startup_resolver.rs create mode 100644 src/server/quarantine.rs diff --git a/pg_doorman.toml b/pg_doorman.toml index 768291b2c..f10103882 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -410,6 +410,18 @@ hba = [] # PostgreSQL Startup Parameters (operator-defined GUCs) # -------------------------------------------------------------------------- +# Consecutive backend startup rejections for the same parameter +# before pg_doorman stops including it in the StartupMessage for +# this pool. Paired with startup_parameter_quarantine_ttl. +# Default: 3 +startup_parameter_quarantine_threshold = 3 + +# TTL (milliseconds) for a quarantined parameter. On expiry the +# key is re-included in the next backend StartupMessage and the +# rejection counter is reset. +# Default: 300000 +startup_parameter_quarantine_ttl = 300000 + # Operator-supplied PostgreSQL configuration parameters. # Reserved keys (user, database, replication, options, _pq_.*) # and the StartupMessage budget are validated at config load. diff --git a/pg_doorman.yaml b/pg_doorman.yaml index c686e2253..f1630bb97 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -450,6 +450,18 @@ general: # PostgreSQL Startup Parameters (operator-defined GUCs) # -------------------------------------------------------------------------- + # Consecutive backend startup rejections for the same parameter + # before pg_doorman stops including it in the StartupMessage for + # this pool. Paired with startup_parameter_quarantine_ttl. + # Default: 3 + startup_parameter_quarantine_threshold: 3 + + # TTL (milliseconds) for a quarantined parameter. On expiry the + # key is re-included in the next backend StartupMessage and the + # rejection counter is reset. + # Default: 300000 + startup_parameter_quarantine_ttl: 300000 + # Operator-supplied PostgreSQL configuration parameters. # Reserved keys (user, database, replication, options, _pq_.*) # and the StartupMessage budget are validated at config load. diff --git a/src/app/generate/annotated.rs b/src/app/generate/annotated.rs index a01176b7e..06d11383a 100644 --- a/src/app/generate/annotated.rs +++ b/src/app/generate/annotated.rs @@ -1033,6 +1033,22 @@ fn write_general_section(w: &mut ConfigWriter, config: &Config) { w.separator(fi, f.section_title("startup_parameters").get(w.russian)); w.blank(); + write_field_comment(w, fi, "general", "startup_parameter_quarantine_threshold"); + w.kv( + fi, + "startup_parameter_quarantine_threshold", + &w.num_val(g.startup_parameter_quarantine_threshold), + ); + w.blank(); + + write_field_comment(w, fi, "general", "startup_parameter_quarantine_ttl"); + w.kv( + fi, + "startup_parameter_quarantine_ttl", + &w.num_val(g.startup_parameter_quarantine_ttl), + ); + w.blank(); + write_field_comment(w, fi, "general", "startup_parameters"); match w.format { ConfigFormat::Toml => { diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index 645c61507..415f87c2b 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -1149,6 +1149,39 @@ fields: - For authentication methods other than `trust`, PgDoorman performs the corresponding challenge/response with the client. - For Talos/JWT/PAM flows configured at the pool/user level, `trust` still bypasses the client password prompt; however, those modes may be used when `trust` does not match. + startup_parameter_quarantine_threshold: + config: + en: | + Consecutive backend startup rejections for the same parameter + before pg_doorman stops including it in the StartupMessage for + this pool. Paired with startup_parameter_quarantine_ttl. + ru: | + Сколько подряд отказов backend startup'а по одному и тому же + параметру до того, как pg_doorman перестанет включать его в + StartupMessage для этого пула. Связан с + startup_parameter_quarantine_ttl. + doc: | + Counts consecutive `ErrorResponse`s from PG during backend startup that name the same `startup_parameters` key. Once the counter reaches this threshold, the key is quarantined and excluded from subsequent StartupMessages for this pool until `startup_parameter_quarantine_ttl` elapses. + + A value of `0` is clamped to `1` at runtime (quarantine on the first rejection). The intent of this knob is to absorb transient configuration errors without blackholing a key after a single backend hiccup; raise it for pools where startup errors are expected to recover on their own, lower it (down to 1) when an operator misconfiguration should drop the offending key immediately. + default: "3" + + startup_parameter_quarantine_ttl: + config: + en: | + TTL (milliseconds) for a quarantined parameter. On expiry the + key is re-included in the next backend StartupMessage and the + rejection counter is reset. + ru: | + TTL (миллисекунды) для параметра в карантине. По истечении ключ + снова включается в StartupMessage следующего backend'а, счётчик + отказов сбрасывается. + doc: | + Quarantine release is **TTL-only**: pg_doorman stops sending the key while it is quarantined, so a subsequent successful backend startup carries no information about whether the operator-supplied value was actually fixed. Releasing on success would oscillate between "quarantined" and "rejected again" as soon as the operator hadn't updated the config. + + On expiry the bookkeeping for the key is dropped: the next rejection starts counting from zero. Lower this knob to retry sooner after operator config changes; raise it to give the operator more time to react before pg_doorman tries again. + default: "300000" + startup_parameters: config: en: | diff --git a/src/config/general.rs b/src/config/general.rs index 771fe6640..d7e6507cb 100644 --- a/src/config/general.rs +++ b/src/config/general.rs @@ -269,6 +269,17 @@ pub struct General { #[serde(default, skip_serializing)] pub pg_hba: Option, + /// Consecutive backend startup rejections for the same parameter before + /// pg_doorman quarantines it for this pool. See + /// `startup_parameter_quarantine_ttl`. Default `3`. + #[serde(default = "General::default_startup_parameter_quarantine_threshold")] + pub startup_parameter_quarantine_threshold: u32, + + /// TTL (milliseconds) for a quarantined parameter; on expiry pg_doorman + /// retries including it in the StartupMessage. Default `300000` (5 min). + #[serde(default = "General::default_startup_parameter_quarantine_ttl")] + pub startup_parameter_quarantine_ttl: u64, + /// Operator-supplied PostgreSQL configuration parameters. Reserved /// keys (`user`, `database`, `replication`, `options`, `_pq_.*`) and /// the StartupMessage budget are validated at config load. Wire @@ -467,6 +478,14 @@ impl General { "/tmp/pg_doorman.pid".to_string() } + pub fn default_startup_parameter_quarantine_threshold() -> u32 { + 3 + } + + pub fn default_startup_parameter_quarantine_ttl() -> u64 { + 300_000 + } + /// Test-only builder that produces a `General` with the two /// prepared-cache knobs explicitly set and everything else at /// defaults. Lets tests outside this module exercise resolution @@ -594,6 +613,9 @@ impl Default for General { Self::default_query_interner_anon_idle_ttl_seconds(), hba: Self::default_hba(), pg_hba: None, + startup_parameter_quarantine_threshold: + Self::default_startup_parameter_quarantine_threshold(), + startup_parameter_quarantine_ttl: Self::default_startup_parameter_quarantine_ttl(), startup_parameters: std::collections::BTreeMap::new(), daemon_pid_file: Self::default_daemon_pid_file(), syslog_prog_name: None, @@ -774,4 +796,11 @@ client_prepared_statements_cache_size = 2048 Some(2048), ); } + + #[test] + fn startup_parameter_quarantine_defaults_are_sane() { + let g = General::default(); + assert_eq!(g.startup_parameter_quarantine_threshold, 3); + assert_eq!(g.startup_parameter_quarantine_ttl, 300_000); + } } diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 83b03d225..871a24be5 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -35,6 +35,7 @@ pub mod gc; pub mod pool_coordinator; pub mod retain; mod server_pool; +pub mod startup_resolver; pub mod fallback; diff --git a/src/pool/startup_resolver.rs b/src/pool/startup_resolver.rs new file mode 100644 index 000000000..696c5cd92 --- /dev/null +++ b/src/pool/startup_resolver.rs @@ -0,0 +1,94 @@ +//! Pure cascade resolver for operator-supplied PostgreSQL startup parameters. +//! +//! Three levels merge by union; per-key, the more specific level wins +//! (auth_query > pool > general). The result is what pg_doorman sends in +//! `StartupMessage` for one backend connection. + +use std::collections::{BTreeMap, HashMap}; + +/// Merge cascade and return the map pg_doorman will put on the wire. +/// +/// `auth_query_params` is `None` for connections that don't go through +/// `auth_query` (static user) or for dedicated-mode auth_query pools where +/// per-user parameters are intentionally ignored (D7). +pub fn resolve( + general: &BTreeMap, + pool: &BTreeMap, + auth_query_params: Option<&HashMap>, +) -> BTreeMap { + let mut merged: BTreeMap = BTreeMap::new(); + merged.extend(general.iter().map(|(k, v)| (k.clone(), v.clone()))); + merged.extend(pool.iter().map(|(k, v)| (k.clone(), v.clone()))); + if let Some(extra) = auth_query_params { + merged.extend(extra.iter().map(|(k, v)| (k.clone(), v.clone()))); + } + merged +} + +#[cfg(test)] +mod tests { + use super::*; + + fn b(pairs: &[(&str, &str)]) -> BTreeMap { + pairs + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect() + } + fn h(pairs: &[(&str, &str)]) -> HashMap { + pairs + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect() + } + + #[test] + fn empty_cascade_yields_empty() { + let r = resolve(&BTreeMap::new(), &BTreeMap::new(), None); + assert!(r.is_empty()); + } + + #[test] + fn general_baseline_passes_through() { + let g = b(&[("statement_timeout", "10s")]); + let r = resolve(&g, &BTreeMap::new(), None); + assert_eq!(r.get("statement_timeout").map(String::as_str), Some("10s")); + } + + #[test] + fn pool_overrides_general_per_key() { + let g = b(&[("plan_cache_mode", "auto"), ("statement_timeout", "10s")]); + let p = b(&[("plan_cache_mode", "force_custom_plan")]); + let r = resolve(&g, &p, None); + assert_eq!(r.get("plan_cache_mode").unwrap(), "force_custom_plan"); + assert_eq!(r.get("statement_timeout").unwrap(), "10s"); + } + + #[test] + fn auth_query_overrides_pool() { + let p = b(&[("work_mem", "64MB")]); + let a = h(&[("work_mem", "256MB"), ("lock_timeout", "5s")]); + let r = resolve(&BTreeMap::new(), &p, Some(&a)); + assert_eq!(r.get("work_mem").unwrap(), "256MB"); + assert_eq!(r.get("lock_timeout").unwrap(), "5s"); + } + + #[test] + fn dedicated_mode_signaled_by_none_auth_query() { + let p = b(&[("work_mem", "64MB")]); + let r = resolve(&BTreeMap::new(), &p, None); + assert_eq!(r.get("work_mem").unwrap(), "64MB"); + assert!(!r.contains_key("lock_timeout")); + } + + #[test] + fn application_name_can_cascade_too() { + // operator-wins on application_name (D5/B2): pool can override general's + // baseline; auth_query in turn overrides pool. + let g = b(&[("application_name", "tier-default")]); + let p = b(&[("application_name", "checkout-pool")]); + let a = h(&[("application_name", "vip-user-app")]); + let r = resolve(&g, &p, Some(&a)); + assert_eq!(r.get("application_name").unwrap(), "vip-user-app"); + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 16e937948..e77fa48fc 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod cleanup; pub(crate) mod parameters; pub(crate) mod prepared_statements; pub(crate) mod protocol_io; +pub mod quarantine; pub(crate) mod startup_cancel; pub(crate) mod startup_error; pub(crate) mod stream; diff --git a/src/server/quarantine.rs b/src/server/quarantine.rs new file mode 100644 index 000000000..164d88e86 --- /dev/null +++ b/src/server/quarantine.rs @@ -0,0 +1,216 @@ +//! Per-pool quarantine for operator-supplied startup parameters that PG keeps +//! rejecting at backend startup. +//! +//! Lifecycle: +//! 1. Backend `Server::startup` issues StartupMessage with N parameters. +//! 2. PG ErrorResponse arrives; parsed parameter name is reported via +//! [`QuarantineState::record_rejection`]. +//! 3. After N consecutive rejections of the same parameter +//! (`startup_parameter_quarantine_threshold`), the key is quarantined for +//! TTL ms. +//! 4. [`QuarantineState::filter_active_keys`] is called on every subsequent +//! backend spawn and skips quarantined keys. +//! 5. TTL only releases keys (never a "success": we skipped them, so success +//! is meaningless evidence). See D8. + +use std::collections::HashMap; +use std::sync::Mutex; +use std::time::{Duration, Instant}; + +#[derive(Debug, Clone)] +pub struct QuarantineEntry { + pub reject_count: u32, + pub quarantined_until: Option, + pub last_sqlstate: String, +} + +#[derive(Debug)] +pub struct QuarantineState { + threshold: u32, + ttl: Duration, + entries: Mutex>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum RecordOutcome { + /// Counter advanced but threshold not yet reached. + Counting { reject_count: u32 }, + /// This call moved the parameter into quarantine. + JustQuarantined, + /// Already quarantined; this is a refresher with a fresh deadline. + AlreadyQuarantined, +} + +impl QuarantineState { + pub fn new(threshold: u32, ttl: Duration) -> Self { + Self { + threshold: threshold.max(1), + ttl, + entries: Mutex::new(HashMap::new()), + } + } + + /// Record a backend-startup rejection for `key`. Returns the resulting + /// outcome for caller-side logging and metrics. + pub fn record_rejection(&self, key: &str, sqlstate: &str) -> RecordOutcome { + let now = Instant::now(); + let mut entries = self.entries.lock().expect("quarantine mutex"); + let entry = entries + .entry(key.to_owned()) + .or_insert_with(|| QuarantineEntry { + reject_count: 0, + quarantined_until: None, + last_sqlstate: sqlstate.to_owned(), + }); + entry.last_sqlstate = sqlstate.to_owned(); + if entry.quarantined_until.map(|d| d > now).unwrap_or(false) { + entry.quarantined_until = Some(now + self.ttl); + return RecordOutcome::AlreadyQuarantined; + } + entry.reject_count = entry.reject_count.saturating_add(1); + if entry.reject_count >= self.threshold { + entry.quarantined_until = Some(now + self.ttl); + return RecordOutcome::JustQuarantined; + } + RecordOutcome::Counting { + reject_count: entry.reject_count, + } + } + + /// Strip currently-quarantined keys from the operator-supplied map and + /// drop the bookkeeping for entries whose TTL has expired (so the next + /// rejection starts counting afresh). + /// + /// Returns the list of keys whose quarantine just expired in this call; + /// callers should use it to clear corresponding Prometheus gauges. + pub fn filter_active_keys( + &self, + params: &mut std::collections::BTreeMap, + ) -> Vec { + let now = Instant::now(); + let mut entries = self.entries.lock().expect("quarantine mutex"); + let mut released: Vec = Vec::new(); + let mut to_drop: Vec = Vec::new(); + for (key, entry) in entries.iter_mut() { + match entry.quarantined_until { + Some(deadline) if deadline > now => { + params.remove(key); + } + Some(_) => { + entry.quarantined_until = None; + entry.reject_count = 0; + to_drop.push(key.clone()); + released.push(key.clone()); + } + None => {} + } + } + for k in &to_drop { + entries.remove(k); + } + released + } + + /// Return currently-quarantined parameter names (for SHOW POOLS / metrics). + pub fn snapshot_quarantined(&self) -> Vec { + let now = Instant::now(); + let entries = self.entries.lock().expect("quarantine mutex"); + entries + .iter() + .filter_map(|(k, e)| match e.quarantined_until { + Some(d) if d > now => Some(k.clone()), + _ => None, + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeMap; + + #[test] + fn first_rejections_count_up() { + let q = QuarantineState::new(3, Duration::from_secs(60)); + assert_eq!( + q.record_rejection("foo", "42704"), + RecordOutcome::Counting { reject_count: 1 } + ); + assert_eq!( + q.record_rejection("foo", "42704"), + RecordOutcome::Counting { reject_count: 2 } + ); + assert_eq!( + q.record_rejection("foo", "42704"), + RecordOutcome::JustQuarantined + ); + } + + #[test] + fn extra_rejection_after_quarantine_is_refresher() { + let q = QuarantineState::new(2, Duration::from_secs(60)); + let _ = q.record_rejection("foo", "42704"); + let _ = q.record_rejection("foo", "42704"); + assert_eq!( + q.record_rejection("foo", "42704"), + RecordOutcome::AlreadyQuarantined + ); + } + + #[test] + fn filter_strips_active_quarantines() { + let q = QuarantineState::new(1, Duration::from_secs(60)); + let _ = q.record_rejection("bad", "42704"); + let mut params: BTreeMap = [ + ("bad".to_string(), "x".to_string()), + ("ok".to_string(), "y".to_string()), + ] + .into_iter() + .collect(); + let released = q.filter_active_keys(&mut params); + assert!(!params.contains_key("bad")); + assert!(params.contains_key("ok")); + assert!(released.is_empty(), "active quarantine should not release"); + } + + #[test] + fn expired_quarantine_is_released_and_counter_resets() { + let q = QuarantineState::new(1, Duration::from_millis(10)); + let _ = q.record_rejection("bad", "42704"); + std::thread::sleep(Duration::from_millis(25)); + let mut params: BTreeMap = + [("bad".to_string(), "x".to_string())].into_iter().collect(); + let released = q.filter_active_keys(&mut params); + assert!( + params.contains_key("bad"), + "expired quarantine should not strip the key" + ); + assert_eq!(released, vec!["bad".to_string()]); + // counter reset; next rejection re-starts counting and (with threshold=1) + // immediately quarantines. + assert_eq!( + q.record_rejection("bad", "42704"), + RecordOutcome::JustQuarantined + ); + } + + #[test] + fn snapshot_lists_only_active() { + let q = QuarantineState::new(1, Duration::from_secs(60)); + let _ = q.record_rejection("a", "42704"); + let _ = q.record_rejection("b", "22023"); + let mut s = q.snapshot_quarantined(); + s.sort(); + assert_eq!(s, vec!["a".to_string(), "b".to_string()]); + } + + #[test] + fn threshold_zero_is_clamped_to_one() { + let q = QuarantineState::new(0, Duration::from_secs(60)); + assert_eq!( + q.record_rejection("foo", "42704"), + RecordOutcome::JustQuarantined + ); + } +} From bc2820481cd57ebf32acf895aecb5008408b7385 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 22:07:16 +0300 Subject: [PATCH 06/72] comments: drop spec-decision shorthand from startup_parameters code Earlier commits left shorthand references like D5/B2, D7, D8 in code comments and rustdoc. Those identifiers point to a brainstorm document that is intentionally gitignored, so any reader outside the original session has no way to resolve them. Comments now describe the rule in prose: operator-supplied value wins over the pg_doorman-managed default for application_name; auth_query parameters are dropped in dedicated mode because one shared backend serves multiple users; quarantine releases by TTL only because a key that has been skipped offers no evidence of being fixed. No behavior change; comment-only. --- src/config/startup_parameters.rs | 5 +++-- src/messages/protocol.rs | 5 +++-- src/pool/startup_resolver.rs | 9 +++++---- src/server/quarantine.rs | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/config/startup_parameters.rs b/src/config/startup_parameters.rs index b5ea67137..30b27e15b 100644 --- a/src/config/startup_parameters.rs +++ b/src/config/startup_parameters.rs @@ -177,8 +177,9 @@ mod tests { #[test] fn application_name_is_not_reserved() { - // Operator-wins (B2): explicitly allowed; pg_doorman default merges - // happen in the wire layer, not validation. + // application_name is explicitly allowed in startup_parameters; the + // operator-wins merge against pg_doorman's default happens at the + // wire layer, not here. let map = m(&[("application_name", "my_app")]); assert!(validate(&map, "scope").is_ok()); } diff --git a/src/messages/protocol.rs b/src/messages/protocol.rs index 639ef92a1..5a2b103d6 100644 --- a/src/messages/protocol.rs +++ b/src/messages/protocol.rs @@ -166,7 +166,8 @@ pub fn simple_query(query: &str) -> BytesMut { /// `extra_params` is empty. Operator-supplied values from `extra_params` are /// appended in the map's iteration order (BTreeMap → lexicographic). If /// `extra_params` contains an `application_name` key, it overrides the -/// `application_name` argument per the design (D5/B2: operator-wins). +/// `application_name` argument so an operator-supplied value wins over the +/// pg_doorman-managed default. pub async fn startup( stream: &mut S, user: String, @@ -188,7 +189,7 @@ where bytes.put_u8(0); // Application name. Operator-supplied value in `extra_params` wins over - // the pg_doorman-managed default per design D5/B2. + // the pg_doorman-managed default. let effective_app_name = extra_params .get("application_name") .map(String::as_str) diff --git a/src/pool/startup_resolver.rs b/src/pool/startup_resolver.rs index 696c5cd92..3b5865228 100644 --- a/src/pool/startup_resolver.rs +++ b/src/pool/startup_resolver.rs @@ -9,8 +9,9 @@ use std::collections::{BTreeMap, HashMap}; /// Merge cascade and return the map pg_doorman will put on the wire. /// /// `auth_query_params` is `None` for connections that don't go through -/// `auth_query` (static user) or for dedicated-mode auth_query pools where -/// per-user parameters are intentionally ignored (D7). +/// `auth_query` (static user), and also for dedicated-mode auth_query pools +/// where one shared backend serves multiple dynamic users so per-user +/// parameters cannot be honoured. pub fn resolve( general: &BTreeMap, pool: &BTreeMap, @@ -83,8 +84,8 @@ mod tests { #[test] fn application_name_can_cascade_too() { - // operator-wins on application_name (D5/B2): pool can override general's - // baseline; auth_query in turn overrides pool. + // operator-wins on application_name extends through the cascade: + // pool overrides general's baseline; auth_query overrides pool. let g = b(&[("application_name", "tier-default")]); let p = b(&[("application_name", "checkout-pool")]); let a = h(&[("application_name", "vip-user-app")]); diff --git a/src/server/quarantine.rs b/src/server/quarantine.rs index 164d88e86..8fd0d69d8 100644 --- a/src/server/quarantine.rs +++ b/src/server/quarantine.rs @@ -11,7 +11,7 @@ //! 4. [`QuarantineState::filter_active_keys`] is called on every subsequent //! backend spawn and skips quarantined keys. //! 5. TTL only releases keys (never a "success": we skipped them, so success -//! is meaningless evidence). See D8. +//! is meaningless evidence). use std::collections::HashMap; use std::sync::Mutex; From 969e2b69fefeaf1a590ffaf33ef60b1caf1f8eca Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 22:11:44 +0300 Subject: [PATCH 07/72] feat(server): Server::startup wires startup_parameters and quarantine * messages::protocol::startup() (already taking the operator map since the previous commit on this branch) is now called with the cascade- resolved map. Before sending, currently-quarantined keys are stripped via QuarantineState::filter_active_keys. * On PG ErrorResponse with sqlstate 22023 / 42704 / 42501 and a non- empty operator map, pg_doorman extracts the failing parameter name via the literal pattern `parameter "..."` (hand-rolled to keep regex in dev-deps) and records the rejection in the pool-scoped QuarantineState. After N consecutive rejections of the same key, subsequent backend spawns drop that key from the StartupMessage for a configurable TTL. * extract_parameter_name() pure helper added to startup_error.rs with unit tests covering unknown / invalid-value / insufficient-privilege / namespaced and no-match cases. Call sites currently pass an empty map and a per-call QuarantineState constructed from the general-level knobs; the pool-owned shared Arc ships in the next commit. The now-unused handle_startup_error stays in the tree behind #[allow(dead_code)] as a reference for the pre- quarantine behavior; cleanup is left to a follow-up. --- src/pool/server_pool.rs | 25 ++++++++++++ src/server/server_backend.rs | 67 ++++++++++++++++++++++++++++--- src/server/startup_error.rs | 76 ++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 6 deletions(-) diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 55772b798..0104adc10 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -217,6 +217,15 @@ impl ServerPool { stats.register(stats.clone()); + // Phase 4 placeholder: per-call construction defeats sharing across + // backend spawns. Phase 6 will lift this onto the pool-owned Arc so + // rejection counters are actually shared. + let cfg = crate::config::get_config(); + let quarantine = Arc::new(crate::server::quarantine::QuarantineState::new( + cfg.general.startup_parameter_quarantine_threshold, + Duration::from_millis(cfg.general.startup_parameter_quarantine_ttl), + )); + let result = startup_with_timeout( self.connect_timeout, &self.address.host, @@ -232,6 +241,8 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, + std::collections::BTreeMap::new(), + quarantine.clone(), ), ) .await; @@ -287,6 +298,8 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, + std::collections::BTreeMap::new(), + quarantine.clone(), ), ) .await; @@ -650,6 +663,14 @@ impl ServerPool { )); stats.register(stats.clone()); + // Phase 4 placeholder: see the matching note in `create`. Phase 6 + // replaces this with the pool-owned shared quarantine Arc. + let cfg = crate::config::get_config(); + let quarantine = Arc::new(crate::server::quarantine::QuarantineState::new( + cfg.general.startup_parameter_quarantine_threshold, + Duration::from_millis(cfg.general.startup_parameter_quarantine_ttl), + )); + let result = startup_with_timeout( fallback_timeout, &fallback_address.host, @@ -665,6 +686,8 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, + std::collections::BTreeMap::new(), + quarantine.clone(), ), ) .await; @@ -713,6 +736,8 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, + std::collections::BTreeMap::new(), + quarantine.clone(), ), ) .await; diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index 888992d31..5ffe98509 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -27,7 +27,6 @@ use crate::stats::ServerStats; use super::authentication::handle_authentication; use super::cleanup::CleanupState; use super::parameters::ServerParameters; -use super::startup_error::handle_startup_error; use super::stream::{create_tcp_stream_inner, create_unix_stream_inner, StreamInner}; use super::{prepared_statements, protocol_io, startup_cancel}; @@ -741,6 +740,14 @@ impl Server { /// Pretend to be the Postgres client and connect to the server given host, port and credentials. /// Perform the authentication and return the server in a ready for query state. + /// + /// `startup_parameters` is the cascade-resolved operator-supplied map + /// (general → pool → auth_query, more specific wins). Keys currently held + /// in `quarantine` are stripped before the StartupMessage is sent; any + /// PG ErrorResponse with a startup-parameter SQLSTATE + /// (`22023` / `42704` / `42501`) is fed back into the quarantine so a + /// persistently-failing key is dropped from subsequent backend spawns + /// for the configured TTL. #[allow(clippy::too_many_arguments)] pub async fn startup( address: &Address, @@ -753,6 +760,8 @@ impl Server { server_prepared_statement_cache_size: usize, application_name: String, session_mode: bool, + startup_parameters: std::collections::BTreeMap, + quarantine: std::sync::Arc, ) -> Result { let config = get_config(); @@ -797,12 +806,19 @@ impl Server { // code 0); see the `'R'` branch below. let auth_started = Instant::now(); let mut startup_started: Option = None; + + // Strip currently-quarantined keys from the operator-supplied set + // right before we serialize the StartupMessage. The released set is + // currently unused; Phase 7 will wire Prometheus gauges from it. + let mut startup_parameters_sent = startup_parameters; + let _released_keys = quarantine.filter_active_keys(&mut startup_parameters_sent); + startup( &mut stream, username.clone(), database, application_name.clone(), - &std::collections::BTreeMap::new(), + &startup_parameters_sent, ) .await?; @@ -903,11 +919,50 @@ impl Server { } } - // ErrorResponse + // ErrorResponse. Read the message body, parse it, and - + // when the SQLSTATE belongs to the startup-parameter family + // and the operator-supplied map was non-empty - feed the + // failing parameter name into the per-pool quarantine. We + // intentionally bypass `handle_startup_error` here because + // that helper consumes the body internally and we need to + // inspect the M-field for the parameter name; the existing + // error pathway (`Error::ServerStartupError`) is preserved + // so callers see the same outward behavior. 'E' => { - return handle_startup_error(&mut stream, len, &server_identifier) - .await - .map(|_| unreachable!()); + let mut bytes = read_message_data(&mut stream, code as u8, len).await?; + let _ = bytes.get_u8(); + let _ = bytes.get_i32(); + let parsed = PgErrorMsg::parse(&bytes).ok(); + + if let Some(ref msg) = parsed { + let is_startup_parameter_sqlstate = + matches!(msg.code.as_str(), "22023" | "42704" | "42501"); + if is_startup_parameter_sqlstate && !startup_parameters_sent.is_empty() { + if let Some(param_name) = + crate::server::startup_error::extract_parameter_name(&msg.message) + { + let outcome = quarantine.record_rejection(¶m_name, &msg.code); + warn!( + "[{}@{}] backend startup rejected: parameter=\"{}\" \ + sqlstate={} message=\"{}\" outcome={:?}", + address.username, + address.pool_name, + param_name, + msg.code, + msg.message, + outcome, + ); + // Phase 7 will wire real Prometheus vectors here. + } + } + } + + return Err(Error::ServerStartupError( + parsed + .map(|m| format!("{}: {}", m.code, m.message)) + .unwrap_or_else(|| "startup ErrorResponse".to_string()), + server_identifier.clone(), + )); } // Notice diff --git a/src/server/startup_error.rs b/src/server/startup_error.rs index eaaab0513..09e4bf44f 100644 --- a/src/server/startup_error.rs +++ b/src/server/startup_error.rs @@ -9,7 +9,37 @@ use crate::messages::PgErrorMsg; use super::stream::StreamInner; +/// Extract the GUC name from the human-readable M-field of a PG +/// ErrorResponse. Matches messages of the form: +/// `unrecognized configuration parameter "foobar"` +/// `invalid value for parameter "work_mem": "abc"` +/// `permission denied to set parameter "session_preload_libraries"` +/// +/// Returns `None` if the message does not contain the literal substring +/// `parameter "..."` (e.g. it is unrelated to a configuration parameter). +/// +/// Hand-rolled rather than using `regex` to keep that crate out of the +/// runtime dependency set; the pattern is fixed (`parameter "([^"]+)"`) +/// and trivial to scan for. The same constraint shaped +/// `crate::config::startup_parameters::is_valid_guc_name`. +pub fn extract_parameter_name(message: &str) -> Option { + const NEEDLE: &str = r#"parameter ""#; + let start = message.find(NEEDLE)? + NEEDLE.len(); + let rest = &message[start..]; + let end = rest.find('"')?; + if end == 0 { + return None; + } + Some(rest[..end].to_owned()) +} + /// Handles error response during server startup. +/// +/// Currently unused: `Server::startup` inlines the equivalent logic so it +/// can also inspect the M-field for the failing parameter name and feed +/// it into the quarantine. Kept in tree as a reference helper for any +/// future startup path that needs the pre-quarantine behavior verbatim. +#[allow(dead_code)] pub(crate) async fn handle_startup_error( stream: &mut StreamInner, len: i32, @@ -73,3 +103,49 @@ pub(crate) async fn handle_startup_error( } } } + +#[cfg(test)] +mod parameter_extractor_tests { + use super::*; + + #[test] + fn unknown_parameter_extracted() { + assert_eq!( + extract_parameter_name(r#"unrecognized configuration parameter "foobar""#), + Some("foobar".into()) + ); + } + + #[test] + fn invalid_value_extracted() { + assert_eq!( + extract_parameter_name(r#"invalid value for parameter "work_mem": "abc""#), + Some("work_mem".into()) + ); + } + + #[test] + fn permission_denied_extracted() { + assert_eq!( + extract_parameter_name( + r#"permission denied to set parameter "session_preload_libraries""# + ), + Some("session_preload_libraries".into()) + ); + } + + #[test] + fn no_quoted_parameter_returns_none() { + assert_eq!(extract_parameter_name("connection refused by peer"), None); + } + + #[test] + fn namespaced_parameter_extracted() { + assert_eq!( + extract_parameter_name( + r#"unrecognized configuration parameter "auto_explain.log_min_duration""# + ), + Some("auto_explain.log_min_duration".into()) + ); + } +} From ed3f8136f62820cafe6c5c90b47b3175f3120758 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 22:27:09 +0300 Subject: [PATCH 08/72] feat(auth_query): per-user startup_parameters via optional JSON column The auth_query SQL contract gains an optional text column 'startup_parameters' holding a JSON object: its string-typed entries become the per-user GUC pg_doorman injects when it opens a backend on the user's behalf. SQL that does not return the column continues to work unchanged. Operator config validation rules (reserved keys, GUC name syntax, no null bytes) are reused: invalid entries are dropped with a warning, unparseable JSON yields an empty set with a warning, but auth never fails because of an operator config error in the query result. In dedicated mode (auth_query.server_user set, one shared backend pool under that identity) per-user values are semantically impossible: pg_doorman drops them with a single warning per pool and username and lets pool-level or general startup_parameters take over. The parsed map is stored on the credential cache entry. The next commit on this branch wires it into the cascade resolver. --- src/auth/auth_query.rs | 410 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 394 insertions(+), 16 deletions(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index 7a3778b0d..907d0026a 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -33,13 +33,37 @@ const MAX_USERNAME_LEN: usize = 63; // PasswordFetcher trait (allows mocking AuthQueryExecutor in unit tests) // --------------------------------------------------------------------------- -/// Trait for fetching password hashes from PostgreSQL. +/// Password hash plus the per-user startup_parameters map surfaced by +/// auth_query. The map is empty when the SQL did not return the optional +/// `startup_parameters` column, when the column was NULL/empty, or when +/// every entry failed validation. +pub type Credentials = (String, std::collections::HashMap); + +/// Trait for fetching credentials from PostgreSQL. /// `AuthQueryExecutor` implements this; tests and benchmarks can substitute a mock. +/// +/// `fetch` returns just the password hash; `fetch_credentials` additionally +/// returns the per-user startup_parameters map parsed from the optional +/// auth_query result column. The default implementation pairs the password +/// with an empty map, which is the correct semantics for any fetcher that +/// does not surface per-user parameters. pub trait PasswordFetcher: Send + Sync { fn fetch<'a>( &'a self, username: &'a str, ) -> impl Future, Error>> + Send + 'a; + + fn fetch_credentials<'a>( + &'a self, + username: &'a str, + ) -> impl Future, Error>> + Send + 'a { + async move { + Ok(self + .fetch(username) + .await? + .map(|p| (p, std::collections::HashMap::new()))) + } + } } impl PasswordFetcher for AuthQueryExecutor { @@ -49,6 +73,13 @@ impl PasswordFetcher for AuthQueryExecutor { ) -> impl Future, Error>> + Send + 'a { self.fetch_password(username) } + + fn fetch_credentials<'a>( + &'a self, + username: &'a str, + ) -> impl Future, Error>> + Send + 'a { + AuthQueryExecutor::fetch_credentials(self, username) + } } // --------------------------------------------------------------------------- @@ -183,11 +214,12 @@ impl AuthQueryExecutor { Ok(client) } - /// Fetch password hash for a username from PostgreSQL. - /// Returns `Some(password_hash)` or `None` if user not found. - pub async fn fetch_password(&self, username: &str) -> Result, Error> { + /// Fetch credentials (password hash plus the optional per-user + /// startup_parameters map) for a username from PostgreSQL. + /// Returns `Some((password_hash, params))` or `None` if user not found. + pub async fn fetch_credentials(&self, username: &str) -> Result, Error> { debug!( - "[{username}@{}] auth_query: fetching password", + "[{username}@{}] auth_query: fetching credentials", self.pool_name ); @@ -195,7 +227,7 @@ impl AuthQueryExecutor { let mut rx = self.rx.lock().await; rx.recv().await.ok_or_else(|| { error!( - "[{username}@{}] auth_query: executor pool closed, cannot fetch password", + "[{username}@{}] auth_query: executor pool closed, cannot fetch credentials", self.pool_name ); Error::AuthQueryPoolClosed @@ -242,6 +274,12 @@ impl AuthQueryExecutor { result } + /// Backwards-compatible password-only accessor. Discards any per-user + /// startup_parameters returned alongside the password. + pub async fn fetch_password(&self, username: &str) -> Result, Error> { + Ok(self.fetch_credentials(username).await?.map(|(p, _)| p)) + } + async fn try_reconnect(&self) { let database = self .config @@ -282,7 +320,7 @@ impl AuthQueryExecutor { &self, client: &Client, username: &str, - ) -> Result, Error> { + ) -> Result, Error> { let rows = client .query( &self.config.query, @@ -297,7 +335,15 @@ impl AuthQueryExecutor { match rows.len() { 0 => Ok(None), - 1 => Self::extract_password(&rows[0], username, &self.pool_name), + 1 => { + let row = &rows[0]; + let pw_opt = Self::extract_password(row, username, &self.pool_name)?; + let Some(pw) = pw_opt else { + return Ok(None); + }; + let params = Self::extract_startup_parameters(row, username, &self.pool_name); + Ok(Some((pw, params))) + } n => Err(Error::AuthQueryConfigError(format!( "query returned {n} rows for user '{username}', expected 0 or 1" ))), @@ -342,6 +388,103 @@ impl AuthQueryExecutor { } } } + + /// Read the optional `startup_parameters` text column from the auth_query + /// row and parse it as a JSON object. A missing column yields an empty + /// map; a present column whose type does not coerce to `Option` + /// logs a warning and yields an empty map. Actual JSON parsing and + /// per-entry validation happen in `parse_startup_parameters_text`. + fn extract_startup_parameters( + row: &tokio_postgres::Row, + username: &str, + pool_name: &str, + ) -> std::collections::HashMap { + let has_column = row + .columns() + .iter() + .any(|c| c.name() == "startup_parameters"); + if !has_column { + return std::collections::HashMap::new(); + } + let raw: Option = match row.try_get::<_, Option>("startup_parameters") { + Ok(v) => v, + Err(e) => { + warn!( + "[{username}@{pool_name}] auth_query startup_parameters: column present but \ + not readable as text: {e}; parameters ignored" + ); + return std::collections::HashMap::new(); + } + }; + Self::parse_startup_parameters_text(raw.as_deref(), username, pool_name) + } + + /// Parse the optional `startup_parameters` JSON-object value shipped by + /// the auth_query SQL. Each string-typed entry becomes a per-user GUC; + /// reserved keys, syntactically invalid GUC names, and non-string values + /// are dropped with a warning. Unparseable JSON and JSON that is not an + /// object both yield an empty map with a warning. Never fails: this is + /// operator-supplied configuration data, and a malformed entry must not + /// block authentication. + fn parse_startup_parameters_text( + text: Option<&str>, + username: &str, + pool_name: &str, + ) -> std::collections::HashMap { + let Some(text) = text else { + return std::collections::HashMap::new(); + }; + if text.is_empty() { + return std::collections::HashMap::new(); + } + let parsed: serde_json::Value = match serde_json::from_str(text) { + Ok(v) => v, + Err(e) => { + warn!( + "[{username}@{pool_name}] auth_query startup_parameters: JSON parse failed: \ + {e}; parameters ignored" + ); + return std::collections::HashMap::new(); + } + }; + let serde_json::Value::Object(obj) = parsed else { + warn!( + "[{username}@{pool_name}] auth_query startup_parameters: top-level value is not a \ + JSON object; ignored" + ); + return std::collections::HashMap::new(); + }; + let mut out = std::collections::HashMap::new(); + let scope = format!("auth_query.startup_parameters[user={username}]"); + for (k, v) in obj { + match v { + serde_json::Value::String(s) => { + let mut probe = std::collections::BTreeMap::new(); + probe.insert(k.clone(), s.clone()); + if let Err(e) = crate::config::startup_parameters::validate(&probe, &scope) { + warn!("[{pool_name}] {e}"); + continue; + } + out.insert(k, s); + } + other => { + let kind = match other { + serde_json::Value::Null => "null", + serde_json::Value::Bool(_) => "boolean", + serde_json::Value::Number(_) => "number", + serde_json::Value::Array(_) => "array", + serde_json::Value::Object(_) => "object", + serde_json::Value::String(_) => unreachable!(), + }; + warn!( + "[{username}@{pool_name}] auth_query startup_parameters: value for '{k}' \ + is {kind}, not string; ignored" + ); + } + } + } + out + } } // --------------------------------------------------------------------------- @@ -364,6 +507,10 @@ pub struct CacheEntry { /// for SCRAM passthrough to backend PG (Step 6). /// None for MD5 users or before first SCRAM auth. pub client_key: Option>, + /// Per-user startup parameters surfaced by the auth_query optional + /// `startup_parameters` JSON column. Empty when the column is absent, + /// empty/NULL, or filtered out in dedicated auth_query mode. + pub startup_parameters: std::collections::HashMap, } impl CacheEntry { @@ -374,6 +521,7 @@ impl CacheEntry { is_negative: false, last_refetch_at: None, client_key: None, + startup_parameters: std::collections::HashMap::new(), } } @@ -384,6 +532,7 @@ impl CacheEntry { is_negative: true, last_refetch_at: None, client_key: None, + startup_parameters: std::collections::HashMap::new(), } } @@ -420,7 +569,7 @@ pub struct AuthQueryCache { /// Per-username locks for request coalescing. /// First request acquires lock + fetches; others wait + get cache hit. locks: DashMap>>, - /// Fetcher for cache miss → PG fetch. + /// Fetcher for cache miss to PG. executor: Arc, /// TTL for positive cache entries (user found). cache_ttl: Duration, @@ -430,6 +579,14 @@ pub struct AuthQueryCache { min_interval: Duration, /// Optional stats for observability (None in unit tests). stats: Option>, + /// True when auth_query runs in dedicated mode (server_user is set). + /// In that mode every backend connection shares a single backend + /// identity, so per-user startup_parameters cannot be honored. + is_dedicated: bool, + /// Usernames already warned about dropped per-user startup_parameters + /// in dedicated mode. Ensures the warning fires at most once per + /// (pool, user) until the cache is cleared by a config reload. + dedicated_warnings: DashMap, } impl AuthQueryCache { @@ -448,9 +605,34 @@ impl AuthQueryCache { cache_failure_ttl: config.cache_failure_ttl, min_interval: config.min_interval, stats, + is_dedicated: config.is_dedicated_mode(), + dedicated_warnings: DashMap::new(), } } + /// In dedicated auth_query mode (`server_user` set) every backend + /// connection shares a single identity, so per-user startup_parameters + /// cannot be honored: pg_doorman has no per-user backend on which to + /// apply them. Drop the parsed map before it reaches downstream code + /// and warn once per (pool, username) so the operator notices. + fn dedicated_mode_filter(&self, entry: &mut CacheEntry, username: &str) { + if !self.is_dedicated || entry.startup_parameters.is_empty() { + return; + } + if self + .dedicated_warnings + .insert(username.to_string(), ()) + .is_none() + { + warn!( + "[{username}@{pool}] per-user startup_parameters ignored in dedicated \ + auth_query mode; use pool-level startup_parameters instead", + pool = self.pool_name + ); + } + entry.startup_parameters.clear(); + } + /// Increment a stats counter if stats are enabled. fn inc(&self, counter: fn(&AuthQueryStats) -> &AtomicU64) { if let Some(ref stats) = self.stats { @@ -507,12 +689,14 @@ impl AuthQueryCache { } } - // Cache miss — fetch from PG + // Cache miss: fetch credentials from PG. self.inc(|s| &s.executor_queries); - match self.executor.fetch(username).await { - Ok(Some(password_hash)) => { + match self.executor.fetch_credentials(username).await { + Ok(Some((password_hash, startup_params))) => { self.inc(|s| &s.cache_misses); - let entry = CacheEntry::positive(password_hash); + let mut entry = CacheEntry::positive(password_hash); + entry.startup_parameters = startup_params; + self.dedicated_mode_filter(&mut entry, username); self.entries.insert(username.to_string(), entry.clone()); Ok(Some(entry)) } @@ -572,13 +756,15 @@ impl AuthQueryCache { } } - // Fetch fresh from PG + // Fetch fresh from PG. self.inc(|s| &s.executor_queries); self.inc(|s| &s.cache_refetches); - match self.executor.fetch(username).await { - Ok(Some(password_hash)) => { + match self.executor.fetch_credentials(username).await { + Ok(Some((password_hash, startup_params))) => { let mut entry = CacheEntry::positive(password_hash); + entry.startup_parameters = startup_params; entry.last_refetch_at = Some(Instant::now()); + self.dedicated_mode_filter(&mut entry, username); self.entries.insert(username.to_string(), entry.clone()); Ok(Some(entry)) } @@ -596,9 +782,12 @@ impl AuthQueryCache { } /// Clear all entries (called on RELOAD when auth_query config changes). + /// Also resets dedicated-mode warning suppression so an operator who + /// fixes their config and reloads gets fresh warnings next time. pub fn clear(&self) { self.entries.clear(); self.locks.clear(); + self.dedicated_warnings.clear(); } /// Store ClientKey for a cached user (called after successful SCRAM auth). @@ -639,6 +828,10 @@ mod tests { /// Pre-configure responses; fetch calls are counted. struct MockFetcher { responses: DashMap>, + /// Optional per-user startup_parameters map. Surfaced via + /// `fetch_credentials` so cache-side wiring can be exercised + /// without standing up a real PG. + params: DashMap>, fetch_count: AtomicUsize, /// Optional delay to simulate slow PG queries (for concurrency tests). delay: std::time::Duration, @@ -648,6 +841,7 @@ mod tests { fn new() -> Self { Self { responses: DashMap::new(), + params: DashMap::new(), fetch_count: AtomicUsize::new(0), delay: std::time::Duration::ZERO, } @@ -656,6 +850,7 @@ mod tests { fn with_delay(delay: std::time::Duration) -> Self { Self { responses: DashMap::new(), + params: DashMap::new(), fetch_count: AtomicUsize::new(0), delay, } @@ -666,6 +861,21 @@ mod tests { .insert(username.to_string(), Some(password_hash.to_string())); } + fn add_user_with_params( + &self, + username: &str, + password_hash: &str, + params: &[(&str, &str)], + ) { + self.responses + .insert(username.to_string(), Some(password_hash.to_string())); + let map: std::collections::HashMap = params + .iter() + .map(|(k, v)| ((*k).to_string(), (*v).to_string())) + .collect(); + self.params.insert(username.to_string(), map); + } + fn fetch_count(&self) -> usize { self.fetch_count.load(Ordering::SeqCst) } @@ -690,6 +900,30 @@ mod tests { Ok(result) } } + + fn fetch_credentials<'a>( + &'a self, + username: &'a str, + ) -> impl Future, Error>> + Send + 'a { + self.fetch_count.fetch_add(1, Ordering::SeqCst); + let pw = self + .responses + .get(username) + .map(|r| r.clone()) + .unwrap_or(None); + let params = self + .params + .get(username) + .map(|r| r.clone()) + .unwrap_or_default(); + let delay = self.delay; + async move { + if !delay.is_zero() { + tokio::time::sleep(delay).await; + } + Ok(pw.map(|p| (p, params))) + } + } } fn test_config() -> AuthQueryConfig { @@ -950,4 +1184,148 @@ mod tests { assert_eq!(stats.cache_rate_limited.load(Ordering::Relaxed), 1); assert_eq!(stats.executor_queries.load(Ordering::Relaxed), 2); // no new query } + + // -- parse_startup_parameters_text: pure-parser unit tests -- + + #[test] + fn parse_startup_parameters_absent_column_returns_empty() { + let r = AuthQueryExecutor::parse_startup_parameters_text(None, "u", "p"); + assert!(r.is_empty()); + } + + #[test] + fn parse_startup_parameters_empty_string_returns_empty() { + let r = AuthQueryExecutor::parse_startup_parameters_text(Some(""), "u", "p"); + assert!(r.is_empty()); + } + + #[test] + fn parse_startup_parameters_simple_json_object() { + let r = AuthQueryExecutor::parse_startup_parameters_text( + Some(r#"{"plan_cache_mode":"force_custom_plan","work_mem":"64MB"}"#), + "u", + "p", + ); + assert_eq!( + r.get("plan_cache_mode").map(String::as_str), + Some("force_custom_plan") + ); + assert_eq!(r.get("work_mem").map(String::as_str), Some("64MB")); + assert_eq!(r.len(), 2); + } + + #[test] + fn parse_startup_parameters_reserved_key_dropped() { + // 'user' is reserved by pg_doorman; the valid sibling key survives. + let r = AuthQueryExecutor::parse_startup_parameters_text( + Some(r#"{"user":"x","work_mem":"64MB"}"#), + "u", + "p", + ); + assert!(!r.contains_key("user")); + assert_eq!(r.get("work_mem").map(String::as_str), Some("64MB")); + } + + #[test] + fn parse_startup_parameters_non_string_values_dropped() { + // number, boolean, null, array, object on the right-hand side are + // all rejected; only string-valued entries survive. + let r = AuthQueryExecutor::parse_startup_parameters_text( + Some( + r#"{"work_mem":64,"on":true,"off":null,"arr":[1],"obj":{},"plan_cache_mode":"force_custom_plan"}"#, + ), + "u", + "p", + ); + assert_eq!(r.len(), 1); + assert_eq!( + r.get("plan_cache_mode").map(String::as_str), + Some("force_custom_plan") + ); + } + + #[test] + fn parse_startup_parameters_malformed_json_returns_empty() { + let r = AuthQueryExecutor::parse_startup_parameters_text(Some("not-json"), "u", "p"); + assert!(r.is_empty()); + } + + #[test] + fn parse_startup_parameters_non_object_returns_empty() { + let r = AuthQueryExecutor::parse_startup_parameters_text(Some("[1,2,3]"), "u", "p"); + assert!(r.is_empty()); + } + + #[test] + fn parse_startup_parameters_invalid_guc_name_dropped() { + // Keys with spaces fail the shared `is_valid_guc_name` check used + // for operator-supplied parameter maps. + let r = AuthQueryExecutor::parse_startup_parameters_text( + Some(r#"{"bad name":"x","plan_cache_mode":"force_custom_plan"}"#), + "u", + "p", + ); + assert!(!r.contains_key("bad name")); + assert!(r.contains_key("plan_cache_mode")); + } + + #[test] + fn parse_startup_parameters_null_byte_value_dropped() { + // A null byte in the value fails the shared validator; the good + // neighbor still survives. + let r = AuthQueryExecutor::parse_startup_parameters_text( + Some("{\"work_mem\":\"64\\u0000MB\",\"plan_cache_mode\":\"force_custom_plan\"}"), + "u", + "p", + ); + assert!(!r.contains_key("work_mem")); + assert!(r.contains_key("plan_cache_mode")); + } + + // -- dedicated_mode_filter: drops params + warns once per username -- + + #[tokio::test] + async fn dedicated_mode_filter_drops_params_and_warns_once() { + let fetcher = Arc::new(MockFetcher::new()); + fetcher.add_user_with_params("alice", "md5abc123", &[("work_mem", "64MB")]); + let mut config = test_config(); + // Mark the config as dedicated by providing a server_user. + config.server_user = Some("doorman_backend".to_string()); + + let cache = make_cache(fetcher.clone(), &config); + + // Cache miss path applies the filter: per-user params are dropped + // because the backend identity is shared in dedicated mode. + let entry = cache.get_or_fetch("alice").await.unwrap().unwrap(); + assert!( + entry.startup_parameters.is_empty(), + "params must be cleared in dedicated mode" + ); + + // The warning fires at most once per username: subsequent calls do + // not insert into dedicated_warnings again. We assert that the + // tracker still holds exactly one entry after a second miss-and-fill. + cache.invalidate("alice"); + let entry = cache.get_or_fetch("alice").await.unwrap().unwrap(); + assert!(entry.startup_parameters.is_empty()); + assert_eq!(cache.dedicated_warnings.len(), 1); + + // clear() resets the warning tracker so a config reload re-arms it. + cache.clear(); + assert_eq!(cache.dedicated_warnings.len(), 0); + } + + #[tokio::test] + async fn non_dedicated_mode_keeps_params() { + let fetcher = Arc::new(MockFetcher::new()); + fetcher.add_user_with_params("alice", "md5abc123", &[("work_mem", "64MB")]); + let config = test_config(); // server_user = None: passthrough mode + + let cache = make_cache(fetcher.clone(), &config); + let entry = cache.get_or_fetch("alice").await.unwrap().unwrap(); + assert_eq!( + entry.startup_parameters.get("work_mem").map(String::as_str), + Some("64MB") + ); + } } From 4c95817c687d7033216736e344ea69299f14e672 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 22:43:08 +0300 Subject: [PATCH 09/72] feat(pool): resolve startup_parameters per-backend and wire quarantine ServerPool now owns a single Arc initialised from general.startup_parameter_quarantine_{threshold,ttl}. On every backend spawn the cascade (general / pool / passthrough auth_query) is resolved lazily through pool::startup_resolver::resolve(); that lazy contract is what allows general-level edits to take effect on the next backend without forcing a pool-hash recycle. Per-user parameters from auth_query enter the cascade only in passthrough mode. In dedicated mode the cache layer already clears the map (with an operator warning); this layer adds defence in depth by refusing to consult the cache entry at all when the pool is dedicated. After this commit, an operator config like [pools.app_db.startup_parameters] plan_cache_mode = "force_custom_plan" actually reaches PG. The prometheus counters and admin column ship in the next commit. --- src/auth/auth_query.rs | 77 +++++++++++++++++++++++++ src/pool/auth_query_state.rs | 13 +++++ src/pool/server_pool.rs | 105 +++++++++++++++++++++++++++-------- 3 files changed, 172 insertions(+), 23 deletions(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index 907d0026a..03b3b54ea 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -804,6 +804,23 @@ impl AuthQueryCache { .and_then(|e| e.client_key.clone()) } + /// Cheap, sync, non-fetching lookup of the per-user startup_parameters + /// map. Returns `None` when the username has no cached entry yet (e.g. + /// pool prewarm fires before any client has authenticated) or when the + /// cached entry is negative. Never triggers a PG fetch and never + /// initializes the executor; intended for the backend-spawn hot path + /// where blocking on auth_query I/O would be unacceptable. + pub fn peek_startup_parameters( + &self, + username: &str, + ) -> Option> { + let entry = self.entries.get(username)?; + if entry.is_negative { + return None; + } + Some(entry.startup_parameters.clone()) + } + /// Number of cached entries (for metrics/admin). pub fn len(&self) -> usize { self.entries.len() @@ -1328,4 +1345,64 @@ mod tests { Some("64MB") ); } + + // --------------------------------------------------------------------- + // peek_startup_parameters: sync, non-fetching lookup used by backend spawn + // --------------------------------------------------------------------- + + #[tokio::test] + async fn peek_startup_parameters_missing_user_returns_none() { + let fetcher = Arc::new(MockFetcher::new()); + let config = test_config(); + let cache = make_cache(fetcher, &config); + assert!(cache.peek_startup_parameters("alice").is_none()); + } + + #[tokio::test] + async fn peek_startup_parameters_negative_entry_returns_none() { + let fetcher = Arc::new(MockFetcher::new()); + // No user added; first lookup populates a negative cache entry. + let config = test_config(); + let cache = make_cache(fetcher, &config); + assert!(cache.get_or_fetch("ghost").await.unwrap().is_none()); + assert!(cache.peek_startup_parameters("ghost").is_none()); + } + + #[tokio::test] + async fn peek_startup_parameters_positive_entry_returns_map() { + let fetcher = Arc::new(MockFetcher::new()); + fetcher.add_user_with_params( + "alice", + "md5abc123", + &[("work_mem", "64MB"), ("statement_timeout", "10s")], + ); + let config = test_config(); + let cache = make_cache(fetcher, &config); + cache.get_or_fetch("alice").await.unwrap().unwrap(); + + let params = cache.peek_startup_parameters("alice").unwrap(); + assert_eq!(params.get("work_mem").map(String::as_str), Some("64MB")); + assert_eq!( + params.get("statement_timeout").map(String::as_str), + Some("10s") + ); + } + + #[tokio::test] + async fn peek_startup_parameters_dedicated_mode_returns_empty() { + // Dedicated mode filters cached entries to drop per-user params. + // peek must surface the filtered state: a present-but-empty map, + // not None, because the user is still cached. + let fetcher = Arc::new(MockFetcher::new()); + fetcher.add_user_with_params("alice", "md5abc123", &[("work_mem", "64MB")]); + let mut config = test_config(); + config.server_user = Some("shared".to_string()); + config.server_password = Some("secret".to_string()); + + let cache = make_cache(fetcher, &config); + cache.get_or_fetch("alice").await.unwrap().unwrap(); + + let params = cache.peek_startup_parameters("alice").unwrap(); + assert!(params.is_empty()); + } } diff --git a/src/pool/auth_query_state.rs b/src/pool/auth_query_state.rs index b9c4068f0..1bbf512b9 100644 --- a/src/pool/auth_query_state.rs +++ b/src/pool/auth_query_state.rs @@ -100,4 +100,17 @@ impl AuthQueryState { pub fn cache_len(&self) -> usize { self.cache_cell.get().map_or(0, |c| c.len()) } + + /// Sync, non-fetching peek of the per-user startup_parameters map. + /// Returns `None` if the auth_query executor was never initialized + /// (no client has authenticated through this pool yet) or if the + /// username has no cached entry. Used on the backend-spawn hot path + /// where blocking on a PG roundtrip would defeat the point of the + /// cache; cold lookups intentionally surface as "no per-user override". + pub fn peek_startup_parameters( + &self, + username: &str, + ) -> Option> { + self.cache_cell.get()?.peek_startup_parameters(username) + } } diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 0104adc10..477b244cc 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -83,6 +83,12 @@ pub struct ServerPool { /// Notify to wake up clients blocked on PAUSE. resume_notify: Notify, + + /// Shared quarantine state for operator-supplied startup_parameters. + /// One instance per pool so the rejection counter accumulates across + /// every backend spawn this pool issues; per-call construction would + /// reset the counter on each spawn and never trip the threshold. + startup_parameter_quarantine: Arc, } impl std::fmt::Debug for ServerPool { @@ -105,6 +111,10 @@ impl std::fmt::Debug for ServerPool { "connection_counter", &self.connection_counter.load(Ordering::Relaxed), ) + .field( + "startup_parameter_quarantine", + &self.startup_parameter_quarantine, + ) .finish() } } @@ -129,6 +139,17 @@ impl ServerPool { session_mode: bool, fallback_state: Option>, ) -> ServerPool { + // The quarantine knobs are global (general.*) so we read them at + // construction time; subsequent RELOADs that touch only the knobs + // do not redirect the existing pool's quarantine because RELOAD + // builds a new ServerPool whenever a pool's hash changes. + let cfg = crate::config::config_arc(); + let startup_parameter_quarantine = + Arc::new(crate::server::quarantine::QuarantineState::new( + cfg.general.startup_parameter_quarantine_threshold, + Duration::from_millis(cfg.general.startup_parameter_quarantine_ttl), + )); + ServerPool { address, user: user.clone(), @@ -149,6 +170,7 @@ impl ServerPool { resume_notify: Notify::new(), session_mode, fallback_state, + startup_parameter_quarantine, } } @@ -217,14 +239,11 @@ impl ServerPool { stats.register(stats.clone()); - // Phase 4 placeholder: per-call construction defeats sharing across - // backend spawns. Phase 6 will lift this onto the pool-owned Arc so - // rejection counters are actually shared. - let cfg = crate::config::get_config(); - let quarantine = Arc::new(crate::server::quarantine::QuarantineState::new( - cfg.general.startup_parameter_quarantine_threshold, - Duration::from_millis(cfg.general.startup_parameter_quarantine_ttl), - )); + // Resolve once for this spawn attempt: the plain attempt and the + // optional sslmode=allow TLS retry must see the same parameter set, + // otherwise a config RELOAD landing between the two would silently + // ship different StartupMessages for the same client request. + let startup_parameters = self.resolved_startup_parameters(); let result = startup_with_timeout( self.connect_timeout, @@ -241,8 +260,8 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - std::collections::BTreeMap::new(), - quarantine.clone(), + startup_parameters.clone(), + self.startup_parameter_quarantine.clone(), ), ) .await; @@ -298,8 +317,8 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - std::collections::BTreeMap::new(), - quarantine.clone(), + startup_parameters, + self.startup_parameter_quarantine.clone(), ), ) .await; @@ -342,6 +361,47 @@ impl ServerPool { &self.address } + /// Resolve the operator-supplied startup_parameters map that this pool + /// will hand to `Server::startup` for one backend spawn. The cascade is + /// `general` -> pool -> (optional) auth_query per-user entry, with the + /// more specific level winning per key. + /// + /// The general and pool maps are read live from the current config + /// snapshot, so a RELOAD that only changes operator-supplied parameters + /// can take effect on the next backend spawn without rebuilding the + /// pool. The auth_query layer is consulted only when this pool has a + /// passthrough auth_query state and a cached entry for the pool's + /// username already exists; dedicated mode pools never consult the + /// cache here, even though the cache write path already clears the map + /// in that mode, because the safety net is cheaper than another bug + /// hunt. + fn resolved_startup_parameters(&self) -> std::collections::BTreeMap { + let cfg = crate::config::config_arc(); + let pool_cfg = cfg.pools.get(&self.address.pool_name); + + let pool_params = pool_cfg + .map(|p| &p.startup_parameters) + .cloned() + .unwrap_or_default(); + + // Look up the per-user auth_query entry only when the pool runs in + // passthrough auth_query mode (no shared server_user). In dedicated + // mode the shared backend serves multiple dynamic users, so no + // single per-user override could be honoured. + let auth_query_params = match super::get_auth_query_state(&self.address.pool_name) { + Some(state) if !state.config.is_dedicated_mode() => { + state.peek_startup_parameters(&self.user.username) + } + _ => None, + }; + + super::startup_resolver::resolve( + &cfg.general.startup_parameters, + &pool_params, + auth_query_params.as_ref(), + ) + } + /// Establish a fallback connection by iterating through Patroni-discovered /// candidates. Per-candidate failures (auth error, "database is starting up", /// startup timeout, etc.) mark the candidate unhealthy and proceed to the @@ -663,13 +723,12 @@ impl ServerPool { )); stats.register(stats.clone()); - // Phase 4 placeholder: see the matching note in `create`. Phase 6 - // replaces this with the pool-owned shared quarantine Arc. - let cfg = crate::config::get_config(); - let quarantine = Arc::new(crate::server::quarantine::QuarantineState::new( - cfg.general.startup_parameter_quarantine_threshold, - Duration::from_millis(cfg.general.startup_parameter_quarantine_ttl), - )); + // Resolve once: the optional sslmode=allow retry below must see the + // same map as the plain attempt for the same reasons noted in + // `create()`. The fallback target's pool name matches `self`, so + // the per-pool cascade still applies even though we are talking + // to a different physical host than `self.address.host`. + let startup_parameters = self.resolved_startup_parameters(); let result = startup_with_timeout( fallback_timeout, @@ -686,8 +745,8 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - std::collections::BTreeMap::new(), - quarantine.clone(), + startup_parameters.clone(), + self.startup_parameter_quarantine.clone(), ), ) .await; @@ -736,8 +795,8 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - std::collections::BTreeMap::new(), - quarantine.clone(), + startup_parameters, + self.startup_parameter_quarantine.clone(), ), ) .await; From f49317e583121b3783e3242ca6eae1fb717562b4 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 22:55:35 +0300 Subject: [PATCH 10/72] feat(observability): metrics and SHOW POOLS for startup_parameters health * pg_doorman_backend_startup_parameter_errors_total (counter, labels pool / parameter / sqlstate) increments on every PG ErrorResponse that rejects an operator-supplied parameter at backend startup. The failing username is intentionally not a label so that dynamic auth_query pools (one Address per validated PG role) cannot blow up the series count when many roles use the same broken config; the username is in the corresponding warn log line. * pg_doorman_backend_startup_parameter_quarantined (gauge, labels pool / parameter) goes to 1 the moment a key is parked in the pool quarantine and back to 0 when its TTL expires. * SHOW POOLS gains a quarantined_params text column (comma-separated list) so operators can see which operator parameters a pool is currently dropping from StartupMessage. Replaces the temporary noop counter wrappers that were left in the backend startup error path while these vectors were being prepared. --- src/pool/inner.rs | 7 ++++ src/pool/server_pool.rs | 8 +++++ src/server/server_backend.rs | 29 ++++++++++++++--- src/stats/pool.rs | 63 ++++++++++++++++++++++++++++++++++++ src/web/metrics/mod.rs | 51 +++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 4 deletions(-) diff --git a/src/pool/inner.rs b/src/pool/inner.rs index 5f2db1dc5..348a939cc 100644 --- a/src/pool/inner.rs +++ b/src/pool/inner.rs @@ -1545,6 +1545,13 @@ impl Pool { self.inner.server_pool.is_paused() } + /// Operator-supplied startup_parameters currently parked in the + /// per-pool quarantine. Delegates to the `ServerPool` snapshot so + /// SHOW POOLS can render the same set the Prometheus gauge exposes. + pub fn quarantined_startup_parameters(&self) -> Vec { + self.inner.server_pool.quarantined_startup_parameters() + } + /// Bumps reconnect epoch and drains all idle connections. /// Returns the new epoch value. pub fn reconnect(&self) -> u32 { diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 477b244cc..50cb96026 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -361,6 +361,14 @@ impl ServerPool { &self.address } + /// Snapshot of operator-supplied startup_parameter names that this + /// pool currently strips from `StartupMessage`. Used by SHOW POOLS + /// (admin console) so operators can see which knobs a pool is + /// parking without correlating logs to Prometheus. + pub fn quarantined_startup_parameters(&self) -> Vec { + self.startup_parameter_quarantine.snapshot_quarantined() + } + /// Resolve the operator-supplied startup_parameters map that this pool /// will hand to `Server::startup` for one backend spawn. The cascade is /// `general` -> pool -> (optional) auth_query per-user entry, with the diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index 5ffe98509..bd78786d1 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -808,10 +808,17 @@ impl Server { let mut startup_started: Option = None; // Strip currently-quarantined keys from the operator-supplied set - // right before we serialize the StartupMessage. The released set is - // currently unused; Phase 7 will wire Prometheus gauges from it. + // right before we serialize the StartupMessage. Any keys whose TTL + // expired during this call are reported back so we can flip their + // Prometheus gauge series back to 0 (the alert rule fires on the + // gauge, not on log scraping). let mut startup_parameters_sent = startup_parameters; - let _released_keys = quarantine.filter_active_keys(&mut startup_parameters_sent); + let released_keys = quarantine.filter_active_keys(&mut startup_parameters_sent); + for k in &released_keys { + crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED + .with_label_values(&[&address.pool_name, k]) + .set(0); + } startup( &mut stream, @@ -952,7 +959,21 @@ impl Server { msg.message, outcome, ); - // Phase 7 will wire real Prometheus vectors here. + crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL + .with_label_values(&[ + &address.pool_name, + ¶m_name, + &msg.code, + ]) + .inc(); + if matches!( + outcome, + crate::server::quarantine::RecordOutcome::JustQuarantined + ) { + crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED + .with_label_values(&[&address.pool_name, ¶m_name]) + .set(1); + } } } } diff --git a/src/stats/pool.rs b/src/stats/pool.rs index f7e85ee80..c72afa5bd 100644 --- a/src/stats/pool.rs +++ b/src/stats/pool.rs @@ -203,6 +203,15 @@ pub struct PoolStats { /// Configured maximum pool size (from user config or default) pub pool_size: u32, + + /// Names of operator-supplied startup parameters that are currently + /// parked in the per-pool quarantine. Snapshot taken at pool-stats + /// build time; rendered as a comma-separated text column in SHOW + /// POOLS so an operator at `psql` can see at a glance which knobs a + /// pool is dropping from StartupMessage without leaving the prompt. + /// Empty for pools whose `startup_parameters` map is empty or where + /// no parameter has crossed the quarantine threshold. + pub quarantined_params: Vec, } #[derive(Debug, Clone)] @@ -298,6 +307,7 @@ impl PoolStats { fallback_active: false, source_generation: 0, pool_size: 0, + quarantined_params: Vec::new(), } } @@ -371,6 +381,7 @@ impl PoolStats { ("paused", DataType::Text), ("fallback_active", DataType::Text), ("oldest_active_age_ms", DataType::Numeric), + ("quarantined_params", DataType::Text), ] } @@ -445,6 +456,7 @@ impl PoolStats { Cow::Borrowed(if self.paused { "1" } else { "0" }), Cow::Borrowed(if self.fallback_active { "1" } else { "0" }), Cow::Owned(self.oldest_active_age_ms.to_string()), + Cow::Owned(self.quarantined_params.join(",")), ] } @@ -618,6 +630,11 @@ impl PoolStats { current.prepared_statements_bytes = cache.memory_usage() as u64; } + // Snapshot the currently-parked startup_parameters so SHOW + // POOLS reflects the same set of parameters the Prometheus + // gauge already exposes through the metrics endpoint. + current.quarantined_params = pool.database.quarantined_startup_parameters(); + // Load statistics for SHOW STATS command current.total_xact_count = address.total.xact_count.load(Ordering::Relaxed); current.total_query_count = address.total.query_count.load(Ordering::Relaxed); @@ -828,6 +845,52 @@ mod tests { assert_eq!(row[14].as_ref(), "5", "column 14 should be avg_errors"); } + /// SHOW POOLS header advertises a `quarantined_params` column so the + /// admin console reflects the same state that + /// `pg_doorman_backend_startup_parameter_quarantined` exposes through + /// the metrics endpoint. A missing column there means operators have + /// to leave `psql` to triage a quarantined parameter. + #[test] + fn show_pools_header_advertises_quarantined_params_column() { + let header = PoolStats::generate_show_pools_header(); + let names: Vec<&str> = header.iter().map(|(n, _)| *n).collect(); + assert!( + names.contains(&"quarantined_params"), + "SHOW POOLS header missing quarantined_params column: {names:?}" + ); + } + + /// SHOW POOLS row width must match the header width on every config, + /// otherwise the admin console renders misaligned columns. The risk + /// shows up when only one of header or row is touched when a new + /// column is added. + #[test] + fn show_pools_row_and_header_have_same_width() { + let percentile = Percentile { + p99: 0, + p95: 0, + p90: 0, + p50: 0, + }; + let mut stats = PoolStats::new_with_percentiles( + PoolIdentifier::new("shop", "alice"), + PoolMode::Transaction, + percentile.clone(), + percentile.clone(), + percentile, + ); + stats.quarantined_params = vec!["search_path".to_string(), "work_mem".to_string()]; + + let header = PoolStats::generate_show_pools_header(); + let row = stats.generate_show_pools_row(); + assert_eq!(header.len(), row.len(), "header/row width mismatch"); + let idx = header + .iter() + .position(|(n, _)| *n == "quarantined_params") + .expect("quarantined_params column registered"); + assert_eq!(row[idx].as_ref(), "search_path,work_mem"); + } + /// Both entry points must agree on shape when fed the same global /// POOLS state and equivalent client/server maps. Validates that /// `construct_pool_lookup_from` is a structural extract of diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index effa42827..da6b9fe45 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -457,6 +457,57 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| counter }); +/// Counter for every backend startup attempt that PostgreSQL rejected +/// because of an operator-supplied `startup_parameters` entry. Tracked +/// per pool/user/parameter so dashboards can spot a single bad knob in a +/// large fleet, and split by `sqlstate` (`22023` invalid_value, `42704` +/// undefined_object, `42501` insufficient_privilege) so the alerting +/// rule can distinguish a typo from a permission denial. Increments +/// once per failed StartupMessage, regardless of whether this rejection +/// happens to be the one that crosses the quarantine threshold. +pub(crate) static BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL: Lazy = Lazy::new(|| { + let counter = IntCounterVec::new( + Opts::new( + "pg_doorman_backend_startup_parameter_errors_total", + "Cumulative count of backend startup attempts rejected by \ + PostgreSQL because of an operator-supplied startup_parameters \ + entry. Labels: pool, parameter, sqlstate (22023 invalid_value \ + / 42704 undefined_object / 42501 insufficient_privilege). \ + Counts every rejection, not just the one that crossed the \ + quarantine threshold. The failing username is available in the \ + corresponding warn log line.", + ), + &["pool", "parameter", "sqlstate"], + ) + .unwrap(); + REGISTRY.register(Box::new(counter.clone())).unwrap(); + counter +}); + +/// One series per `(pool, parameter)` pair that pg_doorman currently +/// strips from the StartupMessage because its consecutive-rejection +/// counter crossed `general.startup_parameter_quarantine_threshold`. +/// Value is `1` while the entry is parked and flips back to `0` exactly +/// once when its TTL expires (the next backend spawn attempts the +/// parameter again from scratch). No mid-flight clearing on its own: +/// even if the operator fixes the underlying issue, the gauge stays at +/// `1` until `general.startup_parameter_quarantine_ttl` elapses. +pub(crate) static BACKEND_STARTUP_PARAMETER_QUARANTINED: Lazy = Lazy::new(|| { + let gauge = IntGaugeVec::new( + Opts::new( + "pg_doorman_backend_startup_parameter_quarantined", + "1 while pg_doorman has quarantined an operator-supplied \ + startup_parameter for a pool (consecutive rejections crossed \ + general.startup_parameter_quarantine_threshold). Cleared to 0 \ + only when the quarantine TTL expires.", + ), + &["pool", "parameter"], + ) + .unwrap(); + REGISTRY.register(Box::new(gauge.clone())).unwrap(); + gauge +}); + /// Counter for protocol-level large-message streaming events. pg_doorman /// drops to byte-stream forwarding when a server message of type DataRow /// ('D') or CopyData ('d') exceeds max_message_size — see From e4985755c2f408d1ef2473d7f100f61a11293a84 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 23:52:01 +0300 Subject: [PATCH 11/72] test(bdd): startup_parameters end-to-end scenarios Twelve scenarios exercise the operator-facing surface from this branch: cascade resolution across general, pool, and auth_query passthrough levels, reserved-key config rejection, the invalid-GUC backend startup error path, quarantine engagement after consecutive rejections, the central invariant that startup defaults survive a client-side RESET ALL or DISCARD ALL, RELOAD-driven pool recycle when pool.startup_parameters changes, dedicated-mode auth_query ignoring per-user values with a single warning, and the backwards-compatible path where auth_query SQL without the new column keeps working. Adds tests/auth_query_startup_params_fixture.sql with two users (sp_tuned_user with a per-user plan_cache_mode override in the JSON column, sp_plain_user with a NULL column value) used by the passthrough cascade, dedicated-mode warning, and baseline scenarios. --- tests/auth_query_startup_params_fixture.sql | 34 ++ tests/bdd/features/startup-parameters.feature | 513 ++++++++++++++++++ 2 files changed, 547 insertions(+) create mode 100644 tests/auth_query_startup_params_fixture.sql create mode 100644 tests/bdd/features/startup-parameters.feature diff --git a/tests/auth_query_startup_params_fixture.sql b/tests/auth_query_startup_params_fixture.sql new file mode 100644 index 000000000..c58fb0487 --- /dev/null +++ b/tests/auth_query_startup_params_fixture.sql @@ -0,0 +1,34 @@ +-- Fixtures for auth_query scenarios that exercise the optional +-- startup_parameters JSON column. +-- +-- Layout matches auth_query_passthrough_fixture.sql with one extra +-- column. Existing scenarios that select only (username, password) keep +-- working: the new column is opt-in at the auth_query SQL level, not +-- at the table schema level. + +CREATE TABLE IF NOT EXISTS auth_users ( + username TEXT NOT NULL, + password TEXT, + startup_parameters TEXT +); + +SET password_encryption = 'md5'; + +-- User whose per-user startup_parameters override pool defaults. +-- Used by the passthrough-cascade scenario: pool sets plan_cache_mode +-- to 'auto', auth_query column overrides it to 'force_custom_plan' +-- for this user only. +CREATE USER sp_tuned_user WITH PASSWORD 'tuned_pass'; +INSERT INTO auth_users + SELECT rolname, rolpassword, '{"plan_cache_mode":"force_custom_plan"}' + FROM pg_authid WHERE rolname = 'sp_tuned_user'; + +-- User with NULL startup_parameters: same fixture covers the +-- "column present but no per-user override" baseline. +CREATE USER sp_plain_user WITH PASSWORD 'plain_pass'; +INSERT INTO auth_users + SELECT rolname, rolpassword, NULL + FROM pg_authid WHERE rolname = 'sp_plain_user'; + +GRANT ALL ON DATABASE postgres TO sp_tuned_user; +GRANT ALL ON DATABASE postgres TO sp_plain_user; diff --git a/tests/bdd/features/startup-parameters.feature b/tests/bdd/features/startup-parameters.feature new file mode 100644 index 000000000..3f58f4282 --- /dev/null +++ b/tests/bdd/features/startup-parameters.feature @@ -0,0 +1,513 @@ +@startup-parameters +Feature: Per-pool startup_parameters injection + pg_doorman injects operator-supplied PostgreSQL run-time parameters into + every backend StartupMessage on a three-level cascade: general defaults, + per-pool overrides, and (in auth_query passthrough mode) per-user + overrides from an optional JSON column. + + Scenario: general.startup_parameters apply on a fresh backend + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [general.startup_parameters] + statement_timeout = "12345" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + Then psql query "SHOW statement_timeout" via pg_doorman as user "example_user_1" to database "example_db" with password "test" returns "12345ms" + + Scenario: pool.startup_parameters overrides general per-key + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [general.startup_parameters] + statement_timeout = "10001" + lock_timeout = "5001" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.example_db.startup_parameters] + statement_timeout = "23456" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + Then psql query "SHOW statement_timeout" via pg_doorman as user "example_user_1" to database "example_db" with password "test" returns "23456ms" + And psql query "SHOW lock_timeout" via pg_doorman as user "example_user_1" to database "example_db" with password "test" returns "5001ms" + + Scenario: auth_query passthrough per-user JSON column overrides pool default + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all postgres 127.0.0.1/32 trust + host all all 127.0.0.1/32 md5 + host all all ::1/128 trust + """ + And fixtures from "tests/auth_query_startup_params_fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.postgres] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.postgres.startup_parameters] + plan_cache_mode = "auto" + + [pools.postgres.auth_query] + query = "SELECT username, password, startup_parameters FROM auth_users WHERE username = $1" + user = "postgres" + password = "" + workers = 1 + pool_size = 5 + cache_ttl = "1h" + cache_failure_ttl = "30s" + min_interval = "0s" + """ + Then psql query "SHOW plan_cache_mode" via pg_doorman as user "sp_tuned_user" to database "postgres" with password "tuned_pass" returns "force_custom_plan" + And psql query "SHOW plan_cache_mode" via pg_doorman as user "sp_plain_user" to database "postgres" with password "plain_pass" returns "auto" + + Scenario: operator-supplied application_name in startup_parameters wins over pool default + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + application_name = "doorman_default_app" + + [pools.example_db.startup_parameters] + application_name = "operator_supplied_app" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + Then psql query "SHOW application_name" via pg_doorman as user "example_user_1" to database "example_db" with password "test" returns "operator_supplied_app" + + Scenario: reserved-key in startup_parameters is rejected at config validation + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + # pg_doorman is running with a valid config. Now overwrite the file with a + # reserved-key violation and re-run the binary in -t mode to confirm config + # validation rejects the change before any worker can pick it up. + When we overwrite pg_doorman config file with: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.example_db.startup_parameters] + user = "evil_override" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + # `pg_doorman -t` exits non-zero when validation fails; the precise + # reserved-key message lands in a logger that has not been initialised at + # this point in startup, so we assert on the exit code only. The narrower + # reserved-key behaviour is exercised by unit tests in src/config/tests.rs. + And I run shell command "${DOORMAN_BINARY} -t ${DOORMAN_CONFIG_FILE}" + Then the command should fail + + Scenario: invalid GUC name produces a backend startup error with warn log + Given pg_doorman log capture enabled + And PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + startup_parameter_quarantine_threshold = 999 + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.example_db.startup_parameters] + nonexistent_guc_zzz = "value" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" fails + And pg_doorman log contains "backend startup rejected" + And pg_doorman log contains "nonexistent_guc_zzz" + + Scenario: quarantine after consecutive rejections strips the bad key on next attempt + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + startup_parameter_quarantine_threshold = 1 + startup_parameter_quarantine_ttl = 60000 + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.example_db.startup_parameters] + nonexistent_guc_yyy = "value" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + # First connection trips the quarantine threshold and fails. + Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" fails + # After quarantine engages, the bad key is stripped and a fresh backend can + # be created; the connection now succeeds and the SHOW returns the default + # because nonexistent_guc_yyy never reached PostgreSQL. + Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" succeeds + # SHOW POOLS surfaces the quarantined parameter name so an operator + # triaging from psql can see what pg_doorman is parking. + When we create admin session "adm" to pg_doorman as "admin" with password "admin" + And we execute "SHOW POOLS" on admin session "adm" and store response + Then admin session "adm" response should contain "nonexistent_guc_yyy" + + Scenario: RESET ALL restores startup_parameters defaults + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "session" + + [pools.example_db.startup_parameters] + plan_cache_mode = "force_custom_plan" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + When I run shell command: + """ + PGPASSWORD=test PGSSLMODE=disable psql -h 127.0.0.1 -p ${DOORMAN_PORT} \ + -U example_user_1 -d example_db -A -t <<'SQL' + SET plan_cache_mode = 'auto'; + SHOW plan_cache_mode; + RESET ALL; + SHOW plan_cache_mode; + SQL + """ + Then the command should succeed + # The first SHOW returns the client-set value, the second returns the + # operator-supplied startup default (RESET ALL falls back to reset_val, + # which is the value PG saw in StartupMessage). + And the command output should contain "auto" + And the command output should contain "force_custom_plan" + + Scenario: DISCARD ALL restores startup_parameters defaults + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "session" + + [pools.example_db.startup_parameters] + plan_cache_mode = "force_custom_plan" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + When I run shell command: + """ + PGPASSWORD=test PGSSLMODE=disable psql -h 127.0.0.1 -p ${DOORMAN_PORT} \ + -U example_user_1 -d example_db -A -t <<'SQL' + SET plan_cache_mode = 'auto'; + SHOW plan_cache_mode; + DISCARD ALL; + SHOW plan_cache_mode; + SQL + """ + Then the command should succeed + And the command output should contain "auto" + And the command output should contain "force_custom_plan" + + Scenario: RELOAD recycles pool when pool.startup_parameters changes + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + """ + And fixtures from "tests/fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.example_db.startup_parameters] + statement_timeout = "11111" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + Then psql query "SHOW statement_timeout" via pg_doorman as user "example_user_1" to database "example_db" with password "test" returns "11111ms" + When we overwrite pg_doorman config file with: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.example_db] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.example_db.startup_parameters] + statement_timeout = "22222" + + [[pools.example_db.users]] + username = "example_user_1" + password = "md58a67a0c805a5ee0384ea28e0dea557b6" + pool_size = 2 + """ + And we create admin session "adm1" to pg_doorman as "admin" with password "admin" + And we execute "RELOAD" on admin session "adm1" + And we sleep for 500 milliseconds + Then psql query "SHOW statement_timeout" via pg_doorman as user "example_user_1" to database "example_db" with password "test" returns "22222ms" + + Scenario: dedicated auth_query mode logs a warning and ignores per-user startup_parameters + Given pg_doorman log capture enabled + And PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all postgres 127.0.0.1/32 trust + host all all 127.0.0.1/32 md5 + host all all ::1/128 trust + """ + And fixtures from "tests/auth_query_startup_params_fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.postgres] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.postgres.auth_query] + query = "SELECT username, password, startup_parameters FROM auth_users WHERE username = $1" + user = "postgres" + password = "" + workers = 1 + pool_size = 5 + cache_ttl = "1h" + cache_failure_ttl = "30s" + min_interval = "0s" + server_user = "postgres" + server_password = "" + """ + # Authentication still succeeds; the JSON-column value is dropped silently + # at the cache layer in dedicated mode because every dynamic user shares + # a single backend identity. The warning surfaces the dropped key for the + # operator instead of leaving it unobservable. + Then psql connection to pg_doorman as user "sp_tuned_user" to database "postgres" with password "tuned_pass" succeeds + And pg_doorman log contains "per-user startup_parameters ignored in dedicated" + + Scenario: auth_query SQL without the startup_parameters column keeps working + Given PostgreSQL started with pg_hba.conf: + """ + local all all trust + host all postgres 127.0.0.1/32 trust + host all all 127.0.0.1/32 md5 + host all all ::1/128 trust + """ + And fixtures from "tests/auth_query_passthrough_fixture.sql" applied + And pg_doorman started with config: + """ + [general] + host = "127.0.0.1" + port = ${DOORMAN_PORT} + admin_username = "admin" + admin_password = "admin" + pg_hba.content = "host all all 127.0.0.1/32 md5" + + [pools.postgres] + server_host = "127.0.0.1" + server_port = ${PG_PORT} + pool_mode = "transaction" + + [pools.postgres.auth_query] + query = "SELECT username, password FROM auth_users WHERE username = $1" + user = "postgres" + password = "" + workers = 1 + pool_size = 5 + cache_ttl = "1h" + cache_failure_ttl = "30s" + min_interval = "0s" + """ + Then psql connection to pg_doorman as user "pt_md5_user" to database "postgres" with password "md5_pass" succeeds From 01c354bf55330e223446b6fe036a85c83e0cee45 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Mon, 11 May 2026 23:56:54 +0300 Subject: [PATCH 12/72] fix(server): preserve 57P SQLSTATE mapping for Patroni fallback Phase 4 replaced handle_startup_error with an inline branch in Server::startup that always returned ServerStartupError. The pre-Phase-4 helper specifically mapped SQLSTATE 57P* (cannot_connect_now, admin_shutdown, crash_shutdown, database_dropped, etc.) to ServerUnavailableError, and only that error category triggers the Patroni-assisted fallback path. With the inline branch in place a backend reporting 57P03 no longer routed clients onto a healthy candidate; clients saw a startup error. Restores the classification: 57P* responses turn into ServerUnavailableError, every other ErrorResponse stays ServerStartupError. The startup_parameters quarantine and metrics side-effects still fire for the 22023 / 42704 / 42501 family on the way through; their evaluation is unrelated to the fallback decision. --- src/server/server_backend.rs | 96 ++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index bd78786d1..049f8dfa0 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -926,62 +926,64 @@ impl Server { } } - // ErrorResponse. Read the message body, parse it, and - - // when the SQLSTATE belongs to the startup-parameter family - // and the operator-supplied map was non-empty - feed the - // failing parameter name into the per-pool quarantine. We - // intentionally bypass `handle_startup_error` here because - // that helper consumes the body internally and we need to - // inspect the M-field for the parameter name; the existing - // error pathway (`Error::ServerStartupError`) is preserved - // so callers see the same outward behavior. + // ErrorResponse. Read the message body and parse it. When the + // SQLSTATE belongs to the startup-parameter family and the + // operator-supplied map was non-empty, feed the failing + // parameter name into the per-pool quarantine. Finally, + // preserve the pre-quarantine error classification so the + // Patroni-assisted fallback path still treats transient PG + // unavailability (SQLSTATE class `57P`) as a route-elsewhere + // signal rather than a startup misconfiguration. 'E' => { let mut bytes = read_message_data(&mut stream, code as u8, len).await?; let _ = bytes.get_u8(); let _ = bytes.get_i32(); - let parsed = PgErrorMsg::parse(&bytes).ok(); - - if let Some(ref msg) = parsed { - let is_startup_parameter_sqlstate = - matches!(msg.code.as_str(), "22023" | "42704" | "42501"); - if is_startup_parameter_sqlstate && !startup_parameters_sent.is_empty() { - if let Some(param_name) = - crate::server::startup_error::extract_parameter_name(&msg.message) - { - let outcome = quarantine.record_rejection(¶m_name, &msg.code); - warn!( - "[{}@{}] backend startup rejected: parameter=\"{}\" \ - sqlstate={} message=\"{}\" outcome={:?}", - address.username, - address.pool_name, - param_name, - msg.code, - msg.message, - outcome, - ); - crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL - .with_label_values(&[ - &address.pool_name, - ¶m_name, - &msg.code, - ]) - .inc(); - if matches!( - outcome, - crate::server::quarantine::RecordOutcome::JustQuarantined - ) { - crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED - .with_label_values(&[&address.pool_name, ¶m_name]) - .set(1); - } + let Ok(msg) = PgErrorMsg::parse(&bytes) else { + return Err(Error::ServerStartupError( + "startup ErrorResponse".to_string(), + server_identifier.clone(), + )); + }; + + let is_startup_parameter_sqlstate = + matches!(msg.code.as_str(), "22023" | "42704" | "42501"); + if is_startup_parameter_sqlstate && !startup_parameters_sent.is_empty() { + if let Some(param_name) = + crate::server::startup_error::extract_parameter_name(&msg.message) + { + let outcome = quarantine.record_rejection(¶m_name, &msg.code); + warn!( + "[{}@{}] backend startup rejected: parameter=\"{}\" \ + sqlstate={} message=\"{}\" outcome={:?}", + address.username, + address.pool_name, + param_name, + msg.code, + msg.message, + outcome, + ); + crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL + .with_label_values(&[&address.pool_name, ¶m_name, &msg.code]) + .inc(); + if matches!( + outcome, + crate::server::quarantine::RecordOutcome::JustQuarantined + ) { + crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED + .with_label_values(&[&address.pool_name, ¶m_name]) + .set(1); } } } + if msg.code.starts_with("57P") { + return Err(Error::ServerUnavailableError( + msg.message, + server_identifier.clone(), + )); + } return Err(Error::ServerStartupError( - parsed - .map(|m| format!("{}: {}", m.code, m.message)) - .unwrap_or_else(|| "startup ErrorResponse".to_string()), + format!("{}: {}", msg.code, msg.message), server_identifier.clone(), )); } From 74637540dae7f3ad65f7a760d9d40d2ca1e1e67f Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:02:03 +0300 Subject: [PATCH 13/72] fix(pool): post-cascade size check prevents oversize StartupMessage Per-level validation in Config::validate caps general.startup_parameters and pool.startup_parameters separately. Two levels that each fit the 9 488-byte operator budget can together push the resolved cascade past PG's MAX_STARTUP_PACKET_LENGTH (10 000 bytes), at which point PG would reject every backend startup. With auth_query in the mix the per-pair syntax check sees only one entry at a time, so the per-user contribution to the budget is also invisible to load-time validation. resolved_startup_parameters now serialises the merged map and compares against the operator budget after the cascade has run. When the body would not fit, all operator-supplied keys are dropped for this spawn and the situation is logged; the backend connects with server defaults rather than the alternative of failing every connection attempt for this pool until the operator notices and trims the config. Adds serialized_bytes() as a pub helper and three unit tests: per-pair NUL accounting, empty-map zero, and the multi-level overflow case where each individual level fits but their union does not. --- src/config/startup_parameters.rs | 48 ++++++++++++++++++++++++++++++-- src/pool/server_pool.rs | 27 ++++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/config/startup_parameters.rs b/src/config/startup_parameters.rs index 30b27e15b..95351dffd 100644 --- a/src/config/startup_parameters.rs +++ b/src/config/startup_parameters.rs @@ -85,8 +85,7 @@ fn validate_value(key: &str, value: &str, scope: &str) -> Result<(), Error> { } fn validate_total_size(map: &BTreeMap, scope: &str) -> Result<(), Error> { - // Per the PG wire layout each pair contributes key, NUL, value, NUL. - let total: usize = map.iter().map(|(k, v)| k.len() + 1 + v.len() + 1).sum(); + let total = serialized_bytes(map); if total > MAX_OPERATOR_BUDGET { return Err(Error::BadConfig(format!( "{scope}: serialized size {total} bytes exceeds operator budget {MAX_OPERATOR_BUDGET} \ @@ -98,6 +97,12 @@ fn validate_total_size(map: &BTreeMap, scope: &str) -> Result<() Ok(()) } +/// Bytes the operator-supplied map will occupy on the StartupMessage wire, +/// per the PG layout where each pair contributes `key\0value\0`. +pub fn serialized_bytes(map: &BTreeMap) -> usize { + map.iter().map(|(k, v)| k.len() + 1 + v.len() + 1).sum() +} + #[cfg(test)] mod tests { use super::*; @@ -199,4 +204,43 @@ mod tests { MAX_STARTUP_PACKET_SIZE - RESERVED_HEADROOM ); } + + #[test] + fn serialized_bytes_counts_per_pair_nuls() { + let map = m(&[("k1", "v1"), ("plan_cache_mode", "force_custom_plan")]); + // "k1\0v1\0" = 2 + 1 + 2 + 1 = 6 bytes + // "plan_cache_mode\0force_custom_plan\0" = 15 + 1 + 17 + 1 = 34 bytes + assert_eq!(serialized_bytes(&map), 6 + 34); + } + + #[test] + fn serialized_bytes_empty_map_is_zero() { + assert_eq!(serialized_bytes(&BTreeMap::new()), 0); + } + + #[test] + fn cascade_overflow_detectable_after_merge() { + // Each level fits the per-level budget on its own (every map below is + // ~3 KiB), but the union of all three pushes past 9 488 bytes and + // would trip the post-resolve guard in `server_pool.rs`. + let general: BTreeMap = (0..32) + .map(|i| (format!("g_key_{i}"), "a".repeat(100))) + .collect(); + let pool: BTreeMap = (0..32) + .map(|i| (format!("p_key_{i}"), "b".repeat(100))) + .collect(); + let auth: BTreeMap = (0..32) + .map(|i| (format!("a_key_{i}"), "c".repeat(100))) + .collect(); + // Each map ~ 32 * (8 + 1 + 100 + 1) = 32 * 110 = 3 520 bytes < 9 488. + assert!(serialized_bytes(&general) < MAX_OPERATOR_BUDGET); + assert!(serialized_bytes(&pool) < MAX_OPERATOR_BUDGET); + assert!(serialized_bytes(&auth) < MAX_OPERATOR_BUDGET); + + let mut merged: BTreeMap = BTreeMap::new(); + merged.extend(general.iter().map(|(k, v)| (k.clone(), v.clone()))); + merged.extend(pool.iter().map(|(k, v)| (k.clone(), v.clone()))); + merged.extend(auth.iter().map(|(k, v)| (k.clone(), v.clone()))); + assert!(serialized_bytes(&merged) > MAX_OPERATOR_BUDGET); + } } diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 50cb96026..623f61ce1 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -403,11 +403,34 @@ impl ServerPool { _ => None, }; - super::startup_resolver::resolve( + let merged = super::startup_resolver::resolve( &cfg.general.startup_parameters, &pool_params, auth_query_params.as_ref(), - ) + ); + + // Per-level validation in `Config::validate` does not see the merged + // cascade. Two levels that each fit individually can together push the + // StartupMessage past PG's `MAX_STARTUP_PACKET_LENGTH`. If the merged + // body would not fit, drop all operator-supplied keys for this spawn + // and log; the backend connects with server defaults rather than + // failing every time. The static per-level cap already prevents any + // single level from saturating the budget on its own. + let body_bytes = crate::config::startup_parameters::serialized_bytes(&merged); + if body_bytes > crate::config::startup_parameters::MAX_OPERATOR_BUDGET { + warn!( + "[{}@{}] effective startup_parameters serialize to {} bytes, exceeding \ + operator budget {} (PG cap {}); all operator-supplied parameters dropped \ + for this backend spawn", + self.user.username, + self.address.pool_name, + body_bytes, + crate::config::startup_parameters::MAX_OPERATOR_BUDGET, + crate::config::startup_parameters::MAX_STARTUP_PACKET_SIZE, + ); + return std::collections::BTreeMap::new(); + } + merged } /// Establish a fallback connection by iterating through Patroni-discovered From a2ef85e67ff8ac1e08d4085df0375ba531bc6988 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:05:20 +0300 Subject: [PATCH 14/72] fix(server): only quarantine startup parameters pg_doorman actually sent The startup-parameter quarantine fired whenever PG returned a sqlstate in the 22023 / 42704 / 42501 family and the parsed message named a parameter, regardless of whether that parameter was in the map pg_doorman sent. PG can produce those sqlstates for parameters pg_doorman does not control: an ALTER ROLE SET applied at login, a server-side default the role is not permitted to change, an internal extension, and so on. The quarantine and the counter would then record a rejection for a key that the operator did not configure, and on the next backend spawn pg_doorman would still try to send its own keys, producing a confusing mix in SHOW POOLS and metrics. The handler now cross-references the failing parameter name against the map it actually sent. If the key is ours, the same quarantine and metrics path as before runs. If it is not ours, pg_doorman logs an info line so the situation is visible and falls through to the regular ServerStartupError path without recording a rejection. --- src/server/server_backend.rs | 64 ++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index 049f8dfa0..55c28a5f2 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -951,27 +951,49 @@ impl Server { if let Some(param_name) = crate::server::startup_error::extract_parameter_name(&msg.message) { - let outcome = quarantine.record_rejection(¶m_name, &msg.code); - warn!( - "[{}@{}] backend startup rejected: parameter=\"{}\" \ - sqlstate={} message=\"{}\" outcome={:?}", - address.username, - address.pool_name, - param_name, - msg.code, - msg.message, - outcome, - ); - crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL - .with_label_values(&[&address.pool_name, ¶m_name, &msg.code]) - .inc(); - if matches!( - outcome, - crate::server::quarantine::RecordOutcome::JustQuarantined - ) { - crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED - .with_label_values(&[&address.pool_name, ¶m_name]) - .set(1); + // PG can report the same SQLSTATE family for a parameter pg_doorman + // did not send (for example, an ALTER ROLE/DATABASE SET running on + // login or a server-side default that the role is not allowed to + // apply). Without this check the quarantine and metrics would record + // a rejection for a key the operator never asked us to send. + if startup_parameters_sent.contains_key(¶m_name) { + let outcome = quarantine.record_rejection(¶m_name, &msg.code); + warn!( + "[{}@{}] backend startup rejected: parameter=\"{}\" \ + sqlstate={} message=\"{}\" outcome={:?}", + address.username, + address.pool_name, + param_name, + msg.code, + msg.message, + outcome, + ); + crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL + .with_label_values(&[ + &address.pool_name, + ¶m_name, + &msg.code, + ]) + .inc(); + if matches!( + outcome, + crate::server::quarantine::RecordOutcome::JustQuarantined + ) { + crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED + .with_label_values(&[&address.pool_name, ¶m_name]) + .set(1); + } + } else { + info!( + "[{}@{}] backend startup rejected by an unrelated PG \ + startup parameter \"{}\" (pg_doorman did not send this \ + key); sqlstate={} message=\"{}\"", + address.username, + address.pool_name, + param_name, + msg.code, + msg.message, + ); } } } From 15a5f5f4410b81ae4c2fec7c2b247650dd535198 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:05:54 +0300 Subject: [PATCH 15/72] docs(metrics): correct backend_startup_parameter_errors label list The rustdoc on BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL still listed the counter as tracked "per pool/user/parameter" from an earlier draft. The label set is actually pool, parameter, sqlstate (the user was removed to keep series count bounded when dynamic auth_query pools mint many roles against the same broken config). Doc comment now matches the registered label set and notes where the username can be recovered for incident triage. --- src/web/metrics/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index da6b9fe45..45e30f5ab 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -459,10 +459,13 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// Counter for every backend startup attempt that PostgreSQL rejected /// because of an operator-supplied `startup_parameters` entry. Tracked -/// per pool/user/parameter so dashboards can spot a single bad knob in a +/// per pool/parameter so dashboards can spot a single bad knob in a /// large fleet, and split by `sqlstate` (`22023` invalid_value, `42704` /// undefined_object, `42501` insufficient_privilege) so the alerting -/// rule can distinguish a typo from a permission denial. Increments +/// rule can distinguish a typo from a permission denial. The failing +/// username is on the corresponding warn log line; it is intentionally +/// left out of the label set so dynamic auth_query pools cannot blow up +/// the series count when many roles share a broken config. Increments /// once per failed StartupMessage, regardless of whether this rejection /// happens to be the one that crosses the quarantine threshold. pub(crate) static BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL: Lazy = Lazy::new(|| { From cb015ee5ce29438f13d95a33c58560c4065eb470 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:07:45 +0300 Subject: [PATCH 16/72] fix(server): reset rejection counter on successful backend startup The quarantine counter incremented per rejection but never reset on success, so the threshold model effectively counted "N rejections ever" rather than "N consecutive rejections" the doc-comment and naming both promised. On a sea of healthy startups, a handful of transient failures spread over weeks could still tip a perfectly working parameter into quarantine. QuarantineState gains record_success(sent_keys); Server::startup calls it on ReadyForQuery for every operator-supplied key the spawn just accepted. The reset only touches keys that are not currently quarantined: TTL remains the sole release path for already-quarantined keys, because a quarantined key was by definition not in the sent map and so a later success says nothing about its underlying problem. --- src/server/quarantine.rs | 68 ++++++++++++++++++++++++++++++++++++ src/server/server_backend.rs | 6 ++++ 2 files changed, 74 insertions(+) diff --git a/src/server/quarantine.rs b/src/server/quarantine.rs index 8fd0d69d8..af6b14806 100644 --- a/src/server/quarantine.rs +++ b/src/server/quarantine.rs @@ -123,6 +123,30 @@ impl QuarantineState { }) .collect() } + + /// Reset the partial-rejection counters for keys that pg_doorman just + /// successfully sent in a backend StartupMessage (the backend reached + /// `ReadyForQuery`). This keeps the threshold model honest: only N + /// *consecutive* rejections of the same key arm quarantine, not N + /// rejections spread across a sea of healthy startups. + /// + /// Keys currently quarantined (`quarantined_until = Some(_)`) are not + /// touched; their TTL is the only release path. By definition such keys + /// were not in the sent map at all, so a successful startup says nothing + /// about whether the underlying problem with them was fixed. + pub fn record_success(&self, sent_keys: &std::collections::BTreeMap) { + if sent_keys.is_empty() { + return; + } + let mut entries = self.entries.lock().expect("quarantine mutex"); + for k in sent_keys.keys() { + if let Some(entry) = entries.get_mut(k) { + if entry.quarantined_until.is_none() { + entry.reject_count = 0; + } + } + } + } } #[cfg(test)] @@ -213,4 +237,48 @@ mod tests { RecordOutcome::JustQuarantined ); } + + #[test] + fn record_success_resets_counter_for_non_quarantined_key() { + let q = QuarantineState::new(3, Duration::from_secs(60)); + // counter = 1, not yet quarantined. + let _ = q.record_rejection("a", "42704"); + let sent: BTreeMap = + [("a".to_string(), "x".to_string())].into_iter().collect(); + q.record_success(&sent); + // Next rejection counts from 1 again, not 2 (counter was reset). + assert_eq!( + q.record_rejection("a", "42704"), + RecordOutcome::Counting { reject_count: 1 } + ); + } + + #[test] + fn record_success_preserves_quarantine_state() { + let q = QuarantineState::new(1, Duration::from_secs(60)); + // threshold=1 -> already quarantined. + let _ = q.record_rejection("a", "42704"); + let sent: BTreeMap = + [("a".to_string(), "x".to_string())].into_iter().collect(); + // Success on the same key (e.g., somebody else removed the bad value). + // record_success must not touch the quarantine deadline because the + // key was never actually sent in this successful startup. + q.record_success(&sent); + assert_eq!(q.snapshot_quarantined(), vec!["a".to_string()]); + } + + #[test] + fn record_success_ignores_unknown_keys() { + let q = QuarantineState::new(3, Duration::from_secs(60)); + let _ = q.record_rejection("known", "42704"); + let sent: BTreeMap = [("never_seen".to_string(), "x".to_string())] + .into_iter() + .collect(); + q.record_success(&sent); + // "known" counter untouched: next rejection counts to 2. + assert_eq!( + q.record_rejection("known", "42704"), + RecordOutcome::Counting { reject_count: 2 } + ); + } } diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index 55c28a5f2..4044328fe 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -1073,6 +1073,12 @@ impl Server { phase_started.elapsed().as_secs_f64(), ); + // PG accepted every operator-supplied parameter we sent. + // Reset partial-rejection counters for those keys so that + // the threshold model stays "N consecutive rejections", + // not "N rejections ever". + quarantine.record_success(&startup_parameters_sent); + let server = Server { address: address.to_owned(), stream: BufStream::new(stream), From 3647c6454a09c7e037e2fdbd66f52e442f05fed3 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:09:31 +0300 Subject: [PATCH 17/72] docs(config): describe shipped startup_parameters behaviour fields.yaml and the regenerated reference configs still promised that wire injection and the general / pool / auth_query cascade would arrive in a later commit. Both have shipped: pg_doorman puts the operator parameters into the StartupMessage on every backend spawn and resolves the cascade per call. An operator reading the auto-generated reference would have expected validation-only behaviour and been surprised when the new GUC actually showed up at PG. Doc strings now describe the shipped behaviour: cascade order, that values become pg_settings.reset_val and survive RESET ALL / DISCARD ALL, dedicated-mode auth_query exclusion, the post-cascade size check against MAX_STARTUP_PACKET_LENGTH, the quarantine path, and pointers to SHOW POOLS and the Prometheus counters for observability. --- pg_doorman.toml | 23 +++++++------- pg_doorman.yaml | 23 +++++++------- src/app/generate/fields.yaml | 59 +++++++++++++++++++----------------- 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/pg_doorman.toml b/pg_doorman.toml index f10103882..d90f2ee7e 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -422,11 +422,12 @@ startup_parameter_quarantine_threshold = 3 # Default: 300000 startup_parameter_quarantine_ttl = 300000 -# Operator-supplied PostgreSQL configuration parameters. -# Reserved keys (user, database, replication, options, _pq_.*) -# and the StartupMessage budget are validated at config load. -# Wire injection ships in a later commit on the -# feat/startup-parameters branch. +# Baseline of PostgreSQL configuration parameters that pg_doorman +# injects into every backend StartupMessage. Each pool can override +# per key, and an auth_query in passthrough mode can override per +# user. Reserved keys (user, database, replication, options, +# _pq_.*) are rejected at config load, as is anything that pushes +# the cascade past PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters = { plan_cache_mode = "force_custom_plan", work_mem = "64MB" } @@ -626,12 +627,12 @@ cleanup_server_connections = true # Default: false log_client_parameter_status_changes = false -# Operator-supplied PostgreSQL configuration parameters for this -# pool. Reserved keys (user, database, replication, options, -# _pq_.*) and the StartupMessage budget are validated at config -# load. Per-pool overlay; the eventual cascade -# (general → pool → auth_query) is not yet active. Wire injection -# ships in a later commit on the feat/startup-parameters branch. +# Per-pool overrides for PostgreSQL configuration parameters in +# backend StartupMessage. Wins over general.startup_parameters +# per key; auth_query in passthrough mode wins over this. +# Validated for reserved keys and size at config load; the full +# merged cascade is re-checked at every backend spawn against +# PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters = { plan_cache_mode = "force_custom_plan" } diff --git a/pg_doorman.yaml b/pg_doorman.yaml index f1630bb97..b76f7d45f 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -462,11 +462,12 @@ general: # Default: 300000 startup_parameter_quarantine_ttl: 300000 - # Operator-supplied PostgreSQL configuration parameters. - # Reserved keys (user, database, replication, options, _pq_.*) - # and the StartupMessage budget are validated at config load. - # Wire injection ships in a later commit on the - # feat/startup-parameters branch. + # Baseline of PostgreSQL configuration parameters that pg_doorman + # injects into every backend StartupMessage. Each pool can override + # per key, and an auth_query in passthrough mode can override per + # user. Reserved keys (user, database, replication, options, + # _pq_.*) are rejected at config load, as is anything that pushes + # the cascade past PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters: @@ -672,12 +673,12 @@ pools: # Default: false log_client_parameter_status_changes: false - # Operator-supplied PostgreSQL configuration parameters for this - # pool. Reserved keys (user, database, replication, options, - # _pq_.*) and the StartupMessage budget are validated at config - # load. Per-pool overlay; the eventual cascade - # (general → pool → auth_query) is not yet active. Wire injection - # ships in a later commit on the feat/startup-parameters branch. + # Per-pool overrides for PostgreSQL configuration parameters in + # backend StartupMessage. Wins over general.startup_parameters + # per key; auth_query in passthrough mode wins over this. + # Validated for reserved keys and size at config load; the full + # merged cascade is re-checked at every backend spawn against + # PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters: diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index 415f87c2b..f53f18f44 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -1185,25 +1185,30 @@ fields: startup_parameters: config: en: | - Operator-supplied PostgreSQL configuration parameters. - Reserved keys (user, database, replication, options, _pq_.*) - and the StartupMessage budget are validated at config load. - Wire injection ships in a later commit on the - feat/startup-parameters branch. + Baseline of PostgreSQL configuration parameters that pg_doorman + injects into every backend StartupMessage. Each pool can override + per key, and an auth_query in passthrough mode can override per + user. Reserved keys (user, database, replication, options, + _pq_.*) are rejected at config load, as is anything that pushes + the cascade past PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } ru: | - Конфигурационные параметры PostgreSQL, заданные оператором. - Зарезервированные ключи (user, database, replication, options, - _pq_.*) и бюджет StartupMessage проверяются на этапе загрузки - конфига. Передача параметров в backend появится в одном из - следующих коммитов ветки feat/startup-parameters. + Базовый набор параметров PostgreSQL, которые pg_doorman + передаёт в StartupMessage каждого открываемого бэкенда. Любой + пул может перекрыть значение по ключу, а auth_query в режиме + passthrough — на уровне пользователя. Зарезервированные ключи + (user, database, replication, options, _pq_.*) и слишком + большой суммарный набор (PG limit MAX_STARTUP_PACKET_LENGTH = + 10000 байт) отклоняются на этапе загрузки конфига. Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } doc: | - A map of PostgreSQL configuration parameter names to string values, intended as session defaults for every backend pg_doorman opens for this pool's clients. + Map of PostgreSQL configuration parameter names to string values. pg_doorman writes them into the `StartupMessage` of every backend it opens, which means they become `pg_settings.reset_val` for that session and survive any `RESET ALL` / `DISCARD ALL` the client issues. - Validation enforced at config load: reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`) are refused; keys must match PG GUC naming (`^[A-Za-z_][A-Za-z0-9_.]*$`); values must not contain null bytes; the serialised map must fit within the operator budget carved out of PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) `StartupMessage` cap. + Cascade of overrides (more specific wins per key): `general.startup_parameters` is the baseline; `pools..startup_parameters` overrides per pool; an `auth_query` row in passthrough mode can carry a `startup_parameters` JSON column that overrides per user. Dedicated-mode `auth_query` pools intentionally ignore the per-user column because one shared backend serves multiple roles. - Wire-level injection into the backend `StartupMessage` and the `general` → `pool` → `auth_query` cascade resolution ship in subsequent commits on the `feat/startup-parameters` branch. The current commit only validates this map at config load. + Validation enforced at config load: reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`) are refused; keys must match PG GUC naming (`^[A-Za-z_][A-Za-z0-9_.]*$`); values must not contain null bytes; per-level size must fit the operator budget. The full merged cascade is re-checked at every backend spawn against PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) cap; when the merge would not fit, pg_doorman drops all operator-supplied keys for that spawn and logs. + + If PG keeps rejecting a particular operator-supplied key at backend startup (sqlstate `22023` / `42704` / `42501`), pg_doorman quarantines that key for the pool. See `startup_parameter_quarantine_threshold` and `startup_parameter_quarantine_ttl` for tuning, `SHOW POOLS`' `quarantined_params` column for visibility, and the `pg_doorman_backend_startup_parameter_errors_total` / `pg_doorman_backend_startup_parameter_quarantined` metrics for alerting. default: "{} (empty)" pool: @@ -1508,26 +1513,26 @@ fields: startup_parameters: config: en: | - Operator-supplied PostgreSQL configuration parameters for this - pool. Reserved keys (user, database, replication, options, - _pq_.*) and the StartupMessage budget are validated at config - load. Per-pool overlay; the eventual cascade - (general → pool → auth_query) is not yet active. Wire injection - ships in a later commit on the feat/startup-parameters branch. + Per-pool overrides for PostgreSQL configuration parameters in + backend StartupMessage. Wins over general.startup_parameters + per key; auth_query in passthrough mode wins over this. + Validated for reserved keys and size at config load; the full + merged cascade is re-checked at every backend spawn against + PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } ru: | - Конфигурационные параметры PostgreSQL для этого пула, заданные - оператором. Зарезервированные ключи (user, database, - replication, options, _pq_.*) и бюджет StartupMessage - проверяются на этапе загрузки конфига. Перекрытие на уровне - пула; каскад general → pool → auth_query пока не активен. - Передача параметров в backend появится в одном из следующих - коммитов ветки feat/startup-parameters. + Перекрытия параметров PostgreSQL для этого пула, передаваемые + в StartupMessage бэкенда. Имеют приоритет над + general.startup_parameters по ключу; auth_query в режиме + passthrough перекрывает per-pool. На этапе загрузки конфига + проверяются зарезервированные ключи и размер; общий каскад + re-checking на каждом спавне бэкенда против PG-лимита + MAX_STARTUP_PACKET_LENGTH (10000 байт). Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } doc: | Per-pool map of PostgreSQL configuration parameters. Validation rules match those documented for [`general.startup_parameters`](#startup-parameters): reserved keys, GUC naming, null bytes, and the operator budget within PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) `StartupMessage` cap. - Per-pool overlay semantics — the cascade `general` → `pool` → `auth_query` that selects the effective value for each key — and wire injection into the backend `StartupMessage` ship in subsequent commits on the `feat/startup-parameters` branch. The current commit only validates this map at config load. + In the cascade `general` → `pool` → `auth_query`, this layer overrides general per key, and an auth_query passthrough entry overrides this. Dedicated-mode `auth_query` pools ignore the per-user column on purpose. See [`general.startup_parameters`](#startup-parameters) for the full behaviour description, quarantine knobs, and observability. default: "{} (empty)" user: From 7b2ae91559ba77ea4dae6af9bd788d7abcb8badc Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:12:25 +0300 Subject: [PATCH 18/72] fix(quarantine): hot-reload threshold and TTL knobs on config reload QuarantineState used to read its threshold and TTL once in ServerPool::new and never again. A SIGHUP that only changed general.startup_parameter_quarantine_threshold or _ttl had no effect on any pool that was kept across the reload, because the pool hash does not include general-level knobs and so the pool was reused as-is. Operators ended up running with the previous values until pg_doorman restarted or every pool was independently recycled, which made the runbook for tuning the quarantine misleading. QuarantineState now holds the two knobs as atomics. ServerPool exposes update_quarantine_knobs(threshold, ttl); the reload path pushes the fresh general-level values into every kept pool before declaring the pool unchanged. record_rejection reads the current values on every call. general.startup_parameters itself is read live from config_arc() on every backend spawn, so a SIGHUP that only changes those values takes effect on the next backend spawn for every pool without rebuild. Old idle backends keep their previous parameters until the regular pool recycle (server_lifetime / idle_timeout / RECONNECT) replaces them; this trade-off is intentional and documented in the operator notes. --- src/pool/inner.rs | 9 +++++++++ src/pool/mod.rs | 12 ++++++++++++ src/pool/server_pool.rs | 10 ++++++++++ src/server/quarantine.rs | 37 ++++++++++++++++++++++++++++++------- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/pool/inner.rs b/src/pool/inner.rs index 348a939cc..73cbe7d5a 100644 --- a/src/pool/inner.rs +++ b/src/pool/inner.rs @@ -1552,6 +1552,15 @@ impl Pool { self.inner.server_pool.quarantined_startup_parameters() } + /// Forward a live update of quarantine threshold/TTL to the shared + /// `QuarantineState`. The pool hash does not include these + /// general-level knobs, so reload calls this on every pool it kept. + pub fn update_quarantine_knobs(&self, threshold: u32, ttl: std::time::Duration) { + self.inner + .server_pool + .update_quarantine_knobs(threshold, ttl); + } + /// Bumps reconnect epoch and drains all idle connections. /// Returns the new epoch value. pub fn reconnect(&self) -> u32 { diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 871a24be5..d8b5f39a7 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -335,6 +335,18 @@ impl ConnectionPool { && pool.address.server_tls.as_ref() == server_tls_config.as_ref() { info!("[{}@{}] config unchanged", user.username, pool_name); + // Quarantine threshold and TTL live in `general` and + // therefore do not influence the pool hash. A SIGHUP + // that only retunes those knobs would otherwise leave + // every kept pool running with stale values, so push + // the new numbers into the shared QuarantineState + // here while the pool is still being reused. + pool.database.update_quarantine_knobs( + config.general.startup_parameter_quarantine_threshold, + std::time::Duration::from_millis( + config.general.startup_parameter_quarantine_ttl, + ), + ); new_pools.insert(identifier.clone(), pool.clone()); continue; } diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 623f61ce1..26a74a4ef 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -369,6 +369,16 @@ impl ServerPool { self.startup_parameter_quarantine.snapshot_quarantined() } + /// Live-update the quarantine threshold and TTL so a SIGHUP that only + /// touches `general.startup_parameter_quarantine_*` takes effect on the + /// reused pool. The pool hash does not include these general-level + /// knobs, so without this hook a reload would silently leave the pool + /// running with the previous values. + pub fn update_quarantine_knobs(&self, threshold: u32, ttl: std::time::Duration) { + self.startup_parameter_quarantine + .update_knobs(threshold, ttl); + } + /// Resolve the operator-supplied startup_parameters map that this pool /// will hand to `Server::startup` for one backend spawn. The cascade is /// `general` -> pool -> (optional) auth_query per-user entry, with the diff --git a/src/server/quarantine.rs b/src/server/quarantine.rs index af6b14806..64b63a421 100644 --- a/src/server/quarantine.rs +++ b/src/server/quarantine.rs @@ -14,6 +14,7 @@ //! is meaningless evidence). use std::collections::HashMap; +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use std::sync::Mutex; use std::time::{Duration, Instant}; @@ -26,8 +27,12 @@ pub struct QuarantineEntry { #[derive(Debug)] pub struct QuarantineState { - threshold: u32, - ttl: Duration, + // Hot-reloadable: a SIGHUP that changes + // general.startup_parameter_quarantine_{threshold,ttl} takes effect on + // the next call without recreating the pool. Reads use Relaxed since the + // values are advisory observability knobs, not safety invariants. + threshold: AtomicU32, + ttl_ms: AtomicU64, entries: Mutex>, } @@ -44,16 +49,34 @@ pub enum RecordOutcome { impl QuarantineState { pub fn new(threshold: u32, ttl: Duration) -> Self { Self { - threshold: threshold.max(1), - ttl, + threshold: AtomicU32::new(threshold.max(1)), + ttl_ms: AtomicU64::new(ttl.as_millis() as u64), entries: Mutex::new(HashMap::new()), } } + /// Update the threshold/TTL knobs without recreating the pool. Called + /// from the SIGHUP reload path when only the general-level knobs change + /// and pool hash would not otherwise force a rebuild. + pub fn update_knobs(&self, threshold: u32, ttl: Duration) { + self.threshold.store(threshold.max(1), Ordering::Relaxed); + self.ttl_ms.store(ttl.as_millis() as u64, Ordering::Relaxed); + } + + fn threshold(&self) -> u32 { + self.threshold.load(Ordering::Relaxed) + } + + fn ttl(&self) -> Duration { + Duration::from_millis(self.ttl_ms.load(Ordering::Relaxed)) + } + /// Record a backend-startup rejection for `key`. Returns the resulting /// outcome for caller-side logging and metrics. pub fn record_rejection(&self, key: &str, sqlstate: &str) -> RecordOutcome { let now = Instant::now(); + let ttl = self.ttl(); + let threshold = self.threshold(); let mut entries = self.entries.lock().expect("quarantine mutex"); let entry = entries .entry(key.to_owned()) @@ -64,12 +87,12 @@ impl QuarantineState { }); entry.last_sqlstate = sqlstate.to_owned(); if entry.quarantined_until.map(|d| d > now).unwrap_or(false) { - entry.quarantined_until = Some(now + self.ttl); + entry.quarantined_until = Some(now + ttl); return RecordOutcome::AlreadyQuarantined; } entry.reject_count = entry.reject_count.saturating_add(1); - if entry.reject_count >= self.threshold { - entry.quarantined_until = Some(now + self.ttl); + if entry.reject_count >= threshold { + entry.quarantined_until = Some(now + ttl); return RecordOutcome::JustQuarantined; } RecordOutcome::Counting { From be3bcb48a5aa179037160a47422619d9c75721a4 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:13:45 +0300 Subject: [PATCH 19/72] fix(quarantine): clear gauge on TTL expiry without waiting for new spawns Before this change the BACKEND_STARTUP_PARAMETER_QUARANTINED gauge was only cleared inside Server::startup, when the next backend spawn ran filter_active_keys() and observed the expired entry. A pool that went idle right after quarantining a parameter (no further client traffic, nothing else triggers a spawn) left the gauge stuck at 1 for hours after the TTL was supposed to release the key, which would keep dashboards red and pagers loud long after the underlying issue had resolved. QuarantineState gains reconcile_expired(), and ServerPool wires it into the SHOW POOLS / metrics-collection path so every snapshot pass clears stale series. The reconciliation is a no-op for healthy pools and a constant-time operation per quarantined key, so calling it on every snapshot is cheap. --- src/pool/inner.rs | 8 ++++++++ src/pool/server_pool.rs | 13 +++++++++++++ src/server/quarantine.rs | 27 +++++++++++++++++++++++++++ src/stats/pool.rs | 6 ++++++ 4 files changed, 54 insertions(+) diff --git a/src/pool/inner.rs b/src/pool/inner.rs index 73cbe7d5a..9f9e53c68 100644 --- a/src/pool/inner.rs +++ b/src/pool/inner.rs @@ -1561,6 +1561,14 @@ impl Pool { .update_quarantine_knobs(threshold, ttl); } + /// Drop expired quarantine bookkeeping and clear the matching gauges so + /// that an idle pool does not strand the Prometheus + /// `pg_doorman_backend_startup_parameter_quarantined` series at 1 long + /// after TTL elapsed. + pub fn reconcile_quarantine_gauges(&self) { + self.inner.server_pool.reconcile_quarantine_gauges(); + } + /// Bumps reconnect epoch and drains all idle connections. /// Returns the new epoch value. pub fn reconnect(&self) -> u32 { diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 26a74a4ef..0243aaa6b 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -379,6 +379,19 @@ impl ServerPool { .update_knobs(threshold, ttl); } + /// Drop bookkeeping for quarantined keys whose TTL has elapsed and clear + /// the matching Prometheus gauge series. Called from the SHOW POOLS / + /// metrics-collection path so an idle pool with no backend churn does + /// not leave a stale `pg_doorman_backend_startup_parameter_quarantined` + /// at 1 long after the underlying TTL expired. + pub fn reconcile_quarantine_gauges(&self) { + for key in self.startup_parameter_quarantine.reconcile_expired() { + crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED + .with_label_values(&[&self.address.pool_name, &key]) + .set(0); + } + } + /// Resolve the operator-supplied startup_parameters map that this pool /// will hand to `Server::startup` for one backend spawn. The cascade is /// `general` -> pool -> (optional) auth_query per-user entry, with the diff --git a/src/server/quarantine.rs b/src/server/quarantine.rs index 64b63a421..7d032f128 100644 --- a/src/server/quarantine.rs +++ b/src/server/quarantine.rs @@ -147,6 +147,33 @@ impl QuarantineState { .collect() } + /// Drop bookkeeping for entries whose quarantine TTL has expired and + /// return their names. Used by the SHOW POOLS / metrics-collection path + /// so the Prometheus quarantine gauge clears for an idle pool that no + /// longer sees backend spawns; without this hook the gauge would stay + /// stuck at 1 until the next backend creation triggered + /// `filter_active_keys`. + pub fn reconcile_expired(&self) -> Vec { + let now = Instant::now(); + let mut entries = self.entries.lock().expect("quarantine mutex"); + let mut released: Vec = Vec::new(); + let mut to_drop: Vec = Vec::new(); + for (key, entry) in entries.iter_mut() { + if let Some(deadline) = entry.quarantined_until { + if deadline <= now { + entry.quarantined_until = None; + entry.reject_count = 0; + to_drop.push(key.clone()); + released.push(key.clone()); + } + } + } + for k in &to_drop { + entries.remove(k); + } + released + } + /// Reset the partial-rejection counters for keys that pg_doorman just /// successfully sent in a backend StartupMessage (the backend reached /// `ReadyForQuery`). This keeps the threshold model honest: only N diff --git a/src/stats/pool.rs b/src/stats/pool.rs index c72afa5bd..8bb2d3968 100644 --- a/src/stats/pool.rs +++ b/src/stats/pool.rs @@ -630,6 +630,12 @@ impl PoolStats { current.prepared_statements_bytes = cache.memory_usage() as u64; } + // Reconcile expired quarantine bookkeeping and clear the + // corresponding Prometheus gauges before snapshotting. Pools + // that never see another backend spawn would otherwise leave + // stale series at 1 indefinitely, since the regular release + // path only fires inside `Server::startup`. + pool.database.reconcile_quarantine_gauges(); // Snapshot the currently-parked startup_parameters so SHOW // POOLS reflects the same set of parameters the Prometheus // gauge already exposes through the metrics endpoint. From 541715ab4e0a06af831c8842b3cfb81d8076052b Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:15:03 +0300 Subject: [PATCH 20/72] fix(auth_query): point operator at ::text cast on column-type mismatch When the SELECT returns a native json or jsonb column named startup_parameters, tokio_postgres refuses to decode it as text and pg_doorman silently dropped the per-user parameters with a warn line that named the decode error but not the fix. Operators writing jsonb_build_object(...) AS startup_parameters would authenticate successfully, get an empty per-user map, and then chase the missing parameter through code paths that had nothing to do with the actual column type. The warn line now identifies the actual PostgreSQL type pg_doorman saw and recommends the smallest possible change to the SQL (`...::text AS startup_parameters`). The runtime contract stays the same: the column is read as text; conversion is the operator's responsibility. --- src/auth/auth_query.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index 03b3b54ea..c4e896644 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -399,19 +399,23 @@ impl AuthQueryExecutor { username: &str, pool_name: &str, ) -> std::collections::HashMap { - let has_column = row + let column = row .columns() .iter() - .any(|c| c.name() == "startup_parameters"); - if !has_column { + .find(|c| c.name() == "startup_parameters"); + let Some(column) = column else { return std::collections::HashMap::new(); - } + }; let raw: Option = match row.try_get::<_, Option>("startup_parameters") { Ok(v) => v, Err(e) => { warn!( - "[{username}@{pool_name}] auth_query startup_parameters: column present but \ - not readable as text: {e}; parameters ignored" + "[{username}@{pool_name}] auth_query startup_parameters column has type \ + `{ty}` but pg_doorman reads it as `text`: {e}. If the SELECT returns \ + json or jsonb, add `::text` (for example: \ + `jsonb_build_object(...)::text AS startup_parameters`); per-user \ + parameters are ignored for this row.", + ty = column.type_().name() ); return std::collections::HashMap::new(); } From 3c4d06f09b519b0d69fc2c1e527565eca688c92a Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:25:14 +0300 Subject: [PATCH 21/72] docs: operator tutorial for PostgreSQL startup_parameters (EN + RU) Adds a tutorial covering the three-level cascade (general, per-pool, auth_query passthrough column), the RESET ALL invariant, validation at load and at each backend spawn, the quarantine with consecutive semantics and TTL-only release, and the observability surface (SHOW POOLS column plus the errors counter and quarantined gauge). The reference pages are already generated from fields.yaml; this is a curated walkthrough operators can follow before reaching for the reference. Both language books get the tutorial, linked from the Pooling section of SUMMARY.md next to the other per-pool behaviour articles. --- documentation/en/src/SUMMARY.md | 1 + .../en/src/tutorials/startup-parameters.md | 186 ++++++++++++++++ documentation/ru/src/SUMMARY.md | 1 + .../ru/src/tutorials/startup-parameters.md | 200 ++++++++++++++++++ 4 files changed, 388 insertions(+) create mode 100644 documentation/en/src/tutorials/startup-parameters.md create mode 100644 documentation/ru/src/tutorials/startup-parameters.md diff --git a/documentation/en/src/SUMMARY.md b/documentation/en/src/SUMMARY.md index 4a8d39e26..ac7e2ea36 100644 --- a/documentation/en/src/SUMMARY.md +++ b/documentation/en/src/SUMMARY.md @@ -30,6 +30,7 @@ - [Pool Modes](concepts/pool-modes.md) - [Pool Coordinator](concepts/pool-coordinator.md) - [Anonymous Parse Caching](tutorials/prepared-statements.md) +- [PostgreSQL startup parameters](tutorials/startup-parameters.md) - [Pool Pressure (advanced)](tutorials/pool-pressure.md) # High Availability diff --git a/documentation/en/src/tutorials/startup-parameters.md b/documentation/en/src/tutorials/startup-parameters.md new file mode 100644 index 000000000..8720315b9 --- /dev/null +++ b/documentation/en/src/tutorials/startup-parameters.md @@ -0,0 +1,186 @@ +# PostgreSQL startup parameters + +Some operators need a few PostgreSQL configuration parameters to apply +to every backend pg_doorman opens, without touching `postgresql.conf`, +`ALTER ROLE`, or `ALTER DATABASE`. Three cases recur in practice: + +- A hot OLTP pool gets bitten by a sticky generic plan after the + `plan_cache_mode = auto` heuristic flips. Switching the whole role + to `force_custom_plan` would affect every workload using that role; + scoping the change to one pool is what you want. +- An application that does not set its own `statement_timeout` or + `idle_in_transaction_session_timeout` and cannot be patched fast + enough. The DBA needs a server-side default that survives the + application's own session resets. +- A single application that should announce a stable + `application_name` regardless of what the connecting driver + negotiates, so `pg_stat_activity` and audit logs stay legible. + +`startup_parameters` lets pg_doorman do this from its own config. + +## Configuration + +The cascade has three levels; the more specific level wins per key: + +```toml +[general.startup_parameters] +statement_timeout = "5s" + +[pools.checkout.startup_parameters] +plan_cache_mode = "force_custom_plan" +work_mem = "64MB" +``` + +After `SIGHUP` (or `RELOAD` on the admin console) every new backend +for the `checkout` pool starts with `statement_timeout = 5s`, +`plan_cache_mode = force_custom_plan`, and `work_mem = 64MB`. Other +pools keep `statement_timeout = 5s` from `general` and the PG default +for the rest. Already-open backends are not affected; the change takes +hold as the pool rotates connections. + +When `auth_query` runs in passthrough mode (no `server_user`), the +lookup SQL may return an optional `startup_parameters` text column +holding a JSON object. Values from that column override both +`general` and per-pool settings for that user only: + +```sql +SELECT + rolpassword AS passwd, + CASE rolname + WHEN 'vip' THEN '{"work_mem":"256MB"}'::text + ELSE NULL::text + END AS startup_parameters +FROM pg_authid +WHERE rolname = $1; +``` + +The column must serialise as `text`. If the SQL returns `json` or +`jsonb`, add an explicit `::text` cast. pg_doorman reads the column +as `text` and logs a one-time warning per user when the type does +not match. + +Dedicated `auth_query` mode (`server_user` set) ignores the per-user +column and logs once per (pool, username): one shared backend serves +many users, so a per-user override cannot apply. + +## What pg_doorman does with the values + +The merged map is written into the PostgreSQL `StartupMessage` of +every backend pg_doorman opens. PG records each entry as the session +default for that setting (`pg_settings.reset_val` and +`pg_settings.source = 'session'`), so client-side `RESET ALL` and +`DISCARD ALL` restore the operator value rather than discarding it. +Operators get a stable session default without editing +`postgresql.conf` or running `ALTER ROLE`. + +The values can be observed from the client: + +```text +checkout=> SHOW plan_cache_mode; + plan_cache_mode +------------------- + force_custom_plan + +checkout=> SET plan_cache_mode = 'auto'; RESET ALL; SHOW plan_cache_mode; + plan_cache_mode +------------------- + force_custom_plan +``` + +## Validation + +At config load: + +- Keys must match PG GUC naming `^[A-Za-z_][A-Za-z0-9_.]*$`. Namespaced + names like `auto_explain.log_min_duration` are accepted; arbitrary + punctuation is not. +- Reserved keys (`user`, `database`, `replication`, `options`, and + anything starting with `_pq_.`) are refused. pg_doorman manages + them itself or PG treats them specially in the StartupMessage. +- Values must not contain null bytes. +- Each level (general or per-pool) must fit within the operator + budget: `MAX_STARTUP_PACKET_LENGTH` (10 000 bytes) minus 512 bytes + reserved for pg_doorman-managed keys. + +At every backend spawn pg_doorman re-checks the merged cascade +against the same cap. Two levels that fit individually can together +push past it once `auth_query` adds a third layer; when that happens +pg_doorman drops every operator-supplied key for that one spawn, +logs the byte counts, and lets the backend connect with PG's own +defaults rather than failing every connection attempt. + +## Quarantine + +PG can reject an operator-supplied parameter at backend startup with +sqlstate `22023` (invalid value), `42704` (undefined object: typo +in the parameter name), or `42501` (insufficient privilege: the +parameter requires a higher role than the backend has). pg_doorman +counts consecutive rejections per `(pool, parameter)`; once the +count reaches `general.startup_parameter_quarantine_threshold` +(default 3), pg_doorman stops sending that key for +`general.startup_parameter_quarantine_ttl` (default 5 minutes). + +A successful backend startup resets the counter for every key it +just sent. Three transient rejections do not trigger quarantine if a +successful spawn lands between them; "consecutive" really means +consecutive. + +Both knobs are hot-reloaded on `SIGHUP`. Releasing a quarantined +parameter is **TTL-only**: pg_doorman cannot tell whether the +operator has fixed the underlying problem until the TTL expires and +the next backend spawn tries the parameter again. + +## Observability + +`SHOW POOLS` carries a `quarantined_params` column listing the +currently parked parameters per pool, comma-separated: + +```text + database | user | ... | quarantined_params +----------+-------+-----+-------------------- + checkout | shop | ... | work_mem,search_path + reports | shop | ... | +``` + +The same state is on the Prometheus surface: + +- `pg_doorman_backend_startup_parameter_errors_total{pool, parameter, sqlstate}` + counts every rejection from PG. Increments once per failed + StartupMessage; the failing username is on the corresponding warn + log line so dynamic `auth_query` pools cannot blow up the series + count. +- `pg_doorman_backend_startup_parameter_quarantined{pool, parameter}` + is `1` while a parameter is parked and flips back to `0` exactly + once when its TTL expires. The gauge does not clear on its own + even after the operator fixes the underlying issue; the next + spawn after TTL expiry is what re-arms it. + +A reasonable starting alert is "any non-zero quarantine gauge for +longer than the TTL". If the gauge is set for `2 × ttl`, the +operator-supplied value is still wrong after one retry window and +deserves a look. + +## When not to use this + +- The application already sets the parameter on every connection. + Putting the same value in `startup_parameters` adds a bookkeeping + surface for no behavioural change. +- Per-transaction tuning (`SET LOCAL`). `startup_parameters` is for + session defaults; transaction-scoped tuning belongs in the + application. +- Anything that needs to depend on which query the application is + running. Startup parameters apply to every transaction on every + backend for the lifetime of that backend; there is no + per-statement variant. + +## Reference + +- [General Settings](../reference/general.md): `startup_parameters`, + `startup_parameter_quarantine_threshold`, + `startup_parameter_quarantine_ttl`. +- [Pool Settings](../reference/pool.md): + `pools..startup_parameters`. +- [auth_query](../authentication/auth-query.md): passthrough vs + dedicated modes, where the `startup_parameters` column is read. +- [Admin Commands](../observability/admin-commands.md): `SHOW POOLS`. +- [Prometheus](../reference/prometheus.md): full metric list. diff --git a/documentation/ru/src/SUMMARY.md b/documentation/ru/src/SUMMARY.md index 292690582..28a274dc4 100644 --- a/documentation/ru/src/SUMMARY.md +++ b/documentation/ru/src/SUMMARY.md @@ -30,6 +30,7 @@ - [Режимы пула](concepts/pool-modes.md) - [Координатор пулов](concepts/pool-coordinator.md) - [Кеш Parse для анонимных prepared statements](tutorials/prepared-statements.md) +- [Startup-параметры PostgreSQL](tutorials/startup-parameters.md) - [Пул под нагрузкой (продвинутое)](tutorials/pool-pressure.md) # Высокая доступность diff --git a/documentation/ru/src/tutorials/startup-parameters.md b/documentation/ru/src/tutorials/startup-parameters.md new file mode 100644 index 000000000..b09a1db60 --- /dev/null +++ b/documentation/ru/src/tutorials/startup-parameters.md @@ -0,0 +1,200 @@ +# Startup-параметры PostgreSQL + +Иногда оператору нужно, чтобы несколько параметров конфигурации +PostgreSQL применялись к каждому backend, который открывает +pg_doorman, без правки `postgresql.conf`, без `ALTER ROLE` и без +`ALTER DATABASE`. На практике повторяются три ситуации: + +- На горячем OLTP-пуле план переключается на generic после того, как + эвристика `plan_cache_mode = auto` решает, что generic дешевле, и + обратно уже не возвращается. Перевод всей роли на + `force_custom_plan` затронет любую другую нагрузку под этой ролью; + изменение хочется ограничить одним пулом. +- Приложение не выставляет `statement_timeout` или + `idle_in_transaction_session_timeout` самостоятельно, а быстро + его доработать нет возможности. DBA нужен серверный дефолт, + который переживает собственные `RESET ALL` приложения. +- Одно приложение должно стабильно сообщать конкретный + `application_name` независимо от того, что выставит драйвер: чтобы + `pg_stat_activity` и журналы аудита оставались читаемыми. + +Для этих случаев в pg_doorman есть `startup_parameters`: карта +параметров PostgreSQL (GUC), которая прокидывается в каждое +серверное соединение пула. + +## Конфигурация + +Каскад из трёх уровней: значение более узкого уровня выигрывает +по ключу. + +```toml +[general.startup_parameters] +statement_timeout = "5s" + +[pools.checkout.startup_parameters] +plan_cache_mode = "force_custom_plan" +work_mem = "64MB" +``` + +После `SIGHUP` (или `RELOAD` через консоль администратора) каждый +новый backend пула `checkout` стартует со значениями +`statement_timeout = 5s`, `plan_cache_mode = force_custom_plan` и +`work_mem = 64MB`. У других пулов `statement_timeout = 5s` остаётся +из `general`, а остальное берётся из дефолтов PostgreSQL. Уже +открытые backend'ы не меняются: изменения вступают в силу по мере +ротации соединений. + +В режиме passthrough `auth_query` (когда `server_user` не задан) +запрос аутентификации может вернуть необязательную колонку +`startup_parameters` типа `text`, содержащую JSON-объект. Значения +из этой колонки перекрывают `general` и pool-level настройки +только для конкретного пользователя. + +```sql +SELECT + rolpassword AS passwd, + CASE rolname + WHEN 'vip' THEN '{"work_mem":"256MB"}'::text + ELSE NULL::text + END AS startup_parameters +FROM pg_authid +WHERE rolname = $1; +``` + +Колонка должна возвращаться как `text`. Если SQL отдаёт `json` или +`jsonb`, нужно добавить явный каст `::text`. pg_doorman читает её +именно как `text` и пишет однократное предупреждение для каждого +пользователя, у которого тип не совпал. + +Dedicated-режим `auth_query` (когда `server_user` задан) колонку +игнорирует и пишет одно предупреждение на пару (пул, имя +пользователя): на одном общем backend нельзя одновременно применить +разные значения для разных пользователей. + +## Что pg_doorman делает со значениями + +Слитая карта пишется в `StartupMessage` каждого backend, который +открывает pg_doorman. PostgreSQL запоминает каждое значение как +сессионный дефолт (`pg_settings.reset_val` и +`pg_settings.source = 'session'`), поэтому клиентские `RESET ALL` +и `DISCARD ALL` возвращают именно операторское значение, а не +сбрасывают его. Оператор получает стабильный сессионный дефолт без +правки `postgresql.conf` и без `ALTER ROLE`. + +Значение видно со стороны клиента: + +```text +checkout=> SHOW plan_cache_mode; + plan_cache_mode +------------------- + force_custom_plan + +checkout=> SET plan_cache_mode = 'auto'; RESET ALL; SHOW plan_cache_mode; + plan_cache_mode +------------------- + force_custom_plan +``` + +## Валидация + +Проверки на этапе загрузки конфигурации: + +- Имена ключей должны подходить под маску GUC PostgreSQL: + `^[A-Za-z_][A-Za-z0-9_.]*$`. Составные имена вроде + `auto_explain.log_min_duration` допустимы; произвольные знаки + препинания нет. +- Зарезервированные ключи (`user`, `database`, `replication`, + `options` и всё, что начинается с `_pq_.`) отклоняются. + pg_doorman управляет ими сам, либо PostgreSQL обрабатывает их в + `StartupMessage` особым образом. +- Значения не должны содержать null-байт. +- Каждый уровень (general или pool) обязан укладываться в + операторский бюджет: `MAX_STARTUP_PACKET_LENGTH` (10 000 байт) + минус 512 байт, зарезервированных под служебные ключи pg_doorman. + +При запуске каждого backend pg_doorman заново сверяет уже слитый +каскад с тем же лимитом. Два уровня, каждый из которых помещался +отдельно, могут вместе вылезти за бюджет, когда `auth_query` +добавит третий слой. В таком случае pg_doorman сбрасывает все +операторские ключи для этого запуска, пишет в лог размеры и +открывает backend с дефолтами PostgreSQL, а не проваливает каждую +попытку подключения. + +## Карантин + +PostgreSQL может отклонить операторский параметр на старте backend +с sqlstate `22023` (некорректное значение), `42704` (неизвестный +объект: опечатка в имени параметра) или `42501` (недостаточно +прав: параметр требует роли выше, чем у backend). pg_doorman +считает подряд идущие отказы по паре `(pool, parameter)`. Когда +счётчик достигает `general.startup_parameter_quarantine_threshold` +(по умолчанию 3), pg_doorman перестаёт отправлять этот ключ на +`general.startup_parameter_quarantine_ttl` (по умолчанию 5 минут). + +Успешный запуск backend сбрасывает счётчик для всех ключей, +которые он только что отправил. Три временных отказа не приводят +к карантину, если между ними прошёл хотя бы один удачный запуск: +«подряд» здесь буквально означает подряд. + +Оба значения подхватываются на `SIGHUP`. Выход из карантина +происходит **только по TTL**: pg_doorman не может узнать, починил +ли оператор исходную проблему, пока TTL не истечёт и следующий +backend не попробует параметр заново. + +## Мониторинг + +В выводе `SHOW POOLS` есть колонка `quarantined_params` со списком +параметров, которые сейчас в карантине у пула. Имена разделены +запятой: + +```text + database | user | ... | quarantined_params +----------+-------+-----+-------------------- + checkout | shop | ... | work_mem,search_path + reports | shop | ... | +``` + +То же состояние видно через Prometheus: + +- `pg_doorman_backend_startup_parameter_errors_total{pool, parameter, sqlstate}` + считает каждый отказ от PostgreSQL. Инкрементируется один раз на + каждый отвергнутый `StartupMessage`; имя пользователя осознанно + не входит в набор лейблов, чтобы динамические `auth_query`-пулы + не разнесли количество серий. Имя отказавшего пользователя есть + в соответствующей warn-строке лога. +- `pg_doorman_backend_startup_parameter_quarantined{pool, parameter}` + держит `1`, пока параметр в карантине, и сбрасывается в `0` + ровно один раз, когда истекает TTL. Починка операторского + значения не сбрасывает метрику раньше времени: переоценка + происходит на следующем запуске backend после истечения TTL. + +Разумная отправная точка для алерта: «ненулевое значение +quarantine-метрики дольше TTL». Если метрика держится `2 × ttl`, +после одного окна ретрая операторское значение всё ещё неверно и +заслуживает внимания. + +## Когда это не нужно + +- Приложение само выставляет параметр на каждом подключении. + Дублирование в `startup_parameters` добавляет учётную поверхность + без изменения поведения. +- Тюнинг на одну транзакцию (`SET LOCAL`). `startup_parameters` + задают сессионные дефолты; параметры на уровне транзакции + выставляет приложение. +- Значения, которые должны зависеть от того, какой запрос идёт + прямо сейчас. Startup-параметры применяются ко всем транзакциям + каждого backend на протяжении его жизни; варианта «на один + statement» нет. + +## Справочник + +- [Общие настройки](../reference/general.md): `startup_parameters`, + `startup_parameter_quarantine_threshold`, + `startup_parameter_quarantine_ttl`. +- [Настройки пула](../reference/pool.md): + `pools..startup_parameters`. +- [auth_query](../authentication/auth-query.md): режимы passthrough + и dedicated, чтение колонки `startup_parameters`. +- [Команды администратора](../observability/admin-commands.md): + `SHOW POOLS`. +- [Метрики Prometheus](../reference/prometheus.md): полный список. From 6eb77a3d3b47e1e9a427c22082bfd77e0f75776d Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:28:34 +0300 Subject: [PATCH 22/72] fix(ci): wait past postgres init-restart in docker-smoke The official postgres image starts an init listener, runs initdb scripts, then restarts before opening the final listener. pg_isready returns YES on the init listener, so the next query in the smoke (creating the smoke_user / smoke_db fixture) sometimes lands during the restart window and fails with FATAL: the database system is shutting down. That fail-pattern flaked the dashboard-validation workflow on every push to this branch with no underlying pg_doorman defect. Probe with two consecutive successful psql SELECT 1 runs instead of one pg_isready: if the first success was on the init listener, the second probe one second later catches the restart and the loop keeps waiting. The 90-second ceiling stays the same. --- scripts/docker-smoke.sh | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/docker-smoke.sh b/scripts/docker-smoke.sh index 7842d330e..8aee56165 100755 --- a/scripts/docker-smoke.sh +++ b/scripts/docker-smoke.sh @@ -116,11 +116,26 @@ docker run -d --name "$PG_NAME" --network "$NET_NAME" \ # Wait for postgres to accept connections. 90s ceiling: typical fresh # postgres:17 boot is <5s; the headroom covers cold-cache image pulls # and slow runners (Ubicloud cold VM, congested GHA pool). +# +# The official postgres image cycles through an init listener and then +# restarts before the final listener comes up. `pg_isready` answers YES +# on the init listener too, so a single success is not enough: the very +# next query can hit "FATAL: the database system is shutting down" and +# the smoke fails for a reason that has nothing to do with pg_doorman. +# Require two consecutive successful `psql SELECT 1` runs to ride past +# the restart window. echo "waiting for postgres readiness..." +prev_ok=0 for i in $(seq 1 90); do - if docker exec "$PG_NAME" pg_isready -U "$PG_USER" >/dev/null 2>&1; then - echo "postgres ready after ${i}s" - break + if docker exec -e "PGPASSWORD=$PG_PASSWORD" "$PG_NAME" \ + psql -U "$PG_USER" -d "$PG_DB" -At -c "SELECT 1" >/dev/null 2>&1; then + if [ "$prev_ok" -eq 1 ]; then + echo "postgres ready after ~${i}s" + break + fi + prev_ok=1 + else + prev_ok=0 fi if [ "$i" -eq 90 ]; then echo "::error::postgres did not become ready in 90s" From 5d27039b39af403ef174be40aa779d92c63e6113 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:33:19 +0300 Subject: [PATCH 23/72] release: 3.9.0 (per-pool startup_parameters) Bumps the package version to mark per-pool startup_parameters as the shipped feature of this release. The Changelog summarises the cascade, validation, quarantine, and observability surface introduced on this branch, and points to the new operator tutorial. The comparison page gains a row that contrasts the feature with the closest analogues in other poolers: PgBouncer only ships specific client-encoding/datestyle/timezone parameters per database via the connection string, Odyssey's maintain_params preserves client-side values across rebind but has no operator-side injection, and PgCat documents no equivalent. pg_doorman is the only one in this set that lets an operator put arbitrary GUCs into the backend StartupMessage on a per-pool basis. --- Cargo.lock | 2 +- Cargo.toml | 2 +- documentation/en/src/changelog.md | 85 ++++++++++++++++++++++++++++++ documentation/en/src/comparison.md | 1 + documentation/ru/src/comparison.md | 1 + 5 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4aa2fe0e4..c4d783d2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1900,7 +1900,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pg_doorman" -version = "3.8.5" +version = "3.9.0" dependencies = [ "ahash", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 7f6694966..8aadd7d9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pg_doorman" -version = "3.8.5" +version = "3.9.0" edition = "2021" rust-version = "1.87.0" license = "MIT" diff --git a/documentation/en/src/changelog.md b/documentation/en/src/changelog.md index 515935e79..ab0d6cb76 100644 --- a/documentation/en/src/changelog.md +++ b/documentation/en/src/changelog.md @@ -1,5 +1,90 @@ # Changelog +### 3.9.0 + +Per-pool injection of arbitrary PostgreSQL configuration parameters +(GUCs) into the backend `StartupMessage`. The map cascades over three +levels — `general.startup_parameters`, per-pool overrides, and an +optional `auth_query` JSON column for per-user values in passthrough +mode — and the resulting values are written to `pg_settings.reset_val`, +so they survive client-side `RESET ALL` and `DISCARD ALL`. Among +mainstream poolers this is unique to pg_doorman: PgBouncer only carries +`client_encoding` / `datestyle` / `timezone` per database via the +connection string, Odyssey's `maintain_params` preserves client-side +parameters but offers no operator-side injection, and PgCat exposes no +equivalent. + +The most common use case is forcing `plan_cache_mode = "force_custom_plan"` +on a hot OLTP pool that keeps getting bitten by a sticky generic plan; +the same mechanism pins `statement_timeout`, `work_mem`, +`idle_in_transaction_session_timeout`, or any other GUC that a single +application needs without touching `postgresql.conf`, `ALTER ROLE`, or +`ALTER DATABASE`. + +#### Cascade resolution + +- `general.startup_parameters`, `pools..startup_parameters`, and + the optional `startup_parameters` text column on an `auth_query` row + merge per key, with the more specific level winning. Auth_query in + dedicated mode (a shared `server_user`) intentionally ignores the + per-user column and logs a one-time warning per pool and username. +- The merged cascade is resolved lazily on every backend spawn from + the live config snapshot, so a `RELOAD` that only changes + `general.startup_parameters` takes effect on the next backend + without recycling the pool. + +#### Validation and protocol safety + +- Reserved protocol keys (`user`, `database`, `replication`, + `options`, the `_pq_.*` extension prefix) are refused at config load. +- Keys must match the PG GUC naming shape `[A-Za-z_][A-Za-z0-9_.]*`, + values must not contain null bytes, and each level fits the operator + budget of `MAX_STARTUP_PACKET_LENGTH - 512` bytes. +- The full cascade is rechecked at every spawn against PG's 10 000-byte + `MAX_STARTUP_PACKET_LENGTH`; if the union would overflow, all + operator-supplied keys are dropped for that spawn and the event is + logged, so the connection still completes with PG defaults instead + of failing every client request. + +#### Quarantine for keys PG keeps rejecting + +- After + `general.startup_parameter_quarantine_threshold` consecutive + rejections of the same key (default `3`), pg_doorman parks the key + for `general.startup_parameter_quarantine_ttl` (default 5 minutes) + and stops sending it on subsequent backend startups. +- Both knobs are hot-reloadable: a SIGHUP that touches only the + threshold or TTL takes effect on every pool without rebuild. +- A successful backend startup resets partial-rejection counters for + every key it accepted, keeping the threshold model "N consecutive + rejections" rather than "N rejections ever". +- The quarantine record is keyed on the failing parameter name parsed + from PG's error message and cross-checked against the map pg_doorman + actually sent, so a server-side `ALTER ROLE SET` rejection for an + unrelated key cannot poison the operator's quarantine. +- SQLSTATE class `57P` keeps mapping to `ServerUnavailableError` and + drives the Patroni-assisted fallback path; the quarantine + observability runs alongside that mapping, not in place of it. + +#### Observability + +- `pg_doorman_backend_startup_parameter_errors_total{pool,parameter,sqlstate}` + counts every rejection. The failing username is in the corresponding + warn log line; it is deliberately not a label, so a dynamic + `auth_query` pool that mints many roles cannot blow up the series + count. +- `pg_doorman_backend_startup_parameter_quarantined{pool,parameter}` + goes to 1 the moment a key is parked and back to 0 when the TTL + expires, including on idle pools where the metrics collector + reconciles the gauge without waiting for the next backend spawn. +- `SHOW POOLS` exposes a `quarantined_params` text column so operators + can see at a glance which keys a pool is currently dropping. + +See [PostgreSQL startup parameters](tutorials/startup-parameters.md) +for the operator walkthrough and the +[reference entry](reference/configuration.md) for the full parameter +list. + ### 3.8.5 The web console now accepts JWTs issued by an external SSO proxy diff --git a/documentation/en/src/comparison.md b/documentation/en/src/comparison.md index e512a63f0..9b18d8eb5 100644 --- a/documentation/en/src/comparison.md +++ b/documentation/en/src/comparison.md @@ -77,6 +77,7 @@ See [Patroni-assisted fallback](tutorials/patroni-assisted-fallback.md), [`patro | LISTEN / NOTIFY pinning in transaction mode | No | No | Experimental | | Cross-rule connection cap (`shared_pool`) | No | No | Yes (since 1.5.1) | | `PAUSE` / `RESUME` / `RECONNECT` admin commands | Yes | Yes | Yes (since 1.4.1) | +| Operator-supplied PostgreSQL GUCs injected into backend `StartupMessage` per pool | Yes (`startup_parameters`, three-level cascade `general` → pool → `auth_query` passthrough, survives client `RESET ALL` / `DISCARD ALL`, with per-pool quarantine for keys PG keeps rejecting) | No (only `client_encoding` / `datestyle` / `timezone` per-database in the connection string) | No (`maintain_params` preserves client-side parameters across rebind; no operator-side injection) | See [Pool Coordinator](concepts/pool-coordinator.md), [Pool pressure](tutorials/pool-pressure.md). diff --git a/documentation/ru/src/comparison.md b/documentation/ru/src/comparison.md index b387eec0f..af034eb0b 100644 --- a/documentation/ru/src/comparison.md +++ b/documentation/ru/src/comparison.md @@ -77,6 +77,7 @@ PgCat намеренно опущен: у него центр тяжести — | LISTEN / NOTIFY pinning в transaction mode | Нет | Нет | Экспериментально | | Cross-rule connection cap (`shared_pool`) | Нет | Нет | Да (с 1.5.1) | | Команды администратора `PAUSE` / `RESUME` / `RECONNECT` | Да | Да | Да (с 1.4.1) | +| Внедрение операторских GUC PostgreSQL в `StartupMessage` бэкенда на уровне пула | Да (`startup_parameters`, трёхуровневый каскад `general` → пул → `auth_query` passthrough, значения переживают клиентские `RESET ALL` / `DISCARD ALL`, карантин на ключи, которые PG раз за разом отклоняет) | Нет (только `client_encoding` / `datestyle` / `timezone` в строке подключения на уровне базы) | Нет (`maintain_params` сохраняет параметры клиента при rebind, операторской инжекции нет) | См. [Координатор пулов](concepts/pool-coordinator.md), [Пул под нагрузкой](tutorials/pool-pressure.md). From ecec40fcaaff3088296407da95d465119f8479e5 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 00:34:15 +0300 Subject: [PATCH 24/72] ci(bdd): run @startup-parameters scenarios in CI The new feature file in tests/bdd/features/startup-parameters.feature ships with the @startup-parameters tag only. None of the existing BDD matrix entries pick that tag up, so the twelve end-to-end scenarios that exercise the feature were never running in CI even after the file landed. Locally the tag-filtered cargo invocation passes; the gap was only visible at the workflow level. Adds a dedicated matrix row that runs the same invocation in CI. --- .github/workflows/bdd-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/bdd-tests.yml b/.github/workflows/bdd-tests.yml index 890089e34..454068334 100644 --- a/.github/workflows/bdd-tests.yml +++ b/.github/workflows/bdd-tests.yml @@ -245,6 +245,7 @@ jobs: - { name: "Patroni-assisted fallback", cargo: "test --test bdd -- --tags @patroni_fallback" } - { name: "Server TLS", cargo: "test --test bdd -- --tags @server-tls" } - { name: "TLS migration (vendored OpenSSL)", cargo: "test --features tls-migration --test bdd -- --tags @tls-migration" } + - { name: "Startup parameters", cargo: "test --test bdd -- --tags @startup-parameters" } steps: - name: Checkout repository uses: actions/checkout@v4 From 19b7c46881969ec1a8a5b538446dc3811161752b Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 01:12:07 +0300 Subject: [PATCH 25/72] admin/web: surface operator-injected startup_parameters + codex fixes Operator visibility on operator-injected GUCs: * Admin SQL console: new SHOW STARTUP_PARAMETERS lists the per-pool effective merged cascade with the cascade layer that contributed each value (general / pool / auth_query) and a quarantined column. The entry is in the canonical SHOW_SUBCOMMANDS list, so psql tab completion on SHOW now offers `startup_parameters`. * Web UI: /api/pools carries startup_parameters[] (with source per key) and quarantined_params[]. PoolDetail renders a "Startup parameters (operator-injected)" section that highlights quarantined keys, so an operator triaging a misbehaving pool no longer has to drop to psql to see which key is being parked. * BDD: new scenario "admin SHOW STARTUP_PARAMETERS lists the merged cascade per pool" under @startup-parameters. Codex review fixes: * HIGH #3 (SQLSTATE allowlist too narrow): quarantine now triggers whenever the parameter pg_doorman parses out of a PG ErrorResponse matches a key it actually sent, instead of restricting to the 22023 / 42704 / 42501 whitelist. The new heuristic covers 55P02 (cant_change_runtime_param) and any future or extension-specific codes a PG release reports under the startup family. SQLSTATE class 57P (server unavailable) keeps its dedicated handling so the Patroni-assisted fallback path stays unchanged. * HIGH #8 (final packet size guard checked the pre-final payload): validation now reuses the exact wire layout pg_doorman puts on the network - length prefix, protocol version, user, application_name, database, every operator-supplied pair, terminator - and rejects the cascade for one backend spawn when the full packet would exceed PG's 10000-byte cap. The backend then connects with PostgreSQL defaults and the operator sees a warn log naming the actual computed length. Operator documentation in EN and RU was overhauled to match the shipped behaviour: the RU startup-parameters tutorial got a full rewrite, RU reference docs gained the three new knobs, EN+RU auth-query docs document the new startup_parameters column, and the PgBouncer/Odyssey comparison rows in both languages now state the precise distinction (no operator-side injection of startup parameters into the backend StartupMessage, with the RESET/DISCARD contract pg_doorman provides). Reference configs and EN reference docs were regenerated to pick up the operator copy refresh in src/app/generate/fields.yaml. --- .../en/src/authentication/auth-query.md | 2 +- documentation/en/src/changelog.md | 24 +- documentation/en/src/comparison.md | 2 +- .../en/src/tutorials/startup-parameters.md | 12 +- documentation/ru/src/SUMMARY.md | 2 +- .../ru/src/authentication/auth-query.md | 2 +- documentation/ru/src/comparison.md | 2 +- documentation/ru/src/reference/general.md | 33 +++ documentation/ru/src/reference/pool.md | 19 +- .../ru/src/tutorials/startup-parameters.md | 214 +++++++++--------- frontend/dist/.source-hash | 2 +- frontend/dist/assets/index-C67S1foj.js.gz | Bin 0 -> 124418 bytes frontend/dist/assets/index-Dl7lSMbo.js.gz | Bin 124072 -> 0 bytes frontend/dist/index.html.gz | Bin 320 -> 321 bytes frontend/src/pages/PoolDetail.tsx | 83 +++++++ frontend/src/types.ts | 15 ++ pg_doorman.toml | 33 +-- pg_doorman.yaml | 33 +-- src/admin/mod.rs | 24 +- src/admin/show.rs | 59 +++++ src/app/generate/fields.yaml | 79 ++++--- src/config/general.rs | 12 +- src/config/pool.rs | 12 +- src/config/startup_parameters.rs | 65 ++++++ src/pool/inner.rs | 13 ++ src/pool/server_pool.rs | 81 ++++++- src/pool/startup_resolver.rs | 69 ++++++ src/server/server_backend.rs | 42 ++-- src/web/routes/collect/pools.rs | 6 +- src/web/routes/dto.rs | 43 +++- tests/bdd/features/startup-parameters.feature | 58 ++++- 31 files changed, 795 insertions(+), 246 deletions(-) create mode 100644 frontend/dist/assets/index-C67S1foj.js.gz delete mode 100644 frontend/dist/assets/index-Dl7lSMbo.js.gz diff --git a/documentation/en/src/authentication/auth-query.md b/documentation/en/src/authentication/auth-query.md index f8157dbac..80fa70175 100644 --- a/documentation/en/src/authentication/auth-query.md +++ b/documentation/en/src/authentication/auth-query.md @@ -28,7 +28,7 @@ pools: cache_failure_ttl: "30s" ``` -The query must return a column named `passwd` or `password` containing the MD5 or SCRAM hash. Extra columns are ignored. +The query must return a column named `passwd` or `password` containing the MD5 or SCRAM hash. Extra columns are ignored except for optional `startup_parameters`: passthrough mode reads it as a `text` JSON object with per-user PostgreSQL startup parameters, while dedicated mode ignores it and logs a warning. `user` and `password` are the credentials PgDoorman uses to run the lookup query. They must have permission to read the credential column. Either grant access to a custom view (recommended) or use a user in `pg_read_server_files` group. diff --git a/documentation/en/src/changelog.md b/documentation/en/src/changelog.md index ab0d6cb76..2a007d5ee 100644 --- a/documentation/en/src/changelog.md +++ b/documentation/en/src/changelog.md @@ -2,21 +2,20 @@ ### 3.9.0 -Per-pool injection of arbitrary PostgreSQL configuration parameters -(GUCs) into the backend `StartupMessage`. The map cascades over three +Per-pool PostgreSQL configuration parameters (GUCs) in backend +`StartupMessage`. The map cascades over three levels — `general.startup_parameters`, per-pool overrides, and an optional `auth_query` JSON column for per-user values in passthrough mode — and the resulting values are written to `pg_settings.reset_val`, so they survive client-side `RESET ALL` and `DISCARD ALL`. Among -mainstream poolers this is unique to pg_doorman: PgBouncer only carries -`client_encoding` / `datestyle` / `timezone` per database via the -connection string, Odyssey's `maintain_params` preserves client-side -parameters but offers no operator-side injection, and PgCat exposes no -equivalent. +mainstream poolers, the full cascade/reset contract is specific to +pg_doorman: PgBouncer can track or ignore selected client startup +parameters, Odyssey's `maintain_params` preserves client-side +parameters across rebind, and PgCat exposes no equivalent. The most common use case is forcing `plan_cache_mode = "force_custom_plan"` -on a hot OLTP pool that keeps getting bitten by a sticky generic plan; -the same mechanism pins `statement_timeout`, `work_mem`, +on a hot OLTP pool affected by a sticky generic plan. The same mechanism +pins `statement_timeout`, `work_mem`, `idle_in_transaction_session_timeout`, or any other GUC that a single application needs without touching `postgresql.conf`, `ALTER ROLE`, or `ALTER DATABASE`. @@ -76,14 +75,13 @@ application needs without touching `postgresql.conf`, `ALTER ROLE`, or - `pg_doorman_backend_startup_parameter_quarantined{pool,parameter}` goes to 1 the moment a key is parked and back to 0 when the TTL expires, including on idle pools where the metrics collector - reconciles the gauge without waiting for the next backend spawn. + reconciles the gauge without waiting for the next backend startup. - `SHOW POOLS` exposes a `quarantined_params` text column so operators can see at a glance which keys a pool is currently dropping. See [PostgreSQL startup parameters](tutorials/startup-parameters.md) -for the operator walkthrough and the -[reference entry](reference/configuration.md) for the full parameter -list. +for the operator walkthrough, plus [General Settings](reference/general.md) +and [Pool Settings](reference/pool.md) for the full parameter list. ### 3.8.5 diff --git a/documentation/en/src/comparison.md b/documentation/en/src/comparison.md index 9b18d8eb5..df93d3a78 100644 --- a/documentation/en/src/comparison.md +++ b/documentation/en/src/comparison.md @@ -77,7 +77,7 @@ See [Patroni-assisted fallback](tutorials/patroni-assisted-fallback.md), [`patro | LISTEN / NOTIFY pinning in transaction mode | No | No | Experimental | | Cross-rule connection cap (`shared_pool`) | No | No | Yes (since 1.5.1) | | `PAUSE` / `RESUME` / `RECONNECT` admin commands | Yes | Yes | Yes (since 1.4.1) | -| Operator-supplied PostgreSQL GUCs injected into backend `StartupMessage` per pool | Yes (`startup_parameters`, three-level cascade `general` → pool → `auth_query` passthrough, survives client `RESET ALL` / `DISCARD ALL`, with per-pool quarantine for keys PG keeps rejecting) | No (only `client_encoding` / `datestyle` / `timezone` per-database in the connection string) | No (`maintain_params` preserves client-side parameters across rebind; no operator-side injection) | +| Operator-defined PostgreSQL GUCs in backend `StartupMessage` per pool | Yes (`startup_parameters`, three-level cascade `general` → pool → `auth_query` passthrough, survives client `RESET ALL` / `DISCARD ALL`, with per-pool quarantine for keys PG keeps rejecting) | No equivalent cascade/reset contract; selected client startup parameters can be tracked or ignored, but not set by the operator per pool | No (`maintain_params` preserves client-side parameters across rebind; no operator-defined GUCs) | See [Pool Coordinator](concepts/pool-coordinator.md), [Pool pressure](tutorials/pool-pressure.md). diff --git a/documentation/en/src/tutorials/startup-parameters.md b/documentation/en/src/tutorials/startup-parameters.md index 8720315b9..e0584df1d 100644 --- a/documentation/en/src/tutorials/startup-parameters.md +++ b/documentation/en/src/tutorials/startup-parameters.md @@ -4,7 +4,7 @@ Some operators need a few PostgreSQL configuration parameters to apply to every backend pg_doorman opens, without touching `postgresql.conf`, `ALTER ROLE`, or `ALTER DATABASE`. Three cases recur in practice: -- A hot OLTP pool gets bitten by a sticky generic plan after the +- A hot OLTP pool is affected by a sticky generic plan after the `plan_cache_mode = auto` heuristic flips. Switching the whole role to `force_custom_plan` would affect every workload using that role; scoping the change to one pool is what you want. @@ -128,7 +128,7 @@ consecutive. Both knobs are hot-reloaded on `SIGHUP`. Releasing a quarantined parameter is **TTL-only**: pg_doorman cannot tell whether the operator has fixed the underlying problem until the TTL expires and -the next backend spawn tries the parameter again. +the next backend startup tries the parameter again. ## Observability @@ -150,10 +150,10 @@ The same state is on the Prometheus surface: log line so dynamic `auth_query` pools cannot blow up the series count. - `pg_doorman_backend_startup_parameter_quarantined{pool, parameter}` - is `1` while a parameter is parked and flips back to `0` exactly - once when its TTL expires. The gauge does not clear on its own - even after the operator fixes the underlying issue; the next - spawn after TTL expiry is what re-arms it. + is `1` while a parameter is parked. After the TTL expires, the + quarantine state can be reconciled on the next metrics scrape or + `SHOW POOLS`; the next backend startup will try the parameter + again. A reasonable starting alert is "any non-zero quarantine gauge for longer than the TTL". If the gauge is set for `2 × ttl`, the diff --git a/documentation/ru/src/SUMMARY.md b/documentation/ru/src/SUMMARY.md index 28a274dc4..7cb3a0db2 100644 --- a/documentation/ru/src/SUMMARY.md +++ b/documentation/ru/src/SUMMARY.md @@ -30,7 +30,7 @@ - [Режимы пула](concepts/pool-modes.md) - [Координатор пулов](concepts/pool-coordinator.md) - [Кеш Parse для анонимных prepared statements](tutorials/prepared-statements.md) -- [Startup-параметры PostgreSQL](tutorials/startup-parameters.md) +- [Параметры запуска PostgreSQL](tutorials/startup-parameters.md) - [Пул под нагрузкой (продвинутое)](tutorials/pool-pressure.md) # Высокая доступность diff --git a/documentation/ru/src/authentication/auth-query.md b/documentation/ru/src/authentication/auth-query.md index 45c7427e1..f3f3e1aaf 100644 --- a/documentation/ru/src/authentication/auth-query.md +++ b/documentation/ru/src/authentication/auth-query.md @@ -28,7 +28,7 @@ pools: cache_failure_ttl: "30s" ``` -Запрос должен возвращать колонку с именем `passwd` или `password`, содержащую хеш MD5 или SCRAM. Дополнительные колонки игнорируются. +Запрос должен возвращать колонку с именем `passwd` или `password`, содержащую хеш MD5 или SCRAM. Дополнительные колонки игнорируются, кроме необязательной `startup_parameters`: passthrough-режим читает её как JSON-объект в `text` с пользовательскими параметрами запуска PostgreSQL, а dedicated-режим игнорирует её и пишет warning. `user` и `password` — это учётные данные, под которыми pg_doorman выполняет lookup-запрос. У них должно быть право читать колонку с учётными данными. Либо выдайте доступ к специально созданному представлению (рекомендуется), либо используйте пользователя из группы `pg_read_server_files`. diff --git a/documentation/ru/src/comparison.md b/documentation/ru/src/comparison.md index af034eb0b..2d8591ef1 100644 --- a/documentation/ru/src/comparison.md +++ b/documentation/ru/src/comparison.md @@ -77,7 +77,7 @@ PgCat намеренно опущен: у него центр тяжести — | LISTEN / NOTIFY pinning в transaction mode | Нет | Нет | Экспериментально | | Cross-rule connection cap (`shared_pool`) | Нет | Нет | Да (с 1.5.1) | | Команды администратора `PAUSE` / `RESUME` / `RECONNECT` | Да | Да | Да (с 1.4.1) | -| Внедрение операторских GUC PostgreSQL в `StartupMessage` бэкенда на уровне пула | Да (`startup_parameters`, трёхуровневый каскад `general` → пул → `auth_query` passthrough, значения переживают клиентские `RESET ALL` / `DISCARD ALL`, карантин на ключи, которые PG раз за разом отклоняет) | Нет (только `client_encoding` / `datestyle` / `timezone` в строке подключения на уровне базы) | Нет (`maintain_params` сохраняет параметры клиента при rebind, операторской инжекции нет) | +| GUC PostgreSQL, заданные оператором и переданные в `StartupMessage` бэкенда на уровне пула | Да (`startup_parameters`, трёхуровневый каскад `general` → пул → `auth_query` passthrough, значения переживают клиентские `RESET ALL` / `DISCARD ALL`, карантин на ключи, которые PG раз за разом отклоняет) | Нет эквивалентного каскада с таким контрактом сброса; отдельные клиентские startup-параметры можно отслеживать или игнорировать, но не задавать оператором на уровне пула | Нет (`maintain_params` сохраняет параметры клиента при rebind, операторского задания GUC нет) | См. [Координатор пулов](concepts/pool-coordinator.md), [Пул под нагрузкой](tutorials/pool-pressure.md). diff --git a/documentation/ru/src/reference/general.md b/documentation/ru/src/reference/general.md index 43b0e64be..6fac68b61 100644 --- a/documentation/ru/src/reference/general.md +++ b/documentation/ru/src/reference/general.md @@ -673,6 +673,39 @@ hostnossl all all 192.168.1.0/24 trust - Для методов аутентификации, отличных от `trust`, PgDoorman выполняет соответствующий challenge/response с клиентом. - Для потоков Talos/JWT/PAM, настроенных на уровне пула или пользователя, `trust` всё равно обходит запрос пароля у клиента; однако эти режимы могут использоваться, если `trust` не совпал. +### startup_parameter_quarantine_threshold + +Сколько подряд отказов PostgreSQL по одному и тому же параметру нужно +получить, прежде чем pg_doorman перестанет включать его в `StartupMessage` +бэкендов этого пула. Счётчик ведётся по паре `(pool, parameter)`. + +По умолчанию: `3`. + +### startup_parameter_quarantine_ttl + +На сколько миллисекунд отклонённый параметр запуска помещается в карантин. +После истечения TTL pg_doorman снова попробует отправить ключ при следующем +запуске бэкенда и сбросит счётчик отказов. + +По умолчанию: `300000` (5 минут). + +### startup_parameters + +Базовые параметры запуска PostgreSQL, которые записываются в +`StartupMessage` каждого бэкенда. `startup_parameters` уровня пула +переопределяют эти значения по ключу, а строки passthrough `auth_query` +могут переопределить их на уровне пользователя. + +При загрузке конфигурации pg_doorman проверяет зарезервированные +протокольные ключи (`user`, `database`, `replication`, `options`, +`_pq_.*`), имена GUC, нулевые байты и размер этого уровня. Перед каждым +запуском бэкенда слитый каскад снова проверяется против +`MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается, pg_doorman +пропускает параметры, заданные оператором, для этого запуска и пишет +warning. + +По умолчанию: `{}`. + ### pooler_check_query Когда клиент отправляет ровно этот запрос как SimpleQuery, pg_doorman отвечает немедленно diff --git a/documentation/ru/src/reference/pool.md b/documentation/ru/src/reference/pool.md index 5932c3f44..fea940fa7 100644 --- a/documentation/ru/src/reference/pool.md +++ b/documentation/ru/src/reference/pool.md @@ -132,6 +132,23 @@ По умолчанию: `0 (no protection)`. +### startup_parameters + +Параметры запуска PostgreSQL уровня пула, которые записываются в +`StartupMessage` каждого бэкенда. Значения переопределяют +`general.startup_parameters` по ключу. В passthrough-пулах `auth_query` +пользовательская колонка `startup_parameters` может переопределить и этот +уровень. + +При загрузке конфигурации pg_doorman проверяет зарезервированные +протокольные ключи, имена GUC, нулевые байты и размер этого уровня. Перед +каждым запуском бэкенда слитый каскад снова проверяется против +`MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается, pg_doorman +пропускает параметры, заданные оператором, для этого запуска и пишет +warning. + +По умолчанию: `{}`. + ## Настройки auth_query Секция `auth_query` включает динамическую аутентификацию пользователей через запрос учётных данных @@ -186,7 +203,7 @@ auth_query: ### query -SQL-запрос для получения учётных данных. Должен возвращать колонку с именем `passwd` или `password`, содержащую MD5- или SCRAM-хеш. Если запрос возвращает ровно одну колонку, она используется независимо от имени. Любые лишние колонки игнорируются. В качестве плейсхолдера для имени пользователя используйте `$1`. +SQL-запрос для получения учётных данных. Должен возвращать колонку с именем `passwd` или `password`, содержащую MD5- или SCRAM-хеш. Если запрос возвращает ровно одну колонку, она используется независимо от имени. Дополнительные колонки игнорируются, кроме необязательной `startup_parameters` типа `text`: passthrough-режим читает её как JSON-объект с пользовательскими параметрами запуска PostgreSQL. Dedicated-режим игнорирует эту колонку и пишет warning. В качестве плейсхолдера для имени пользователя используйте `$1`. Пример: `"SELECT passwd FROM pg_shadow WHERE usename = $1"` diff --git a/documentation/ru/src/tutorials/startup-parameters.md b/documentation/ru/src/tutorials/startup-parameters.md index b09a1db60..6c3c03024 100644 --- a/documentation/ru/src/tutorials/startup-parameters.md +++ b/documentation/ru/src/tutorials/startup-parameters.md @@ -1,31 +1,27 @@ -# Startup-параметры PostgreSQL - -Иногда оператору нужно, чтобы несколько параметров конфигурации -PostgreSQL применялись к каждому backend, который открывает -pg_doorman, без правки `postgresql.conf`, без `ALTER ROLE` и без -`ALTER DATABASE`. На практике повторяются три ситуации: - -- На горячем OLTP-пуле план переключается на generic после того, как - эвристика `plan_cache_mode = auto` решает, что generic дешевле, и - обратно уже не возвращается. Перевод всей роли на - `force_custom_plan` затронет любую другую нагрузку под этой ролью; - изменение хочется ограничить одним пулом. -- Приложение не выставляет `statement_timeout` или - `idle_in_transaction_session_timeout` самостоятельно, а быстро - его доработать нет возможности. DBA нужен серверный дефолт, - который переживает собственные `RESET ALL` приложения. -- Одно приложение должно стабильно сообщать конкретный - `application_name` независимо от того, что выставит драйвер: чтобы - `pg_stat_activity` и журналы аудита оставались читаемыми. - -Для этих случаев в pg_doorman есть `startup_parameters`: карта -параметров PostgreSQL (GUC), которая прокидывается в каждое -серверное соединение пула. +# Параметры запуска PostgreSQL + +Иногда параметры PostgreSQL нужно задавать для каждого серверного +соединения, которое открывает pg_doorman, без правки `postgresql.conf`, +`ALTER ROLE` или `ALTER DATABASE`. Типичные случаи: + +- В горячем OLTP-пуле план переключается на generic после решения + эвристики `plan_cache_mode = auto` и обратно уже не возвращается. + `ALTER ROLE SET plan_cache_mode = force_custom_plan` затронет любую + другую нагрузку под этой ролью, а изменить нужно только один пул. +- Приложение не задаёт `statement_timeout` или + `idle_in_transaction_session_timeout`, а быстро доработать его нельзя. + Администратору БД нужен сессионный дефолт, который переживает + клиентский `RESET ALL`. +- Одно приложение должно стабильно показывать конкретный + `application_name`, независимо от значения, которое передаст драйвер, + чтобы `pg_stat_activity` и аудит оставались читаемыми. + +Для этого в pg_doorman есть `startup_parameters`: карта GUC PostgreSQL, +которую pg_doorman передаёт в каждое новое серверное соединение пула. ## Конфигурация -Каскад из трёх уровней: значение более узкого уровня выигрывает -по ключу. +Каскад состоит из трёх уровней. Более узкий уровень выигрывает по ключу. ```toml [general.startup_parameters] @@ -36,19 +32,18 @@ plan_cache_mode = "force_custom_plan" work_mem = "64MB" ``` -После `SIGHUP` (или `RELOAD` через консоль администратора) каждый -новый backend пула `checkout` стартует со значениями +После `SIGHUP` или `RELOAD` через консоль администратора каждый новый +бэкенд пула `checkout` стартует со значениями `statement_timeout = 5s`, `plan_cache_mode = force_custom_plan` и -`work_mem = 64MB`. У других пулов `statement_timeout = 5s` остаётся -из `general`, а остальное берётся из дефолтов PostgreSQL. Уже -открытые backend'ы не меняются: изменения вступают в силу по мере -ротации соединений. +`work_mem = 64MB`. В других пулах остаётся только +`statement_timeout = 5s` из `general`; остальные значения берутся из +настроек PostgreSQL по умолчанию. Уже открытые бэкенды не меняются: +новые значения вступают в силу по мере ротации соединений. -В режиме passthrough `auth_query` (когда `server_user` не задан) -запрос аутентификации может вернуть необязательную колонку -`startup_parameters` типа `text`, содержащую JSON-объект. Значения -из этой колонки перекрывают `general` и pool-level настройки -только для конкретного пользователя. +В passthrough-режиме `auth_query`, когда `server_user` не задан, запрос +аутентификации может вернуть необязательную колонку `startup_parameters` +типа `text` с JSON-объектом. Значения из этой колонки переопределяют +`general` и настройки пула только для конкретного пользователя. ```sql SELECT @@ -62,24 +57,22 @@ WHERE rolname = $1; ``` Колонка должна возвращаться как `text`. Если SQL отдаёт `json` или -`jsonb`, нужно добавить явный каст `::text`. pg_doorman читает её -именно как `text` и пишет однократное предупреждение для каждого -пользователя, у которого тип не совпал. +`jsonb`, добавьте явный каст `::text`. pg_doorman читает её именно как +`text` и один раз пишет предупреждение для каждого пользователя, у +которого тип не совпал. -Dedicated-режим `auth_query` (когда `server_user` задан) колонку -игнорирует и пишет одно предупреждение на пару (пул, имя -пользователя): на одном общем backend нельзя одновременно применить -разные значения для разных пользователей. +Dedicated-режим `auth_query`, когда `server_user` задан, игнорирует эту +колонку и один раз пишет предупреждение на пару `(пул, пользователь)`. +Один общий бэкенд не может одновременно иметь разные значения для +разных пользователей. ## Что pg_doorman делает со значениями -Слитая карта пишется в `StartupMessage` каждого backend, который -открывает pg_doorman. PostgreSQL запоминает каждое значение как -сессионный дефолт (`pg_settings.reset_val` и -`pg_settings.source = 'session'`), поэтому клиентские `RESET ALL` -и `DISCARD ALL` возвращают именно операторское значение, а не -сбрасывают его. Оператор получает стабильный сессионный дефолт без -правки `postgresql.conf` и без `ALTER ROLE`. +Слитая карта записывается в `StartupMessage` каждого бэкенда, который +открывает pg_doorman. PostgreSQL запоминает эти значения как сессионные +дефолты (`pg_settings.reset_val` и `pg_settings.source = 'session'`). +Поэтому клиентские `RESET ALL` и `DISCARD ALL` возвращают именно +значение, заданное оператором, а не исходное значение PostgreSQL. Значение видно со стороны клиента: @@ -97,55 +90,54 @@ checkout=> SET plan_cache_mode = 'auto'; RESET ALL; SHOW plan_cache_mode; ## Валидация -Проверки на этапе загрузки конфигурации: +При загрузке конфигурации pg_doorman проверяет: -- Имена ключей должны подходить под маску GUC PostgreSQL: +- Имена ключей должны соответствовать маске GUC PostgreSQL: `^[A-Za-z_][A-Za-z0-9_.]*$`. Составные имена вроде - `auto_explain.log_min_duration` допустимы; произвольные знаки - препинания нет. -- Зарезервированные ключи (`user`, `database`, `replication`, - `options` и всё, что начинается с `_pq_.`) отклоняются. - pg_doorman управляет ими сам, либо PostgreSQL обрабатывает их в - `StartupMessage` особым образом. -- Значения не должны содержать null-байт. -- Каждый уровень (general или pool) обязан укладываться в - операторский бюджет: `MAX_STARTUP_PACKET_LENGTH` (10 000 байт) - минус 512 байт, зарезервированных под служебные ключи pg_doorman. - -При запуске каждого backend pg_doorman заново сверяет уже слитый -каскад с тем же лимитом. Два уровня, каждый из которых помещался -отдельно, могут вместе вылезти за бюджет, когда `auth_query` -добавит третий слой. В таком случае pg_doorman сбрасывает все -операторские ключи для этого запуска, пишет в лог размеры и -открывает backend с дефолтами PostgreSQL, а не проваливает каждую -попытку подключения. + `auto_explain.log_min_duration` допустимы; произвольная пунктуация + нет. +- Зарезервированные ключи (`user`, `database`, `replication`, `options` + и всё, что начинается с `_pq_.`) отклоняются. pg_doorman управляет + ими сам, либо PostgreSQL обрабатывает их в `StartupMessage` особым + образом. +- Значения не должны содержать нулевой байт. +- Каждый уровень (`general` или `pool`) должен помещаться в + операторский бюджет: `MAX_STARTUP_PACKET_LENGTH` (10000 байт) минус + 512 байт, зарезервированных под служебные ключи pg_doorman. + +Перед запуском каждого бэкенда pg_doorman заново проверяет уже слитый +каскад против того же лимита. Два уровня, которые помещались по +отдельности, могут вместе выйти за бюджет, особенно когда `auth_query` +добавляет третий слой. В таком случае pg_doorman пропускает все GUC, +заданные оператором, для этого запуска, пишет размеры в лог и открывает +бэкенд с настройками PostgreSQL по умолчанию. ## Карантин -PostgreSQL может отклонить операторский параметр на старте backend -с sqlstate `22023` (некорректное значение), `42704` (неизвестный -объект: опечатка в имени параметра) или `42501` (недостаточно -прав: параметр требует роли выше, чем у backend). pg_doorman -считает подряд идущие отказы по паре `(pool, parameter)`. Когда -счётчик достигает `general.startup_parameter_quarantine_threshold` -(по умолчанию 3), pg_doorman перестаёт отправлять этот ключ на +PostgreSQL может отклонить параметр на старте бэкенда с sqlstate +`22023` (некорректное значение), `42704` (неизвестный параметр) или +`42501` (недостаточно прав: параметр требует более привилегированной +роли). pg_doorman считает подряд идущие отказы по паре +`(pool, parameter)`. Когда счётчик достигает +`general.startup_parameter_quarantine_threshold` (по умолчанию 3), +pg_doorman перестаёт отправлять этот ключ на `general.startup_parameter_quarantine_ttl` (по умолчанию 5 минут). -Успешный запуск backend сбрасывает счётчик для всех ключей, -которые он только что отправил. Три временных отказа не приводят -к карантину, если между ними прошёл хотя бы один удачный запуск: -«подряд» здесь буквально означает подряд. +Успешный запуск бэкенда сбрасывает счётчик для всех ключей, которые +были отправлены в этом запуске. Три временных отказа не приводят к +карантину, если между ними был хотя бы один успешный запуск: «подряд» +здесь означает именно подряд. -Оба значения подхватываются на `SIGHUP`. Выход из карантина -происходит **только по TTL**: pg_doorman не может узнать, починил -ли оператор исходную проблему, пока TTL не истечёт и следующий -backend не попробует параметр заново. +Оба значения подхватываются на `SIGHUP`. Выход из карантина происходит +**только по TTL**: pg_doorman не может узнать, исправлен ли параметр, +пока TTL не истечёт и очередной запуск бэкенда не попробует отправить +его снова. ## Мониторинг В выводе `SHOW POOLS` есть колонка `quarantined_params` со списком -параметров, которые сейчас в карантине у пула. Имена разделены -запятой: +параметров, которые сейчас находятся в карантине у пула. Имена +разделены запятой: ```text database | user | ... | quarantined_params @@ -157,34 +149,32 @@ backend не попробует параметр заново. То же состояние видно через Prometheus: - `pg_doorman_backend_startup_parameter_errors_total{pool, parameter, sqlstate}` - считает каждый отказ от PostgreSQL. Инкрементируется один раз на - каждый отвергнутый `StartupMessage`; имя пользователя осознанно - не входит в набор лейблов, чтобы динамические `auth_query`-пулы - не разнесли количество серий. Имя отказавшего пользователя есть - в соответствующей warn-строке лога. + считает каждый отказ PostgreSQL. Метрика увеличивается один раз на + каждый отвергнутый `StartupMessage`; имя пользователя не входит в + набор лейблов, чтобы динамические `auth_query`-пулы не раздували + количество серий. Имя пользователя есть в соответствующей + warn-строке лога. - `pg_doorman_backend_startup_parameter_quarantined{pool, parameter}` - держит `1`, пока параметр в карантине, и сбрасывается в `0` - ровно один раз, когда истекает TTL. Починка операторского - значения не сбрасывает метрику раньше времени: переоценка - происходит на следующем запуске backend после истечения TTL. + держит `1`, пока параметр в карантине. После истечения TTL состояние + может быть очищено при следующей сверке карантина, например во время + сбора метрик или `SHOW POOLS`. Следующий запуск бэкенда снова + попробует отправить этот параметр. -Разумная отправная точка для алерта: «ненулевое значение -quarantine-метрики дольше TTL». Если метрика держится `2 × ttl`, -после одного окна ретрая операторское значение всё ещё неверно и -заслуживает внимания. +Разумная отправная точка для алерта: ненулевое значение quarantine- +метрики дольше TTL. Если метрика держится `2 × ttl`, после одного окна +повторной попытки значение всё ещё неверно и требует внимания. ## Когда это не нужно -- Приложение само выставляет параметр на каждом подключении. - Дублирование в `startup_parameters` добавляет учётную поверхность - без изменения поведения. -- Тюнинг на одну транзакцию (`SET LOCAL`). `startup_parameters` - задают сессионные дефолты; параметры на уровне транзакции - выставляет приложение. -- Значения, которые должны зависеть от того, какой запрос идёт - прямо сейчас. Startup-параметры применяются ко всем транзакциям - каждого backend на протяжении его жизни; варианта «на один - statement» нет. +- Приложение само задаёт параметр на каждом подключении. Дублирование в + `startup_parameters` добавляет поверхность сопровождения без изменения + поведения. +- Тюнинг на одну транзакцию (`SET LOCAL`). `startup_parameters` задают + сессионные дефолты; параметры уровня транзакции должно выставлять + приложение. +- Значения, которые зависят от текущего запроса. Параметры запуска + действуют для всех транзакций бэкенда на протяжении его жизни; + режима «на один statement» нет. ## Справочник @@ -193,8 +183,8 @@ quarantine-метрики дольше TTL». Если метрика держи `startup_parameter_quarantine_ttl`. - [Настройки пула](../reference/pool.md): `pools..startup_parameters`. -- [auth_query](../authentication/auth-query.md): режимы passthrough - и dedicated, чтение колонки `startup_parameters`. +- [auth_query](../authentication/auth-query.md): passthrough- и + dedicated-режимы, чтение колонки `startup_parameters`. - [Команды администратора](../observability/admin-commands.md): `SHOW POOLS`. - [Метрики Prometheus](../reference/prometheus.md): полный список. diff --git a/frontend/dist/.source-hash b/frontend/dist/.source-hash index a917e499e..2c170cb4a 100644 --- a/frontend/dist/.source-hash +++ b/frontend/dist/.source-hash @@ -1 +1 @@ -814730d21a0d7e29a720341a7e95ab7920719785ae0d25c506c66d0703113e2d +ae50796dc29923a9335b87e89ca4d333e7fd4e7f80953e27c5c1e0524c827d75 diff --git a/frontend/dist/assets/index-C67S1foj.js.gz b/frontend/dist/assets/index-C67S1foj.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..ed212091ce51282b962d082fb36abd289f4ba43f GIT binary patch literal 124418 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

}9^yrmM4;Fp=1qxfFFs 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 bRvi!`Aht!kQ$3nr{ipvA4-loVFXaUQ_}A?( literal 0 HcmV?d00001 diff --git a/frontend/dist/assets/index-Dl7lSMbo.js.gz b/frontend/dist/assets/index-Dl7lSMbo.js.gz deleted file mode 100644 index 1032fb4554baa6baaf52933918b4b2e72b7d147d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124072 zcmV(wK>YDT!hTPkUaPy-vQfr7@7Qy6^SGV$lDL#!REA+cc<3g>%5`g4 z>P59??@hnj7x6`Q+3d~F2M_HePP0-g>YwyZo4sx&yBmn4TWZZjtoH?lD&>AJ{#g#HL($*rr5Vj@e{QZ{P4mGZ z8D^>bT`6C28v|rcTAgwb$i6q-DaMMJrhesOc(*V57)mmH*Y8*UH??+x;!&Zh)Bha= ztqP_z7B{8sJZn+!KcA;!cr8$O^wwF%F1697W$adI4@6kY_GPc%p#hWwn{hxHyVX{w zBRV@tN2I}^nzb%=T31|V>*LNREdGGTPGP@Cb91fAAQ%PJME0VhM-@Jp#%Mj${5->& z;d6uyq4#qSV=;T_VboNM+^+WGOk50m+1=b6x^F^}lM;k&bg3(bLowV<`n|Ty>qkY! zJXESZOw#o8uy@gmX+U`@7O>hKbh_8-r{$Gy)d@!BUfk~I9Z_zKm2AaHe0P=P<7JYo zURG`tB2&!hLc*oXVRvL^fmZZ^j$2g>^j@LwXr|WGlS4Dr?24>?iENh`Q`p6+2rABG zCIAk>gPOwO+}yBwb|zA}-^~de43jLO?NGgJrOuhHwp-YE!_XY}U}acU-;LpjfX!h$ zdJwm~#TyT!QP}Qb$--}i*n2?>`RpJ6_+O=el>P%#F5*;__C%|lQOuA&s281_dtJTi z#nm4vk|=dG8QfuhF0&GbTiR%~#d(ta3`-y5cD2-sJEdNhmRj9zuiwMrOslHc*UMg7 zN|Sup7Ns_Ja0x-xa;KEX9Wg9rm!h=&@vDw5b(5T`iz$Js*!sA-zO%nxLe)Y=mxf7_ zl{&p4pUXQsC7II@ZD;DhGn$)U17}lTRCqam>BTJFi{`IERBuwwVWfZ)9l)}j4k4jT zISBg`QfY2meI)iKBzA|b3str?A(u~a5LNc~z zCT^+q%SoL}hBvKY=M38wIc_KQFHZ-w9!S2JkbEVs5+t2XNb0xl?~rt@lX@9053G}2 zhyBW`IS#8hu5sS)KR&AKa23~K!bhUKLJ5t^YIHp}w|a7Y`uMndQvQ$s&=r|QsYN#7HLK6d?n zHZ8-x$i@>#;YWQFx6-tC5rMVnqSksa^TNL*Dks?=L=W@psX=(-Pw!&39G4o`yLA$ml%$Sb66TJP6-muk7YSQP?lI*Qm<1 z$U{TJFVSx z5q_onD(nj0Zed&g5`K5Db|Ar=E4~x%X@-CjUnjjzsRnEVxj8Nf@2lvBn}iC><#~=q zYVbwgulM1V2!Kd`Dh#Z`(Uq^_JLJme+;~aRdvUUVa{WZVyGyg0m=@+kR9=6Rr4Ko2$DQt zl~+%8#A#Cj9TdfE;UEJTG}~f6y5SHFf19_?NW&{+CS*$c+iNR*6!t_O>RGzQgQ@tk&zmUaGF}ErL8}zb-l}bww<#~1eQ4md5 zeB~``Mm~%hgKo+TGdek?!%@@4864m+U`M-~x@$M}y{y^S7}r3LKDyMUWg(NbR0co z!&aR3XdEi;D0+}Mm(01=O9w4LLfmgcgl@`vie63PZttRT?4^8`x2=Oa?N$hhbntOx zW>lQ$Z8{8!116ZTwlsd}JjY&4X;*Rs*nA^H8CO>|p7C(i8Z`b2aopEi?aPKsJkXY; zz3C7=_tn;5(7%&NvO!<4qO_`QBE=dmt}Qo{*|o*W=I615_oFaN7*+C$PAXz=*ZWcM z_;_+U3X{0+m%}>GFzVnFF>(s@sH;@01O^IDF6NETb6fPTab!y=Ku=%sPD+YLAkVdP zd{>$0QnkqS4h1(9TOjIDXjjqrQJX_R$Zhs>irQy30a!&qH#w}x^5~XAXJ`BCt9$ET z&pz&aUEkYzx3zz^_W5k*^VhS3{q?iYduK|7Y|tEMT$p34|c4nPrOVu9AomucD* z6#G~K)~`^%fB+g}Jt5yIgc}kPpfEv~7@*#ckpW#r22de`olc+$zPdDl#7PPNVnWBm ztkoquA&Gg&+mafwM8)=GqJpD=qMvLtQL*h|<2%B1ZDohEG&qaIDz~T69ov%bxSB$D zpvi|FO>ZF3)Hs8tJGK?wu``+OaO>R>qu=B6UnQN;)pWv@p%ZYg@yU>k{8dHjPB_@B zk!@fQo~iXaWO2c&A6}#g>{8sIs|;dl;jUtkG2ji+OiE%589Gx-C+m_?7yabC)hFh< z0@bTkU4_$+F4~nnZmd!kU2nANj7guUOxQT^Oo>?;jl;f7RL^^Hry~1l;K#ib4y+RN zGqvoAZZyWm@~<7vtr&b>_SJDR>@t){N<#swUV2DqlkR`Q{U?UrleL_MXM@%qNgWyw z>iTRf)I)VS(mb8@;$GJHCL1B%VSxixtnorqoNa(ZV04Xpb3i0h@LpDh#)jEfbH`4Y z+~caff}3uD_;A_q?qRh85~>&Dyxu){>8gnwcp34V&J555+O~@9=dJe7iNgRUpka-h zm$Qs?1=DwZp|s41bX{~!oaYz3aU+ipsZ&{~(kWz^R=t>`?6L!p$)Y%0YiIiyPdd?bY;-jKFhxU#Z8uglHv zRkS^A^A{?hd|tsl0i+FZ=8q;vWiHIgWN0u}wCn7HBD;7>A<1xcirvNS^;@thajy0O z_Wp29$BGpOFzKXEsmf+m8x3;UlnR#Xuc}McdKqz2c;*O*);qSk!@=WYkAj%+$U`F0&=$^ z9?A)~5)HjABC{J2M@h`>jMnQmXh}r#HasIdvs=V7DYkNba&{Ws;!i+m;^o}jhuG{Amm)$4Cng%GkCeaolzwsc zT7F?q=@&YsU+9z`+Ed!%Q)(l)hBTsb;aA>@N_yYEh#;H)8ibvT2pT*OXJ@>*&dwxC zsHN=fi&1#;RWbfITeP}g3c~ewginMY3bCKsPm0`A(EX$XF#Mkf8$LMe)pgeE1FhEw zS})5ef^LM#+piplNAeu)0(Vnv%wx$AJpxm(LEE)id$Ze=xmKgyh5FmKZ|fm&y^~c| ztg5|fgZ4TK0*U~|dwjABGArDTRwY5i{OFQ!1PQ&$vFW02l~_#@**rd}ow`aP#|-M+ zd>ZKSS*LCtMN}xHF$|JHg^%7YPBX?v3DK?^i5xqOfgpLB&B`P(+JqIba(7H{t7<|y1|cIrxnCj$Y_3`}+v z9NC~DqrX!0m5RRdn6FMXyJ|)Yl@umbO~sL&v9`acDbB}7Y{J`xBm1qX9-5@u%5LMh z+Up>_*BM#qG=LiB?5Y*FMKms_c2cWxa^rETr7R?JvNVSlv!~qS_(Yd#0rJ4iaIRnw zt>KhGp<*DDeqV>mxNtdQ7<5ZK+6($~h7)itZ;1V93Ae;d9jXHOvqgV)w9p2E>#x8a zfJ%BA4mMf>s%@RW4LQzmQVBOfmN|FY_;__CIs`%9lyk1#L~!1w${Dw=dl#g6LD}Z! zOt7i|8MtE3g*7P|1m?$hkY~G~I{$WVMI}$j=;&I#qtmHR))BO`ka(nIYE3@3M|@{8 z^`^L6Ey|>K{E;FmF;{P*V@?a5eHtCntH0kioI}u*%{t&^O@RzvT_zHJ^V&33nWfq}o*WWkCf90xacu6bMJ$eZ)(8HD{v+JCAbpK8esNNs;=SQ2g6M;=X;mWw*&#dXi+}N z6ke4O%-rW;LZ3}nDs6O>D#fucB;rjf9Kr9okjVEZJreHx@z|s_(aN#9o-j<>07ucM z3ijQx<$)ZnKr)AW!Re@if!uc<#HDbS`rKRUOOGpY4)G=wWAxcFV#G9UU5M4oRve2y z0TbEd8>PPDC5L}OG{~xUi%s=W2XFpLW{My_^_%<)S<+0^QE|KP=i4F4Yt7Z#`9`Po zQ*<2u0*>;Fjw8*4GtXFiK4Dn59aly9cx;J4a$>hRu^+*q*i{=I%k8FUP-~LwZt_-g zV-l9#gdMub`X+73O_O2;07kjboRkBwINtY@_D>|5vWsqxHraRwLW=GZ^QBd-|v zRJ}MC>$jqv7b*0Q&{e{m8W0zFMOljqRKS(k^Oy~2vG(@(@v*-6{LS$SVeC*-s0t~B z@C#{;vl7<6M?4{*tR#KNZiI+SHAanoE~Juo)wUEEbtc-9b7g zH!BAXoYNZmvE9lptKEKrPSnNHv*V!g+#3z_;}tQv>jJn5$iaTG+(vJhW_spH;d??M zhFWg9*kdDN&Xj7!zS5|uOO1*}`LooJ$A3vFWoQ!TDB2(Ci(x-HV%VVZVTVBqST^!n zAGC-CIBCtjY(oJZx-{Eu^;|hqF;gCYgV5bxa6iRQ;JwF?11~| zxmYsN-)MLKp5)iPPUCETKGf&FaTI#jHYC&LB5dr2{?!eYE`v5f9xw`b->!lTad4?! zAWs(D(eFe(;ilXSTZ4(fPoA57O{X}Uc-py{A_w=j8(9i`YNLOj+PpDp_@Mu z-TaB@<`0(7(Pcizhd<^YN#qcJWdO{{C`;;3Y1QTZ~< zz_WVxjOXk}T1|!*&tN-~KI5Bbp(ALV!|G*r1--nO)sX?z%JU+O(NO%RD4uP`kg8_$ zQF#t2>B%W&jj4`lqJzRAVpJ(%B zy4HB!x;Q{3g&L7!HE9@8Jj`Jhu5@J`aa|Jeb#F*m6s}1s%mumrlvf)zN6b8L^?^3^ z2S_eKo*3imgx)pp)E47vP__=+o=SVSBEl?q81PSspZW4?Yk=X1G9pF}&y@4AwbKcB z30X*jXz?b~(?-JR78yTPvJ)-+zKmuxSiJ!;nB2St@@Py4WK>lfIz5%<3u-ARnL+%H z!+a4v`#&d*cMHd7t%du4=C#^tZGnE)Uhv;n9N6H%1_u^5Ht=`pC1+cDxyFAt5U6iZ z$|7o5;GZ@6%h?v|uP9};#(|A>1eR*GIs$7ism#V}PT5#nrNG(-2R1g=PXE8!v41X9 zYYVTb>Gv%~VA`a`4)voq)U0S~qfUsOkiZN9*%Lbr&O8NbO5|)Q zq-04qo@oq2YdE7xlvfCeMq$&P)=ni0Wb}(-G!Xu`*~pB>3}#DrGt9$Ic&E%J3bPnd zi;EZ`ewO6Vvb39MWl*Jp?vY^g$z(z#H1WV#%w-CxFM(LnM?Z=~W2Hf1u0+T<-Zq*1 zPFqlP=-mUhZ$%w#ym6IWi}kBPb|;lqqdSIWJG9TsTB)YgX~h>}2$RJ=EKY&YKwh%> zb$hGiYO3hK>ZNG^WMw{`iwG5{TcoTm5NGRv-h|G4lQ7#PZ{Bqzer6J~JJ{S-(?P!n zTUsczR+W~YrEf`bh|Up#ds+iJP9ZKkqAc-6XDbL#PRep6K(!CSK!nbz3@@}*#A$d^ zZug<{MN@}l{^nS^Nnu@6W_ubHlMU8?<-Kdf(f5Mj!0GA4-pPAgfUVx#ImvWKv#`z` zJ*l4-`&w@MvxHVKEf*K+8ya@~K2I?Mtg>7_{r$8oa%=tp9&5OjXUU!*Q8YCql&E`8 zNW=!#TNo#_F?cH4d8#@@OGhTW@A`ww)}QyK(0UobGP&76+&(Tp?6_76aeIlnv_XyW z*DeT>5>3xA>8EDuzlVL7faCA|T!%vGLoRLVBu_&oii)@tMDr`*;^QWH8q%J`90J`ZtM=g_>9?dLYdmfNbPIy1N>f(5DkQb* z2+)G|0^R5KOvH3p7U+yz44EcE!Tv&mTL|Q}3~)z?+0YG;D zel4^*KS&E#Mu2L<#jOK(eU(uWFfwo>r{ki3H@IAgiA5$qLGfc&<#;a zq(Rjts)2H$1J?_;>RZ~^BiOqFb>d7-Uu2lT1-gDYNL|2(8u3+Y_!Ftftv|1N=970~ z3D@h8*Hv==$jm~=;0A90j0rXji1&m}5J`${oWQq09qdA0%LM=wkX|LzCthMqN_yEE zxLr-NWT5)RpL!w4`01x0E$RH?(ssj-wtYc-@>48yTBx6mKb(l9+r@RMV`vcAr*4pp zPjvW-Y@z!-{OA~`M-;s2b+D;PgKz6?gHi3rHB9U;@~{$$!HJAyp!5ed6+ z-s|*CU`Qwog|c*EK(l&Px?X6}r3FrGuZ;j$tQXYE^#astlMC=#E;;@RM^E4nZ=GwV z`WFN%&y{FL@jkkfpJp4mp}W!zR@Q8x8!)J770J53bbHkYc702pJ4(0e!2kAsP&kFL z(%KHsgGty$@s8zY8X(qly315(2=5eq9dbUg*f;7k>t!aI@X3E)2N{6^JLBf&rh2o) z+>H9`*RP*(TV)r(W1<=aodDtKvyZh>pNtuoPyg*5`M$kHoq6>v*66p_As}bW*duJw z`uF&gi>Lnk8-1tBGq4FoB~`3!BFeG?wVIb4^SgOuDifVfhi%F%%d{k&$e8WbI~n-z zbsx>tf{`ciHZ&eBMGjl@f)-d7tk8N$QGO-YBxufr3#nT)lE=rH%A#StfCS5cO%sus zkbZ^w{!H8y+`;r6bG-cCVZ+gn73xKfejpG_S}{b$xuQB(_R$eeQemMeqj#DHLwbXK z6gV+3U7L@==go@**@`&HJ!%ZI7kMKi3Xo`S*j>gA5Wl%k?S)A(hBsjNbpI-OBx^^$ zT+*2IvkdkDk-9aq@}w?@$%;Reo&8Ee>oj4A7+)aSTWkN41|!V#pVF)R^_1t5Pfsgd zg6;l0*CnZV+ILAJo_1c+6Hj_CITycmUvep)_Fr=IRQg$;oi0JjJk`m`x9Lt!#H6a} z24@Nj8IGrWK{;|!+aha5jCnzU=pKxplVf;65tF^3c*eOa!tsK#TW~_MYI|`{y-+6J zQWqy68HQ`{6^z;~X8CAfACvY&`zMSgdRO35U2*EOf5w&4KPs)P6s!c#g63+JNf7b) zSS~Ae&VT-||Ae)d)Vn5~3EPb;A=$xXnkOC~KPcW)x!nAqbynhtyMw(W$RuQ+x&SS2 zsB%q-SL0!s46(lg=7Bka8-I}DWS?aYJD4bO=(Co?2b|!>)QUz%I|aJ*L>IaOKow>E zbD%W(Hx>=JaB`|={!q?*5*?1lGYVuR&N^$Y@Y+efs8g5+6>kg7FZA3$~eu7Ryx+p4lSbMt;81Ck$ z&hJIES1H;cHsuobHr>#;&gs)ydcqPAX1rio&@F-`t}4f5a5(qIcp`htx8z>5XDf6Q zL$4Bu_4Y<^g#)t|JHS9nY#}q$B1Aev;k4Z!t3yY8?mb1*29g}y`U{&--eSP05|*5VeFKnH;_C^C(gU8%Q1c}Gb<;Yj zy{Zl{$|Y2N^$8iOlCbgeO{}Lz={_eNb7Ci@D-@g*zVW^GNizuq2a~pkp8Lng2}Si8 z(gn5rLG#%^>8@#=`LZ0-FCD)t?{yiQ3KOlbW0{bd1Z!Tbk}hqMUN4z&GnT+>HS!0b zy)>BJQ*iIm)3sCzlTu#QH$IGkwN0gJI&lhVaTP^*2E>tEJUZU=iT$3>2ofkDTfW~S7^TSr>iNOdpYIk8L(6kefdXG z1H$qi6dTIE;H&(VCZPyQyTVAwUb)h=Osu%4mffc6by{51Rem-osH%5y3Mg2tn`6y< z^8(;5LIDG`eJG&ndTvHEWIoI`GwF2Eh6~l}FMa5~Y@oaaDA^=nI^}#8fN3@kOtZ=7 zV#mGhG$d1UUL(TweHx8_3Hh{zN0r=jXFRTD znJZ)&@>>uF9=3l+XfcL&7j|qWh@%PZf1+K-iE>=E0Tg;b7o;D8@qQ%Scp=sF0Eu8i z(_tfZUbi@|30_A|gVC@uX9K4latEvnDV~|_aKPK4P=o+vp%UIU&NiCO6izfFtg3+C zz+O?_2AS0^kmMZiEopczw~V=&$-kD`ndEkYx8_P2Ta5r-7cP78tGr3!vV}08_48M7 z)#%wmQ9|k(t^&bvW^g!y|7vGxjUYCC(Se2{g$-%V8bU74=Ul+-PX#*1N|}f*o%5t~ zr|x5uiQ}B!#FFCGbD@T5?4ZaE3igdJ@mKu{J@*wV?%EBg+8Xf+W{{N{u@~7&yGr;2 z+#ahMN6v!&8v0#wTT!SA*js))7S8ltIjb{an^iT|B;O%eCGM)pEju1#CHyNu4$ge-qHn6C zszGgaJL-TZjKRCqs$ps(iLC1H3bROS1n95j%;RX#rqW#{I#Q=Gd$eKFI9yCN<`>)w z{C$%P?8iqAk!Arfu!nJGr@9CSQAmhpW{P>8wfUVCf3TSIqCGCkXz9BBtIo3L(Ul^SNuk}n!kB8 zzMWidUw05l*@(wa#7RH+#*!8+QwLBR9ezJhBeRjcrW!dD> zECy^TzLR0~IA7gtxnkBOWQCZGOw+N<_-`he&o{a1&Gu7Pkz=-*i5P`Vi# zAj}&sX854Mk#hslE}Vc!(Ct<@#}f`QCLv75fxS85iL*f0KK%rmTIoVICB&S*%z(!V zv^iVV0#@6^caJlw{oY<*XaaA7h!`5wwP%z?5`?7Z@I-Ewg_D69|JmLu7YW8`GH6nv zbcxB^Os=IBFR3$1cm9)=tbCVp*vT2qa4=Vt1Mmj=6!B*zsd~EVh7|l<4#TZso{=^pRzu z_>jK;aCNm(^4qybkrX^1_cI#c^~}{wB_U_DJ3RiZpGvlY9LATt02O9;0XD$vRx! zcsyDXo%6)@Xbs^ zEE9bn+Z;0vCZ9>y%d>G2nU}E@`Tykvh>VkiW6-ysj`3n)N zQ^2SBN@QFc^&^uqoIbuOTg^`)0E zonnOZcO@la?zdN{?L|#hrS{JB8Z}#-l+Ma$XN(*4;^`%d{eA(QpXH8n8)^K|G^}C^ zF5^vU;MlHUx9{Bac8Bo>TQDBLP2IiJN)`&zSv)!hmjO}@`j*fgj`Xy`Rh&}WZjXf* zNO`armk)bmaqPt?8;d4PMMYlqQu0#0z$)6tHmzQ?7OTn4zb9G5jy}%N(@r88;Sk(f zVnd;ihSMpnDnk+lz~w;-TUK(^12sLBgAnOxYASId-1{F#lyn&4jC>y`Y;n++H0+w9 zWw(nx{hAoA?kDHYc8i-HWJ3xh@I;TF&@JP z*dA)O7Uq!}4r$PCSI;t+&~&F>lGEb~7V-wblEe&Rl1X{uC?SE9&`*nKV@)81K<<93 zW1&LYB3VbSB{{ejenuy*m>my_!F4X!TH`Sjvu*?99*>!r^D_&Spp$bTDj1Cc!O*bAGoDizaBu(Yg&A<$L4u$})C~CjN zR!;ZSfm^C$^XQD0ZxhSsa$@z^%q1%zQq*^L(mYwH_C1C+9;q{8W?6*hiod9?8VTf- z)i;dD*U7?zgw+DKN~KJu!sQT^e2+~oWvDqlqmt-wPwjTT?E8kP;d;0yH}|39J30@r z5uG8$hQ{DOTnd^}ZBP1(h{Hpzg4Y+y$`w9|c@hyJ0rt0y&fTq&>|G4m)mJJ*TX_Ca z6==BVCY7mC893>)AmC>{sr!EYmwnZ@t)Az}!U)kP)`7MBnNN0t5q^_b?zc?O)a|h8 zwJBXT6%dx!udr(_NH$3$uF;4OUC46jS|or4#t;QGY@h8c;YUP~xQgE%WRdx1%AAN! zXa`bZB6j<&i*#sXh$=sJ}w0IpeO>L_x%|#b(n=`qgxkey6Tg`mmu49a86a zJJN|9&MYbW%i_dPRPnenn%ZyGNa|Kn-4f45>5lx8Dy>vM2b&$~N095+Q;Z4BN$*R= zCw@G36)H=6=j4-XG-+lDh^h{vdx_p|ilejed*979=H4z8US}pbqzmtcZWiCuUAUFFiH?7~@UvI5>vAu= zRb<4^xIjMaY4YKxMPh!^<3DiC14htB58Mw@+Vgae=(?VisdC$gp_`I2RjfaHd6U!^ zUlCPFKSo!Ry-eaIM{+9H?%yvhuGr7U=U%v-$h3ZP5%yKSt(`^N|Ia;l7sGKd*dpx2 zWCay0)fXz;G{9&5_^}o|*0?=*j(V{iUi72o`s?MT+On=^G?jVr#bSL~JC0jiUVQaRH@5uh`O6o&z1J`6 z&rP4j98KNga!n0i&ZFwa(yLc5YPymaFJHb~tgBj9;)fJwz=gd&sLunM%VYpeD(}Q^4RCXIR z+0r`9ApR?FuCssWB*Io*hdEwnE`lPSLeDt1L&kKBJW_4{m4|n>1Rl>U=GFA1(_sag zYf>IaLhx)Z52Q{GFpp$zAwkcxxrHpu-$os20a$;5#E#wu=rwt$B=yok@8x zO-6svvMF-8Y6X)5-;bho+%r$^1l3{IXIP)y|KFY0Y#zQ1|BYvLpqp!zuBPPf|EObi#ZWjOvKF2ekI4qfn7MwTC)*LzDK55!oQo3K9cCE?#C& zxnPTW2M8zAf$-da0hRP($h+Qen-BdZoZ~|9a3g*CwA@%9W>H*L1Xc>}yW=sKabc5U zPsWDKlOvAFR1kYIZ)Q*;Lf;blSs;H-@;Aob>NtDrrti;(-$SfNb0SD>>V^~BFay-2 zna^d=X-ZD#cz5IDk+kfnT_rL4MVwi?ARZz%7cIby|BYB##($tv=2qiFv;rt&p~G@R zdexpy!;eY$u@}zv!Y{+{tWR{_exEkvjzn9-&x#>)tHN-2#vZdiwLDUL753MGOh@}?4FE1P#g0o*)a*b|3|q2oKoQf3_mEa%WkzV z+1rRREh4#Hi4`tON(s|(pkWtjEuC};N@rWW*vL~5!aE7&hMIQw0c$zZ%ou`D7$Q{S z@2bUdbMvf|Ga4_BrktPbY<%e z^BJpHtVMo#pq>X-;GVcSu8Vz#$!pDpirYs`61EMdZwEs*MMCWs(%C=SSY=4LKMyD6 zcJ`0PORJ8K$<6JR@X2`QWAt$FyGJ>P00qMLXrc9C$>^s>x6BkYVxWF0J8Ps z`-yrdICor2EJLxAR`jCzd=drRFX=+`d{Rn=BeRB>;YeInjsm|mcKD&VrOgJ#o%Mx` zn6y7@_toK#RBRMj1tk4`0Woz+YiW$VEwtIBL@skyjOPJ7u0D&jGdyu;4CRHH= zbV0?g*_d)8O2S5npaBq67um>oOK4cGw(aM?1XvJaiL34=GXI3ve*(mAvJW zx4I;bqE-g_FbwE4IYCF%qVODdjFCdX{e1!i0(d+187e5#Ifs?$oVK6+z-9WcGk3_| z5P*^TT0pOIf|vaG)_z~BEI%NAzt4NcJ~%ABWSdNkV=EL4dE?V&;th?B!3sm8iz*=q zYvvOT5xn&l0C5CWy?IC6E$4HlJM(6na6GBSKXiKtOm@g>M!)Y|Q$0~;DqzVGienyP zuei(}tnn@$PlA&4(Cl1(1rPcH)hUm}hazJu1cQ$bi;kgw3E?_7mAEw8!%g7 zo|&n@i2Hw$e_X$ii-g~2|E)Hc|CKhKA`A*M-E?LXJdx37DXdJw3p({7qlAeyk~-(D ztQ{hgw~}N)Qs>r1OWqD9rz#1K3*0{~)Zfe|2Ad!?6y627&;9QZ#LBrp9kVBAQzF4T zZimB;*#d>`QbMl8UO6Vh;xZGuDxsyRL6Qtgf_#w`IN_N;kt!V|DRhAnB2DRDwi1$H zl8{fxI`s^Zyp- zh)h4mR7^`PDL@^;#&+hLJl-lxeIeMeT?Qm=C<6{D?8ew>wPdl1xF*eKzg;gN^=Ys@ zyjVlXn&tM%;PGhN1t&5ekbh(qe=f-M;=G@0QnA&=^f+Oe!Chb{kzRX{{vo`M^6pUI z(8Im-67h^BA0uz5UO)}T6BY5+^!BVzI*Z$0Hcm;KaFKMrvWirel%7aws}186aEa+D z^ma;)I_+k-xG*Z4ycL|vZZ3t{HQcm2FphACqFQqEs3?`o6Q7mU10H3q%Jz;F=p?)T zd%qxx6KO&Ab~lvl=B=bCbNif*3xWz_p8AW?cnn_`2MVdYPvr0Wq9AoMma2{KjZVb- zn9+V*D$#vH25I_*Q_z8tvbKfZ??JqB5Gx~ad^n~aVaGTIYg$c6lGBp2Ox%O`VSUJ^ zWM{_#6>9}xd@eMg7;8^Iuy3Fl^)V>4g zM6dO1v_nK+#03Ps_*lXjJ1FG03)FiCXOiCf2uP$~K46o1^*(|_!s+J>WC2LsIHd05 zuQF2Z2!-dbmy`iiL8X6P8vhFTTH`wK3)Kt~Lp0d`nqjk3{dBXd7pa*Dh2?0aV_&IO zamtmzctWG2G-;ISA?UYYgD>I5dDvNWB9%BYW~HtRz=wgXTXoC#0@zE( zdSJa57ALex29I&ouWzLoL539C1&T8is9EOm&zLSquEiZCIXjy+c?ayz#K#jGg}1Dx zcC}D$B_78(ZD-}h`aVBD?~nD?O(RR-Cl-S{jE$2*BeUVX9Ea>Onz)AtDS2I#4}C*x zl!v=}NoE_c+TeCle93m%2@7m4AbJ4$2ur02ridUnbn@dPz51&j0}jV_7)(CWuR}Pe zrSDM~4W`;_k&@*O1MdK~uCFimBFUnn4N z=kF*ZzmBN)ee$#Z-F#6Trb~QRznmu<_CMhPc9bK!QGPTkFp1dr^i6~V0yF)OMq?m=x{;b`=3b`;+-wiKgqfp-{ zq&xSDhdthU*(6E;3~80g&F;PWDYhH^5ylaSMI%-!2PB?SGriDH^(U6Y#2?xCqo9F= z15#z^r%;f@!UeW}M^E+dsMx4;`;-@KmzTlakuSD(Q)eYV(usT7u z1lhjkc8~arL-|^q?<-isySd$b#!QkK@euR%TLa+i{6rsi4$$?ESq-Y?h_~=AYWX;{ zwa~)|=Lwvm;WV@+4c5z`2ke(piq>8vO+(K8{s^|$eb2T?)4&~i&&l__Alyu%hxN4G zDB}-2^OwVYvdcYh4I7a2gj=GUHOkPrPHyP493Bi*5L)%&9@`z44Mh(Co%5nkuxn@tQvBRAG(2xN53{e7G3~(_0BB| z4iKq3x0#%9m^@6=ua>>|n|92fo`tQ3O+cvdz2p{2q*m?UhBQ}ah`*{3W#W}JygHcd zXU$q02f}~lm+BvtBCpb3x5$NUNUu-;q=Ahp5t~2158*u*2KcHhqX(02NR8Z2!5GG- zf#-m*0@dr^mYX(oGj6I3<_7J{zt@Go-3XxWuO30&O+eklp1qrlysyD#+AxKtXR;Zz z;x3^!At5B}idY9R`PkTXBJottclAryN_In79m>f{$qtb5DZorT@N&n%{~LGX5Z(CV z>}AQRNx31q{=S;9WLk7=#DvwJxGN?sIGX>!&3{4jU;FlIavvV}gB$giyq6*6*~^@3 zUi7opM7&0(=sQ&9lRSqtCuu=cojBSWob_mGl}BEcM{boz6RPwwOh`8^9y4x>k8Qbo z@U8$)+a8Hlpi%W2cNlIUTs)`YVH!aoQeL%;Erx_ty|O0#OX*QiHtir|TT$6M47?86 zjQb2Xo8$(xWi#YkwNU@u+{|CY@Tf=Wm6^lz@$s+W+}x`-^tWDr8~p_|tH{`d} zcTzu5c|>tT)fxO@RSGhFo=E3MsSH}is)76}6Y?xBzHUz9w-$Mv8diTpgFH$0aOu( zdFr_PrM~gq@>g$e?pvyChl<>+f>XcG#?t#o;Ha|{yA8ASLy!z|vFRow&KB{z!1~f$ zFq(}}*@qO(z!lHiHIXa~o*l-)oL)j9vfGz1ioO((VlQ+m1L>s548{(>MSJy){L;c! zGo=(Mjrw_Vb;6{(S7#ZG^>)fy9`&d<@{%Of;Af)Vr;uwhc?D?yypJB(6rP+M5FcCr z5}8fkWgS@H%FEKj0#VE4IRWSwyJC^vD5MjGG^ID6=$qf2VuuRAm)o2%6Mgw}%gz{d zEBTP4Zy*yOHw$({e|NZiL@sK7yY9E|cem*{WmbX|;AHPl_Lr-C>e7WBD z!U?RwsaF!qxpZJXTNSRUj_iu)NvBE8-NO4irbax6DD4L^zi3IpKV(-KTnnGkdq~=a zlnK7)E~De`NisMy@y>aCeEnwfW#p(&VADLXZ?s$jVXfVQ9%_6-uj&vNCl*Pqms82m znxJ`4(MFn~J~xL&yA^?6wv$tARP3%>aHK=_>JWsm`oEy1a$w^iGm6a8ua}e>u2iVk zA$eosO@l9bGwC;7SMI~iH8nR!wK&$J))B8ULbpOQWsNScRU6>@Pj4nUM#r;faqqFH z)XDAY@?2(qX3r$PuxkrNuj8U$HK}?t!%Fh?XH4!qqX0EP%D*>^g_I-{{iN8Enci~B zpq*Wb*!Ox&Y~wn%2lI>VYbrmzH=4&^3zEa|9sRheSyWUIlPq7?UlF_fb0d^?mjX#_ zpz2*ZO3e(cG00tvkIY0X_P}-*>eGCT)tlY0oWUH;(2$_y8Iq2-8CZRSlGbXhb@ZDI zl06?~zNfKqz;x(&TIQ;nth%iP6$vYW^8kHsWNaEF<~^B|mSG9DdPv?z+O72il?B*U zPNlL1F+wZJNHk=>Z5ya;UmAImo}3q&enfypuhtnWlgi)Fjso~_QuzaPB&;#VwPF;5us(>4?GBuI>zQgDVEDMio zaYyWYy%r);L_ik7bu1hsK!^mnGT8RC&8y@N=p$ZWxYAI+DW6YLVhio1N4@X-O|ly` zJD^IB@>%yi$_6u5PA%eq5KFJ5)MJslHryX!V7s;TcM$FPUq>Re?|$RS-%{uNMutnS zODxBW0=nacf#-N(jV-ZvV-GQ*T!rVbIh}m1yZ6q}j*S*9WdD8y;6;a)f^CruB;O+# zYE*ZjQb}5so)=&o9qeO2&P=Xa+feyHO^k}uO|1Q*X7U}WAqi6xm~?cQ8qofgE9?;2 z-Yo2^jy5tZ>Z^V%E;c6$OiyWJi^qC#B$QZM13RPFBqCNf_IhufIR=iy>O%;A95+ee zG3@KMq$e~trXopKa&<5P;y69iV}~YLB8P7UFP(;yjV6W^#L46ab;09e=WQab7}-!? zdKQij$%|S?i_tj*jW~E5zT=(9_%(Ypl+S1Z2jI74cO==G^X9hEpBVyjf&_+?#Ct^* zxHAPf*5toa`2#);8o%SsR3SIANa8iC$QDCgKjFU0eR;pn*}(`6G13)s{?X~k8aSJr zhKlS#Xc`1#m8)N$kQRD%1WNam?Zki)vCX~(oa8do0j<`a;6Z2!SJ)>p1q+yn`HVma zS*MBknhZu#h17^rd6xWVF;rwlE30O%t}1pvsj3S72&B^#x!8lEV@p&mn9gT2m_kQw_u%Cw5RN3(E!c9 zLE(_&Rxu zH)Wrg+)OOFnK|U9%AqGwk(;cD>XVz9LvAJ{ir0D~?=nw^{J7`hV({*y83iYWoAH!X zNB#G2jkC>pX#bRN)OSV+v_IMD zC5+2ix18KeQ_IK=7=%`nS$FEIi9voF;BJk=tsFBI{?(!Rq7}l|lQgjOfang-=YV1> zsJCD~q*ZrUb41D78cd)p^{B(37$|aah#vh1j06L4bC8X{p`_GTs|H z`EKtTQ_f*GD!1U2m<=;mrQxGA#Kg}ov}pD3SNX`iHjX5HV{IEr;%RF`Notk+XIdTdup zca$GHW!Kg$Ay)FFyH2uJ+^? zTtBN18r79_ucwSkSv$Ezddd8UUXdT@YF)$EHOKIkYgXS>xBjHs)i>2Y#6$NqGSXRA zb0#fSrJiz0YAEv$HH*zbFWM+O){?T?E)fg!fK`B{x$dfC!@I6+c;%c(HD%S5yx&Vw zn^orGe&4jPa_kBu}A|>6jUDWS3m3VCQbe zGR3~#%JarM=+rHDX*gyt@b9W$LKD9MX=Ly(w!k;;VWMv=+)M*?L(y-hZYX_ywEyJ# zkZ`|auYfD}3Xsz=33R(Kw{2kVlhD?JSv3i+wTA|m?s&u7TfV2^tGZIww=!S6`f}r2 z%mct~dE&c(2UR(QotXu?{jN_KNhS`|U6cmx0wdhzrYP!Ohw#D)wQcQwjFfLCsApSL zoTWYNZXW)Pq-!F_Msw^PBi$XMEOTNyu>KOU9}MOX=ex?vO})I%KFh7!!o)u8jrXkY z=)rF7+bMxd8MvQWuWEK*c!+@4{U$paTsO^yd*pQh?}ATM9EQb&c3+Db&Eb*skc{W{ zq)dnUbcZqC8N#P|r(Q++qrQayhK{h;!;7KvV+aR?X~B=H!@_QRE|as9gZz}e-5Z9N ziq!it(B4<(;b2|rKbM>AxnEJnbd8H}ksrp6iNAq(hfI5y`Dn9o(ntPq9zLeE+0KB2y` z#;GaaD|phiVqT>n0F%KiFmAUbr*C8Ec<_MxtvtHVRE!p-=g~gB<(5~^K{Hh}0Zt>! z)%u4s=k#a;&5C2Prk*vjmP;x^B77K8&Z_sxN@pJC_;E;eOVb^NeT;2G0np;$##?LF%?nnUBR017O{?*iw3C5!A zaE|05kB{`TY+%zg`_ZOyeVRW$uDcuR6$G9RPtdX0RV^$B5&&sGugyE(On48hP}QNz zp6t;c7sCK4ZAyQ_J=vMPLTC1hoza88LtV zUeK$w9YA&MQ5V!QK}ko7HA>8gN031m%uWu$kM6UzF?a?oL6U2ApTyv^cG;nb)If;s zTZ*#i88QsRA7I5c$Cb0J(#<`=2KM!*A@XEfS5oIfmA`tfcO7F$i)NIiWQa_ybSx8V zW>GG-xE#;)ld)+Xosqlty{PSjyWZgPNIS~!>Q{6r&yfeOGmiV^^+MZ_B4O2Y z@1#sd5&y~CSQD+kl@b{;=jp%dUR40zgQsWWiTIz?iR;B^);SPcc%iuQ6dSC0u#{#A5&#Sz{%haiyK&E&>4-Czn-W zaVh#3vrp*K9ux*EfJw1$Uj_F}uK5++FM#^`j2_q;sRH_8Bt=(HL`Q)|4k+Qx=^GbA%eu&O5`HH_>S?k|sj!&z}O@KAfE| zOGt|`s!bYe$55Cp3iuX%o76q|etb&rPhc2_vgfMO1FBO3ci#P6=TB4TCVl%=q8Zfr zQ}8Izr!zqd6J%0T1~sE=2N(%PT*Hulisoi>Gy0TPa3W{t=NT?+Mx=5c9RN+nKT3qw zO0cO?L0`@;Y&L_RoI)+lE-dJj&4t-!a6kz-{+l|UGU(-wqJzgr3oc}g^NfT%6Is(% zuc{mUbiIkFK=aJ*&sMk6!W_|{g4^yxm*Jr4p*?Whk-phSPMtn zB%Y>jLOI=MU(ST9a}ry95q_*HAP~DMqR+x8K72>(0m)AeMSzq`^`a#{x22D-EfEbe z9(~2{6?Xesp*Nnsi_^ICfV&ebUvQ^GgVz3%h~TP_!BtZKI|Q_b78Fg}s@jG|ooWji zwOBz~P>HI2Vcwe`^(r1eWsWL!m`;y;Gk2u$(58uK}cN!=oV zyY+-34!Q^#_m&{3`k@x72a_>Rq#~CR!;d+ml$SmU<7++=Qglk=qlE^YV=;aR>3z$a zs3a)H$lVe>05GMLk8a&`igs}sp$gq0&BVQdqh?@|=2AA{;4#N#WY6V*k znzSKrjQ1(N!l$sL4Hrw=0+tl+3G|r_45B@t8R+5-5s7=wy>HVQYok2vbmb?{ed9C# z&>9@d-W4PP)YU`V)4u6xQDWe^)p=qFeRl|l7(w6Z-Y8|*o(1mv;$IT~xy}d8d~DdN zpk?84Aseylu(eh67oJ(f8d@JjPWxO2xC>_wD;9yy9qidoZO)2f{AwFHAe%A%#^5)Q zozrpOJM7$3<;%Q$8mm^Bq?>DnN{>6L|CBn~;bYId;dv%oTED!+2L2MoGb)urx*oOl zwz@9+NKX^ot~qW|!0W&~S$Bc_7*A6(f@0@FE$mSrd#2zXzlb9N-@2XELp!VM z0Oa`!I4NftE8h_pSti}N6N@&(b<9}xHtyx*cu_aT=wP2~@F<23xACSp_ z-+OTz{^$StPaD35Sl3lBja$x3gz!bv<);n0@--?jpX0)NKg)~HU553rrC&JQ$R)P= zF)!G-%;sad`<@ys-|Gpe)ZTy<<&842diB%}ZgQf1Dfc%opt=@!Pdt-qTog~qQI$`p z%8ekOu+6m!G{p5}jGFjXEh{!!IXVjOZgr71IUAT%$W742+FfHm1M0Dt3a^BU)0uBf z8+ctP$1BTV1Z1uBr&q>L(r#qhU3v$8WrD49yNj)qCjD!{EOE#f!017GRlb~e25o`H zT5ebW?3OK@%mh?C$2}~8al!F@?g?B%5`lCQ^Aee>vd_ji?yKtLLY|~SE)zCjkA}H4 zT;z9(pUsvHLz)PGuL@}8!rN+QJP;=f3lg1R(Z_6ALZfLPcm! zaUIV4ReftkF{3;+k|@MANGQrj^gDEQsW`r58QB?+OCDEwyRjcg^iJuRbD!kbO4%>R zqxWnC$IUi63}INk7S48h*#COQtxIG018XXCC~vmSGXQLqsJuyX&tlWFSoiEfb*g4n z$1*eDsnM%>K=>8T4k0y@7Y{!lHCvuPO2ZF{i6u#5Dr1jrUgNu2 zrX1uZg@q@o!2Yd?*5VYE^24TVqJwaY#PRl=bXGAFGW3o!+XHRTew2*m)Sl|mCagYkfEC2zsav2@_WIK zX*5-fi-1M|m3j5Ho8%iAwvqiLuWtH#$-G`@EYi|Hc`MF+cG$b#mX_Sx4KLq19X*0&`d6w=smA93oi4PNc-aY~QInpg7P=O@SMX()f>MS7e< zOm7S3N)&yZ&d=+7ro2@-P*!i-eNtU*#SuER4gUvU`HR>dU7Qo=cK%*?REnQVRa7Y* zYkpa3#`qT8?wj{TJPa%$d{b@kk1_AM{`LF=Ta-UIELD*kX%Cs>}f_iuOsTEY1{&0 z^`)a~BlGu;Y2>ebK+s~^S_+VJ-df@v?LHk5JI|q`}3|673uC6(^2?yl<{W$`B7(O7t(7HBWW}DJ#6ruk1UFb{arqy z{@pyQ?!3ss2p!b|{{USt>0MvCBAwX;9`@b5NB%&KNpFlW{{*t~OEj(V;P)YK4sv}? zPDb75*0qTW>6wH6#LlXK*P)0{qh>JjMy6hH&NX)V-TI}kAR|VkT#wGzL9wl8mGAxV zU7ruI^?RPl`5BEN8G`6}mIc z^kHuCYwX}ws~ONtCMOFES#m08_|&Y{r1+*OJV=`XW)YLRu#f^D;n`J%(bI|XVsgBE zFkE>q`J#TW__6J?VbKvp*S&`GVdc5va?u~``B>YHF{)i&Ti;s$y1rHpoySbrqEb#< zB=`XyHu4~jt#K4jhIuA7lE!LX`DPDU2rHX8_r|#H`YvY(l~TG?&l@h%VEuAfBWG#0 z82jP};wwy%lPH%a=3x=)t~WQQv%E;~B z(w{cT3*j~c-+7Zq{A#?)7~4t*9(tVyPB4igyM2gZv;Ig*Qw#5jM-TS#aH|y5<1e!f-1&!Tg}v# zrgM<(b8UZ_VhUD%Rg;iauzob=dm_>404gW5qL-Up-SK~oKDxc9~78AaU|^JBf~+x(*ly5lG3JD7+q1;o$H{88}ui-!+U;ieI1g5`F?B^kG(Lm#aGMn zt>zgsi-ioYrH1zeKB5wgX+3-8*v7bYH#nqnO=F?LQeA2aoXAyFj(bkaD{5I`#hP7` zMNC?HD9`&2bE-d}s#7V4u=AKNkID_(_mAZP(GA>+k7)iQ>~_UkvhLg0(3XV4Xd}R z5$jyhP#BtMr-ZLCZ>TES975KZGt|j#0v=_y!6~^NIv>8QE*)%1K4NTv#@o^Q- z>Yfx@Yx*^t9XD%OM~dv6HQP?no?kTawMlzNZoW`KunBty?QYtg!zr^eN_AyZ5_P{C zrsr6?somzh-x^94de*;5c;4XSbJpG`sDN(U9Y9sEHIMMu&JY&LwiC)XDZR#UkMhDy z1oN>N2Mv1mR(L)n`e!G4(_O+%e+f6omvGf8j1yHWK^~TrTP##-)^D@8ew)+RkNU{a zXT^bX4dW2iE{T)#aAMAH4GRr7<(h%uHybjno8IH5RlEMG{8KDpwv(T`MNI&{WDSA4}a2w*yrsiL7Favu}NKEDJ z2Jjh_*b`I-_ou-@gIBTN9MXS7H;`-dWCPw))Din&G_p{k#Y*sflqrI81k_~@|38Qc z_)Erh5+2tVdA8(YT10eDjN9OD_RY0V zYl`4#Z2FzIlPBB_1nV5gvFcIKMXL6BP11gwfVq-LqkB{>Y%y;=uu{G)QVpMaWWo;w zpJOQ)be4VQ1NnJ`6%s9to9XYKqs_iKM<0}8xIRiq`qQJd+5gQ^+U!qS+0DMYvYQM% zHv9G{sX_h=hiLlzd{C#!9W4U>f%-cq|4$FplU;I7*3^y_K-PrA^+6O4myG!P(Cl}I ztAF7gE(D)CT)UZbxPF5M;Y(XEe4=kV9yXCHl50Wuui{jSjcG_kLWeQ-+2)q7%CjX5 zF2!L(Pll3^)R`ow^(8F-F<3jLH1|2u!||RwnCk)B)yz)wy6~3xw^E&3k^%j@4$6Kh z?0>6>&$}I!^%zLNZxgF1Or&jLWLH#ETScPV5?`BY!+K5r8wuZo$U$p}pP_LywI8;4)2~ z*$LY&)-1%eC+7$b2rxS!usrf}SL&AylL)L)@?-I%%U`5f)*>ACj*a^it!(SsU64+WxoKDk zrbM75NKNN5mQ1gBk&mQ;Y;UW?Pr~F(NmP|hD8ig=ZJOoSV>r5CiR7CPNEt-Vka`fF z?nDx3!(~%XYLOW^q7;T}y)4}&jHPe8L*m)3n4ORK1&WUyBtwr)?#?z%+b8ld!Z1V7 z<04WfjYkfY6pr&Xi1a2=H7zXd0)2U*6b3dChg_Cnpx7Jmvu?2jQjOr?#py=dyl97TPdSXbw9c>)m=Z5DmrO}Y5 zqY-v(tRiy$33HW9PFkIMNN1M({kaNlv2p;YmJj=;spXA!1G8gIyVkME%TB_>2?A$w zlI-toug1`;t*~tiRch>2fuE%nM?_{HFZ$f~w9G_X>h?&RzAt2X5Ivj&`pS2|ih^8T z>-VixxnGn5+TWAEYpj~=3y-|3k=%>vghv^C{HVGMp6V-{jPiya1S)YLUAP{)tWhD z$4JWkqpYDc01C$^BOOUqmNsyot>Z?fmc%_O48$x=a%cQmlv07auW1hX7bFEN}f?w;v*NvkMtC{Oa_FwQJt{8*y;Lu+ioYu zE;{#?!VI~2V>XyG?}f0xfk-bQ3CoLnHa2vKvdg)PoV^xT&9~fj5-jo7V_;#oUsZiM zd$6iMot@b1=rzpA%~1nBnB`jCh58W>ZFr-5<`>Cq5osY`;#gShIOjHb;@sxNbK21d zGbtX-#2n1f2Xh)?l8r?1lpE4@Y_cQrSFiSMv6zeZT(yc#F2_L$x=VDnDZOn(gAqTO}f?pOuUUchL*F@)iXggdOrk!{4CAaQk;p0c#rc3ICwlv~mS!Jkxl^%8R z_{8)q$OND;QoxxM_t9$KWx7(QL+5-&dtp9)Xvf>?lUkx_&Asn43!wnvVKab9$mOxj z)m5*|2Z)Br)FO3kpG>Xt=0&HkvHZ|$R}pt(YPv1gx@*1bsjX#Z!~jo^fi-CU-dSiDa>b_RkTU6V$h2E+~!ZQ_Fvc8rY4t13ABfNf8?)(Y^&CWM9BYz zw+)XI{qcwWoxwKFix|!r%(aCN+=6)}!gc)chf3=*2<{I2`K-p2;m(L6OUdoOtSlzzNkt++365t>Z;mQD>y zq$s3@EW0bnBGd{av0&7p-s(=fM!!iSO}Z$xLu2Eg?=G3Q%4%eo#_x%-DN>G75xF+B zhDNb!PjWk(>UpP1Va{S#*DYpZ>)6z+C>O6sQX}&^qou96E8D6#Qc$9*GmmqaIXbvX zb=yA`j66EjB#<7fiOV>rGDDT#IkdK`6Pzgr_61ALJvQyWCLKvn=NN?PwP^J}4?AM$ zc&TJnXGbM{#xXZ`>-B74A5>Vg!)QyWDC1>wq||(sQ!1syY=jQ}Y^i#TYg-%P>-teM z3QsgunNFw=?n^qw2zuCN|9wVfsO_ovLYeu^u&%2|p9$LCfGT`{~f3 zs-+Lcmg=s!zDR<25>MYK-U8&F4w2RyO6$l2volo(?;P!drf0n{-g9L^7paNWZ|zL& zQ$zK|JPJtdexS{HKww?lyND?q**Yx)@u1L>Gp9(h)U=&j6)kDFUpnfSWJ6f= z_U}ya$SjI;@%21^Gg`~uqanx$zu}b0+m_=^>l6~a{iQiv@wC~YLnP_)J>pdZZ`>1| zye)>t3iBXi{LPNLLl2BvlDgv}bGb)*-f4tY@unmXBh=iFkyC3#uA*We@Am5JXg*gG zIh>BBv>v!qni}%w(9*O->N03zA{IBObVG^1*5MMcctzzuD5AB+Q`$JJ%8wAP;n(5B zPI~u4v=&sud`Row!TnSI{(?B8UJy1Tl%uB+DdSyjxe+e%qk=Y^eW27t#2 zj*CcRsTX8KVDDmZDCz?FLounnKQXC2ZyKGBSX@GTRXf!K~{GORy~4#V%vNWRpdSjs&8OR;3x zJffg4B6V9zRS$SjO1nWRU#*Qn06ST6UxZO_J$*!{rRWl5N(J4RAqxuyPj4NsCknaz;Q= zwkb;Y{{6Q;-ou-|kDUl-Hi7Qty(3W~ydW&`9X5w^3j{nwVUsm8Q%*(@l|S0@;v!Ln zsF}?+8;!PGL|-SIdu1bfAd_3{iFb%;Z2N3O>IddgglN4{-kx$FGOu(eQ)Fc5k27rs zw5pX2qtcd!-8<40P#a}UJ@NZXvGnH!@34@_rh*DghHmyPe##Z;Ceuh|xNH$-G<;KX z1QOua7&Z>cto?hJvn#JdUgK1+YxhL1?TYf0EjH5%OT&fhXL;}i0>pGf@tO#t9s?qM z{-}v~v68KYm(9E|6o3?={_4OeZ(P$Wng-=AYcxk0NS)BPQuWjrLe&XhYKJMGJ`@4} z_@Uw;g9&Y2PpI&0f!v%&^nil5uPWM4E$UHnYU8eQr~dXluZDYlk1@wUKHYlOeDe57 z^fXF6-C3{emBg4Zbq|Z}^uuD0kXp$v^G8LQM?skneWx|3kQN#a%ZbuqQxLiYF;pH; z3oT(uOSpc)iS*uk_I6-!e^H{UakgpRix zF#5y`Y^p3rf~BA5gFe8yeQ%LJMkaTV7RGWTi-kfMDgNA5pnQpbrDMAB6QxhnGTsVTDx9>0XM!S}N7!a< zocbzj8dPMzC{`|)eiZMk8Vt7gUuiA%S2TDggwkHj)F6_8$Tt1eJL%3p+><^?!8DALaDi(O_8XZpz3Ae{bUX>U+)$n)#;yxdo@>m{Y&i@ z^?N7%JR2xqQFEh@+@r>SRNZq4ea%N^oT1?;pG~@^(9t~W?p`Xmyd&Z;P0M@ygYew( z%IR==kbXN%=>&xwKEW1VMDH$d95aW1ELey?O5~4^k?_pIv9gdOuBz4}cCTMK;_`cU zWdOiN(HesD>@qf>G+&k7^6Fv!sA~3?zG@gRyAu48Y8!b1R{5BZZakzHa%F`hrHQv! zf96sl5*Quo5A_s67fO(y|30xTt&rM^MU+=dZ7(aNw!AF0nY|=Yxyynp`D9z%*^;2D zMo~3ruOIBgez&|2DfhK7y$w|Zl1Wd?#iyG#IHqF)kBIkBj8g2 z@ts9AEvQ~8bJEN6uxUUttxD8(;{f@qNf=Ssh4qF-2xHB}pVTAe+=4|YE@fJhyV6Rc z$Wt9z!_!PJo?Zk&%G0F*a7gIVZq|oU*{q}w%NCkomgbGd2($JJj&L~R7sLKAoxDRR z((tk^pxiP7qGZg;jBp|uU(!KNHhjFuHL_vXTD3JK$~dg85oUBTMEGFyIr%gcYjN!r z?s~7F32XF<0S7<0M0s1uBrLv$2X>8xs+z^kN5cNiN5ZTAQlj4LRuc6W{VGDb(=U>H?wRxZAiQbd|G}q2rts!#^7e47gM+kp$k|W?VfR0CExT_o8VLUBhtCS(Ib?7 zNJd6zO*{*Eh^FA*5aUc64^^s~e_K1rXKW+-uWRpKj)ks61AMM`Sz|NRSf_13V`*jS zL{OVivFaRBZ37G2KyFF7fdq7F8pv|72)FD@KPAZa5}@9p1eMg)8v=UTDb;dBYJMvHPP z6pX8<6GN5Q3^l3(`q5l#kwo8Ud7f93_y~yL9%641t{`y3|7v=J*`G>pfZoX%TVXJ? z-HcpfCNrm>hNBTkd5Ysyqc8gimrD!sLU0v+I5^tg+daK=y|8eTR^Rf~kcD8wS$fAhK5sQVqPv;hvV>h&I0CNU?u9_jJ#TNkWYwL&H*Wlt&7f>k!- zzLWeE|LW-pl~h&e+=G$l(3FiyFw!2Z9@f1ISkd72{_&?!?zQ0TB31OgmNLlNEOi35 zLp#FT$m2?E!O2)=Kn)WoXwx!f#Ok6Gbsj*GtdD;+-;6_=|brxwv_w5kT z{Vl_*lRaF3Kb&UL(M+YDux(QS zM+aJMJf`(QFjsKo8Ea-QR~=>OD%Gq=E0#Ing@h4`Mq#+BLc#WnI4U#oD%RR0M~f^_ z&8!hyL+Cv2`7vGwl0bJOV!N1L+^t_RelT(8obQVf_0{Liwzacll{;>BQUWa}XQf-) zEkcb-bGGWwSz2!VuLXZ4Y$4&IWgG_M=c~zMZ)Dg+>=LXn5%6(+lw*YLpW3Iof~YhA z0};Cn7*@N4<>|KL&f9?>emRBl?=?VudQueI%(eJ z`ACb(R#Ryl3Bf|m^5`d=2tUC-p4p%lSl3;4oQ9WZGWbw%Mb!>xX4P!MgCuTT;$7-i>&&%n1!T=yJz|erxhebbg}I!Cy?n}A(Trd+FvE=h<@fn zn3bH}x~U$0M}@}W%0HLpv8W9$MnOn!d#*JhyBZl}ZuAidGRms79UrB(!$ik)K9ZK> zBS!0E)jXmup1_ZqKORva%TiTFVo@bV)w+cFT{I6smk=-9rg5s&LCn|hR32O~A)6rg z0EJwA#6I{3%@WQzbU1RjE`*j5*k$Hg)JMe5Iq8Jq;7jv)7#5852N?C@@cP;%xV*l8 zOMcu{d>`c{9I*@@9ESzYp^p zRW|dxs%+*rscb}bx2aZ&P_;rZRe$;ADAKncmK6k zkhWgCP5AUeMAwV~aqsDY$6hnn6*Wm*$e z)0(yhAPOmy_l+;0?WUvhYB{1OmQvExgFOE)+(uU@5O!Ph_U-`B63D>d3hIyEx1IB9ZY^BF7OmSDpe6%=XLS$_*b{Q`2|{f&l&nKZlU zR~9|7& z#h%f~V3@DyPRr<%BJ*+!SuWvxs+uIM<>ch!bUDqa=WE&EtkR4364D(;#ve8%M1OXu z(KiGZlu!%yEZ$RUw|UR}3MLpUGAnQNd6!h|r^MIHRkD>?*&DQ)(v?=QwF%*7JQar2A?0goD5Kwml<1=vbsi^&om^hl~U#CfkK_ z*X0s`AG^?nAz!M)Ef7;u39glkTx@;}aY0*rX(tQ9>*YORC_qt8lrEg0z9gl%c^8HH z6Cq4TWnO>{nY`8gqeP@nNnPgB%Ki!agWl@GqmQg34WCoFkK?sd7s|-TKVZV)^I$_8 zHaE%Sr~5GDkI>8vp~-I?e6hmN6*B>P@6KrV276a7wcc%8 z*DYCPDk;y)ys?~Q&nroGuhA|WF-$5DGnC38%<+~I)sRs;HC&8Nu}w}t;*yI`$t>Pb z>}$T>8%(Xj8X6f4j?18qNE0=uWSi6RD{?{?4E72tEop_~PWF*+WmmW87nkY6f^*LA zKbULS%}1?Sys7as6#(ZG+<7YRJb9#{IX9oGA6R_q2Q>Ej5yS=k4B;v2*Q4e$UF_+` zGgFRGRMgL{%^#lVlFBHq3b6G{SM_9by2=iZv?yCg4sv_&Q9Ii(xv?#|KxdLkT;ofB0Z$0#f2dPbi4>YJwnQO zWR>^Ucq95mTb44&HzOp956_9wY#z;RES=xn5kd4N5v=u{k~1g`)~fRKVOhsQOSK zfXDv}5YcbRq&I~Zn-WEBY@)qH;fVs5ZgR{aLe?RWjmVOTKW~WS&K97{>!LbMHgyde z^nblvCG*NuDW$Yur}|*}Q$MKUSPJ)Nw_A+`LTg0l1l2?I(fCF?0cGu?WlwSqD2tHu4TcV6xiJqpzD5;S zqWWy`oYi?zL(_S!)p*=9%RB2lP?<|f6AND@K>`?qSO9Yqi%HHOMoh_w3_2(R`vF6> zG%cFBSE9^gOB>MtN7^%gDdB9ttv}8KYGx9dIRPx1 zxn@SxONeO;)zqdIWP;WfIh0yJ-mSGvj}*Vj_^OH_2u`D_X>@)uftE?zhp$5B-`j4^ z-Ao5Glx|qbOE`0lBwV^ko#JPI^7CFb7%@fb6DYNcbk*=!G2?MzazDuWT&mx-WEm?>(4_hth z3@J_xsyL5@X}u)4Hjlw;QA~kC9EX=e5&}kZXq+>vHHSR zWo1ZPU{-r1oTl`Ywv@C`@uJtmXR3$cqGx8leC9D@Z+dMH=(g+@)B4hg360Y%WE5ta z5pY=`G@hEGsbLQ=XBr`0Zc4?H8~%N(6}YT5BkP2=@ZI-xMCAE6`qGUq$I)QiJ|D+^ z)SbimHiv!YQE1~Q`D$n*TJlivQw(H;t<@(A;dlnw;=7}UPiv@-(_V5xe{gLwnno7=#v?yfE7aLpr zJO$bbmwRH;&1imLn2l#e4PwwgG9|j?>KCO&Ak2DkoK2w#>T+3*pq)iJCPN)%j0emQ z^3sE_CL%F#6iukE*Y>-w%ip!eCzx3L@6GAE-z#t>vz*;JfrgQ-;Z^%L1pOQ(Y5}A6XUNOPNNCnT6S&{D{gU6HrLO{L0MzCA% zdIw0bb($$wF7-G+tWpPlR3y3K1~oT;RXGiY%5kHCb7**427a+>fXV%1m*A{d^`6_+ z!yS${RjX)>9_iJJp@zE_s;s(SF$Heg4jYG6)3%F(L$Fs4%y1-fV<`V)dteq7$=OMqYsfq#4iznR+(FCdS0!EUVm#rFkoIb=ENnp@3W!-% z3-VAL%q{Q*iNf57iYBSwUb|Ypu|gPtyICceoG{_qdz$gLVg;H;Ft6;xP&7=}ti4T$ zjG0MLHNBn*mM+S#mzlcMqrQJ{<`fGvVaww!PoMvnJ}thu>_Zl8N4`g3byDd}z|sP6 z_wpuMXcYS(Bd!dMlt{`6Vu~>XG3%yBSMEmM7gX52Wlf zO`uQ>OIoJY_dcPtPn2Ehea3$h{28%6Ht@Ajsi932cj%cB-q6goXZSZu&KUf=B%yU2 zS!gZw5`Zpn$4Wqsl#sEA<>%1B2MSuZ^R~P7QzVemI$KsN>-ffSgdVq$gQ_smy+N8Q z1)-%}IZG%BG?M!Gy_Ak5kqHRk=z&{f0<2wc(Co!30eIr$5nC9Lv8Rdgoi%y|#l66y@LSS6^ za+IS$MjsN!ROJUa<$P~p<*3mo%YYTNlhe*kHv2Bwi99$$l(qGgTjr9`}^syK9BgHd?^ zl$7y1nMHc4reOvlVOXln2pP~Qe4CKyr@>FJz9rK)5Cw)~06?v-!X&6K7B|)A1dVsq zpH|{PuVM6`=15?+uSZN}ugv)w-o6JGuRX71rgme?Om(19M>8~Mq}f^6`IqoQD6mRF zfl1wa4}K$zNOSDI743rg7T?p18gxRz2f?TP+rCM4;j`UJe%Nqp^BcG|jCr*P`hj5x zejYJW&1!SWJgQO`3{Oz8Y}5`Lv*_2Vlp%Xdq{Q5l5@87rg2HkE%kD9G+J$~*QzD}C z&pNXfgjI^S-O*_fBNb~`6yw&zhu^+^TmQDTo=*l3H=aFv_VAK;c$LTCZz;tL-Jx_R zW9g?m8IFq@1uDZ~l~V)ggo|~Msf!xX=AjWJ;@Wdn{>SIPJ=DKyI(_^(EOkoBq7(G; zMZ>Nz31HTt1}9EclL}Fr%Xy|c#SbV?kknRjpRM`$UAR~{4bLD z^2>jP%tHYd)KWp}E82NCa-T|aU#_K+1EHNc<&zfPT1V6Wzo*x zD4)t9c|ZPXm4KC(_a|J3uB0sMi=E5~L0!jHL>tr}aLLJ3L&Qt=jNxZW0nodX0-$$` z0-$%h)~VO%*beq?R0yyYy=MMRmu5K(pcTzrgs#j~S>?5mI1cjf_ zc7YN8JVTsEjUV#id4Q8kgU&Rn*S8~rVl}g2Ug3xIwCZe+X235)&2QxfJm8~jQMt5V zPMo;Ql&&|bRfDlbw+k8U+6`~cy|vTtwa9AutwM${blY_&$ zIzV}e#QCJ904>X+&p;h3fhwLjPb1d|L>Z@1yPJ^1@|Q1@Mb#3HwyF6-PS7NMAhP=( zu_t;-sKD(s{**c2ajBDXR!L?F?VAwy96k!0vV2=*FeG>4H*ctX(W_v!R>;ie^IE_M z@OiC<<`3RuebLY`zLg#eIS=`=$yX-J9@)$qQQ2~No1cFvjG%1UKvWg~X$_}SCPPnsO)U=3oun3ssmBebLesmwgTTFOSd@l>tIQtCZ?Pe2)j zu^<%{5m0we&4AX4QzuFtRAW#EHxN!@Z8cZaVO7%&N|1=7?p4}Z@V?Jyrk ziICr&N$F6q^Sc>Q9@_fS>2~2BiZm%Br0xNEQ7OXZ65^prZ5-cUIylgSy^- zRjbeU91yQ7JA5hLXC4bjy2ykBG}sUi-8`S1mVCb`6g2!zerq>*t<7V0DvIWwn~LYP zV7>|BGWP%~JR#GUKfMq_Kl*}{+Kks;GOUx=OUizN+SgpGrl0VTIEN@3AyJOX(Qq{; zz`^(>1`P->flR3iXZc6nEH)}7f_BI|d z_J$uilu0*W?UdTdXl=DJcF{uPONUR{v1m`Z#8*eQM7N!)<%?vj8v!olZQB^ zPh8bJpOD^vQmPNS&fsv`w1=p;_gLOrgx-r!w53dDUDV0rmfF*no4n}UQC7cH^D(B) zKuvx?cYe6$KmnDCD71?$v4pu=>$+OYn^^wrE+29u(Y=AGzA3(S8M|DRz!OX9U7C~- z1|3{jM6tgxRNy|$Hd2jsk9_{=uAImM$(M=*q>#o_1do3T^H&Jl&25M+-Gk z362)`S_J5Mq&>qO_*X8$zu1h&BIv4PkW7TW2Yf_JujacP|>_FyW?1r%oTRpZ1Gs&7|Qy3-=f zj4Xm=0X=zhH|s5wAO#IElIGB$__C#dXZ$!!zg=H{L+DNZ&E~=+PzO^Rm6g{juTf3{ z*@P5(FBM7>`Gg{`<2rQBXZeJo0+eyq+>+FPBI29H$Qbg zzfAahtfVTEpF-ggy$<=yuyVaGs_B+M*S5eXZl7lGJ_- zH?}yYe!d1xD3^o7O}?xg(8tztDknwyEmHfh#rh^|2eb|xG9%Du63;~894F`rHU3nG zXef3(VXO=td%?)FjGlqXSz9B&q8SHpgG=GtJ-SVxT70$~(tZ6&875`+hf;qejIhI> zUnd02jI|C6ZV8enl55g~4JcOmuV(Aia@^(hcepSKlz69PG_txtlM6Jx0^GUR$c4$X zpZ3TMLde)=l^xR3qNL`a1RxLC^hNu~i72$E zTv%k)S5&dh63$3KURF&fm^Jl5+^BGd4_rpzN#eP`qn99Kw-H>$xJjje9F|j zumRdgO5RWo8jih8Mc9>DO{O?6Diw4?3)c8ho9Nv%z8FV*9sn~*AoLDHXC)ynC}n_= zs5|+3nD;ynGSd=8rCknSd@qSiujAUGRjn7Jrr_)I(M%$TfJ26X{PLisdNT+`x zoA|XWJ?=D*5Pn|*-Bjq3p&bSWtiPUcXHk{eyF46^sZq7Mksu9Y0xN9yj549?SU9{ zx%Imtx<5WOmEC%98AA;k?k^ zI~l{pvHC@ZZwQk=@>PYfqAUb1DObNFTosc@+!%+w80t3SvID0bx zmWVs^BmaP)^{1Qi2O$%P?Zz#T?B4zRRD^oGONrVKnR4@AX0;$5%CRbr#cELYAjx29c8cu~hNbGUo^rsJ6Pu5u-qR=p z6y0%Oiw(?S5wOF&5mj?owwLWtdQg4PYmY1^7+@DBEw`qhu{vwQAKZ0hC2%kFZX|q1pT5)cWFa zU;)2f2o^tNE81m7F^fg;ipQBC1FWoE6Prw#+|;l&d%|dJ3@I zKarF#n}AqoA3BQ?2}uyz)WV0Ytu7sGtB(C9(PR@gts$!df|9G}PyD zozt_6*e5a1V_(PVjGn{;FsD`}u-r%nKTp&LKTOEbn}aH`G85t`k_uQBz7avV+lp*{ zv9GCCaavv#cNY4^$MVG+Us!kqEo=(BoLtrhsN9ImnZV;f=CA0;jc*$qwEM{AU6Emt zIx?Z1M)9jRc7Ty*3tc!kJK`KEWBzP2gZ(fg7rzQP7I{^Fu=`pvu(a1-&B0 zg_4dEf7don+N^05o}V*3YqZqerM{p06R(_e@8r>^2iy2NB3&qLCqs37zsFCi8`bWP zuhY^)U};pMZ(O4fnX*x6%U2mN*bz?T#@W4F^`TICZ$6r%qFtlKT7z=RWa=H~C2&*|@B6>M&@ zK~-Ns6Pr|nm(v3_0&sC3ASVe_naX~eR6*c*W>jrpO{$f;NlB|K@3=a6tP-M9Bjf0+ zzOJQ_%IBfp*B00BT3j6;psC`>D^v50z~JS*Bt%EPwj!_+A&59ZQNeNmd|;#Byou1%(u>b~x?0+~ioBE#VoyChT!Ow+KK& z;6${SQXkrshOw=Pq+HivykpB3ktTFXQPC zHa(_H1)x7YqfY}PUcE0&C9HHR`5UM$c;q`5*e^KUj<6GAdsY{|lP3acts&GujzUmA zV}JI!75xzq8z>zc8fuIwSzgZBRo5=)CX;rh3qmOPI)8-V3G%O0WUv7xDC;Ee;J?sV z80Z8kTDx4_yOa*4=ZD{GH-~xIDnFNQeVIX`^o}Mw8|r+LA4BJzkDkFFC-TPtGnu&`uTa8#sPpF3eq(CIjF?po&uQa% zVh-fcZPK{g-tL(~Th+`TtF^w8L#jS^vXZ|_rWI3#a+ol#lFB7T!=hMtP=PR5;mDFz zII;*km~0(6J6$5D`kO1K8oyMAL_B@)BO%qvt)y* z%4*oKbW(h`lBx!+p!6k+h09BBUzY2`6z&C0er!qFEicabE5Xe|a5*CXDi0ffe>jLfk*DY{ z%DFdstN&&Mu=NxDGt1+{Q60el@s6#_}VA*VM6opWqL!q60|3 z(>hF6**Ehr!B1BCxP9`+$oarH){))ekrfIR8FnVixWX*E{~*g+UoS<*7|ZCqNpvV5 zer)HIhV)Isa2dZV4|p9z_k!? z$s3K9kSRmiZbjYcco~s`~nTg zM#Ja?nw`&=^><@0Jwu~)-Y?yYzFpP@2&`J4YN%*rRBV?4bvI;rZwLdY4HwDczm0sa z0F2u-$PEJKeo~zyBa7co1G$X~Lm<5eNi}*OG9D{$W|3XGe%BiZHDzWelU6UK&+my!!(P8-t9Vj0!4e#e1 z1NLeB;1-~q90;!A?=u8H>?YPtvPU#|23l;LM&o9y{);5dMf{>rM~_9KrUkpnrnuKr zR=ew{ABI4R-*g$Cw_z5xnk4uP3{|5+;Cb)odgrsYJjx6c^Q57l+TdE&>Z=9|!B`Ew zTJNi`E+KZZ+oc}q$h5Ji+t{;h?3p(9TpRD)NNe@&53}L)EbV>DCtuNRcSD7m=Cp1- z=BE_~9uXu0$-@i>SRf3c_of+?4{xj4+}wHuKX@H$P{i-Lv>G08z}-gkxkh=| zQ(fQ*Q}yX#^z+q3NMe&W6c{v}bmaUA{=sCkDV4u(a~!M}2=mKl#j7Oifvw+})la9M zXNBsF6?qp+diW^lA;mmyKH_Q31-dq=%=l!Z=t4?C@?fLge4e+BVr6UN!8v((z#C}e z`SUrsQ;^Mc{^N{S>72B8@&9I5OIo998$F<8aP^JN=8t&?|F#}IrC<2Bh2%hdvq`_7 zsRH=Z+DfglK&r)@;qIxXrwY`d9p`UOCR@_)3K3nNFGNMGN@ zg}UJku?;8Efv#l^T*j%;m1?f2)WH{_3cipUc-^FCyKo{y5o*@0j3M1>7Uor%FOw*wIoHf02(pA?%;2h{mN88 zQDD|gx`E{M28G3|6vp5^d)klAuWM9+` zf;7MSL^1l7=da}PWh@5`)YXo|9M~Bdz8P-|7Ff)fZGvi1G7(Z17Rc{*KYC9T3-o(R z8KSLEe%-bEcf!aB-k^RIQHc{58C?*%dj(;4mb-+*@Zgd8mNntL3MNR)9sZ0oEw}5P zG=_#f*kCJ&e+pq2%r6-*(sF$b=5K^7O3aL#_O(`44eL>c@Ox-vZjz z`P55MBo5_$Eu3k;Y+62MAF-4%a8kx;5?v(HoHh@b2b+-3+SBs&-}yB5^4FMVsS!7x z;m}(VvJ<|`FfBh7@dxxmi!kYsls*M1GhTMR=<=vCDT^8)OyUMjVqvsYhVN|&k6oLZ zWfvPCDt;xvWVkj=4=22goL8$rMbvno?jE}wmz9+tkyII*b~jRbaUFgFrFozmVN-ik z+wC_t%#X*~T%7USPB)*W9je1~ar6OIMy2SmFf?|Q8mKG%14^k;h1=w)D$Yz9hsY~v z473-{9q8-;r>U3bb~468Xh&fuq@(WC^c{mouEifCl_io)nIGFWia6luzWAn!ugYH> zPLl1fDf@jN2Jl$%*Hn2>d@;|h!QJpx?)<-UEqQH;7)l+n8p0=oaNKsy9CC{g zeQd|#o%4yuZ;3x<=KEdYan=~3cZC}w?VNkWWA6Zf=0dNpy-9w~qb_&T_4TW88@}=s z|1XK}4)7}PjVy^+y*J`OfmaO*OSwzG2{V`$#jfpmFZE`_7_hZ^#**j7Ho19dEzZl7 zBIIB4xx)YiLN+1V6gxj{>v^MSl~wvUl1=(0UTYvImO2}TaMz>P7E$UnX$hiFi~Fjs zz~!#5`R|tgyV+372`Edy6atzKF^xKr?qjD34(?rFgO5ep;!NgT)oioRM{a5r-EFF} zP(Aj0s2-bX_1H|R$IRKqJgFFvT<}UhPrXT=DP{;gz1a(qAcMZH%w}(01j3s!W>EC^ zW4RDCTiayQEtP#E0%~55W%x3*3O`{ngaN;PSZ`z|sIyHKwXl+k@O%^#A)|NN_Ph^l zz0Q*4KpOb~)D)6g8e|HgP^F(Jw$PF|TE}f;gfg@hI^;_gZ6z(1`dFZPe~9FJA~$3s z_0Qil1ab@GxRsoYrTfGl_og*E!rmBh3)yC~a~AX)0q1HN*&=NT!T4Cr9Scz+sPuQr zzv2lJ%ttK~Mn2hW@lF=;%g(5v1uFIDQoIe$ZH1*I)o?3QDbe<3d6+~FDW0@VXon}C zNXLj$CeOQwH_W41PCIma|AAU^XY-6sXs3yECZGVRIeR%Pgd=OZ!lcUW4DDp*>s~gI ztj7>EvoP(w>+5$3`GO}DJPBjSd=d#$jUI}=0fbsK=$N&&Y&?GUtf@0W1|M~g;Pr(W z420S8no&vXNXDhb(^i&ZO-mn@SYn1t!ruH$<{K!@Cj=VPU{ILVy;%eRnZ21EV)J73 zfyBw^k4zEHU0Qwfpea4Y_=vZd%}I!pF=pA6Y_hWkhc0MhrI8x-C~HG*BRb5z@LH*g z@I+b))&jOo+NG4@ge}fXhcY^~v*(48w=&3E@Cks1D9Y@>yVm-gYAR9RPb_q+&;s!qDD@aYs~U)o*5hHflML6^ zT6JTS1;yr{q+AG;FH|3qPq&-(fe*vqenNb*zhbi6)gMoOOv#3v(j4hJ72(qO!3q_4 zXXKmKmlKe!jYh-Szi6J+VmwOSRxi1i^vG3_WLE2832#QL%oVPCyK&DC%iQ&ZZRL*D zY#%<~HS>RH)V3c1tbeFdFQP3UOKliYB!f|ejM5t&ToAnQl)|ZcO3+c#n{B)GX=*qx z=Mj74C(B@x)W@AHXO5&(zS4Rd@{=2*X^n{epg&gmBsnq5h8K|RLTnFc1`sN!?41e9b~P=iMLj-^>%9spthSQ%5S*KC z^{7{KgUwbNQiiF3|KBz#^D)VSnJ4!(4bon^nxVz zOer`ZM$=tRK)p9Izu|YsmRlzlvU|>kH3}5cMMk3~>NM>Jk*C2Qq?2C|pJLUw4Vki6 zZ#i<>2L4->hAFb)khQ9O^fc7v-Guf^zrH()>N!d3sT#nP;Zan>dC)L``gf`Zu?c36k^5b@qisd4_g!|t(yKxkCA22j$SZ-z8g!wA+&NEN~_mr{k$tcuy+*lL++6H(>ihov4G0+#q_V0G~7x!F{|fstBNFA&oaYT4^? z`*oN-R&8H_xEhH2oL5l_{MXm-Ew*Z7KO-mytHjjWTeZL{b4X}Hw$IS9duF+}aqUp< zw*Z%{yc!ijP=!g&DG(eVJ$?Lx!3e%2@A_H7$WguYvf+HWT!jV~lA(%bqeA7C>y0gE%Kv!8{k#F+-c z_KDJL{oXxQz&T@TDv5flz}Jeh_WT|BwNL~l1j(G!xU_m~(g@zyfy^RO*<9-{>l-SN zF=e3Tnf}})K1#eSCqO<~_X9l0`g=wIg*lUckgy9ekZ4;^BYtuBnmqAv1NogXs!uah zbf(_b?hN>8y$o|ddPQVgTQ=8}I&A$E3InttCcED0IGI!WF$Tq^2pA+9ja7qwz9h3H zNIm6rB#$-Atbnk2DVB{aoo83gxJ z(&L0#gDY})%)lY*&qrVTYEhggye5^NrqMQ@_N6EjN}6Hb?fLiwECyWZhpjx44_nx2 zT{_8IV6o|Mq}zDJNA)y)fp~#lG6B#cN-TXAIgXFYiK~Ub=hr`D*wlvZm9}^(t|6Qb)yG+uysw5oL-6ws~*ld4FLBAyU)j>LW@r`(~ri& z(wkrxS=jlP{koUZ=GR$WCvmFY2hi87+LYveY@GrRHIbk=6{34H-X5K~%2Dp(-}ph# z%M}lhoG4M;SfqO}7sF)YwJcJ--(U|n-{=^PC=r0s7u~v-*ecB1Q`(N!AY`U&lFONF zi2?VUyYVj*mQk6o*t|gcvq61f8=l@++|5;~?VEq~#ipiwrq9;jq-r%4CD0 zenR1vnv-EHfJS(O(s>AGkh}`@`*YiXA60lP=~FT$Q9`(6xSc54VGw-A{z z$-_4@Wg5{xW_BDSmi2e-LoIkBai?;1v1HtKzS)?^7JkYE{4$|2iKfCgp6ZNK_M7u% zsWC`VJ9nJ{K%MA}3rnx>`kk4pUd){)Of}n6-&SbrobmxB+w&kn76;GF18pXyYqFVW zoJz>*w_F!{`lY$O2^0L>oD(yDD=#@PY?6q?aIn9-SvoEPkBeHAVts}zx-0#S|XP`v*0rS|*Q0(|P}d3Ck?4Fqg*K1h?w zRav`IE7~(-^x1j{Z)~)6lFXm&tw)B8Y`O-#7%f4VG%5%i>_GaaiBef{3Rx^VjSL2k z&s#dFyu!0W-PA}AB9*z!04rvLJif);Fz9!%(!xs3LdZfNi!4|c4O`f1j8s-ZKzzy7 zlFAf)nMow5Gui2q7UV4}Il1_JQ~+C1)+T!UAq^5K_&EC9Be}z>QtlO1DfbFLf%h@Pw|JgcilHzTE6u=YFCEEjm==mt zz7$xZUY7n?UJY7+vkjvQ!84S6w$yzW_!+;=2_8b-^`pP&(Sr^141LZ9qf~MrK`0aR zIJ1JBg1{umyE1~?4UTuix6^rj+?R}wX$MW5*Z3h;Z87W*)5$wf_~E7Dw93v(6j3eO zdX-E=nr$gjRZ|gTUyI%_mB%Bmx*`NQr_ETy46gpR#lBcL&`UnD_Xg)WWwlqQKEkf?66zUlgWV7 zSDDZnpc$Gw$6F`PG$QY|g#YK2q2jII;h8drjY1sxwc1)9SvrtL{x&bdJuEH8J0|LI z)$)yRRJNMH=+ah7q%+D^s63r*9-5LfWV2!7-BRJNUwHodMJat4DOv)12RY{1{Gp^3 zlJ5k#UvKn8zey+9A}>Y!m(w5#qj~5e8}?yCMLVXxCqbt9&9-WpOY$@BGZ0DPPBTHx z$2V?)50nZ*%a-nWWCtbQQP!FUXBsx4Ub!;d&td?)P}mx&omh75S?U!R9bO+1JPb0% zM|3+ypASf2LEQh$f(zu{I({tX|}* zp94>%Rftc%(6O@m(-D8z%3xsI^#k`tI&coeOb8jZX>8KDPEvx$EqT&(#Qm1@C1?jLOnDEcQZw?3MQlEOPKSEGvrAOE*h!qcZA{;wG zUFd0)V#dqox&Qnu^P6H6vR`_&#z-#BhILEv^Fyp^c zlTJ#nD3r=w76ZwI6WEH>F6HI8PUPi<$vaq`A7jwMIVF52!dm~RG3P+-$J+Cvsa6*B zr}z)K240OtKz~I}HCTpHUVU>*SwH3V8}eMWFqc@=OrXgNaK_3PZPRTx{IplIM*GH2 z?k?>lf+=_XP9Db0v^J?@j?cV%M-O#J5C2?8{oPw2Op*1BFd~U_A(4}7;z%kQ!Jer$ zyjqOStZHm#7~EiN4)p@iV?%I&+^TuRfoj@{g2hJiy5bPz-r`r`2RFETtJxiyR|Fi7ToF}GiYhC*dG*PH^raBmowV~KtTyY}nEa}zX;*8v{3 zAZY=791F-M(RftyF*G8Cp9F?Hs3&#p1eS4{8>1E$`EQC<5p)5@s4cTa!qvme9@#dT z2odu?nXA<2`D7bAKz;O>J`&f~em;78u&(r#!#;5OYimVg?Yom>{K)LR82k zpn_RQKvXcRC?%gueEFPX%;<+%Q=^1XCrCb<1susUlCtg<L*;Y zIKDJ&$~<2fc#J(7^wrhOrIjftj|}GJ{V;ypjouC8xpHoV+W2ZVB{qIEq|D42=Jp*O zROG6(ORAD->-d*E`gt0Sl4y4ry&OhwyC;iiHbnMsXqVYz{l)whT-aaJ(Ux`7R&N@8 zo{K%m z+3Ijt|E#PKk1KB5q6$P*meR1Iq%fI4rSdAzzaBXs$I|T`_OE>hc+({X4pUlaKosj zK!XMo4GMkqSMdY#pm@;V*xGv9e+p=~2b;(!-vlm0Z6S{8)6>JZES<%I6c~ZdAR%j^!35}-NS=zd~xUP>A~Cgryq`XPv0J% z{{8KT(@(GW_fKE!p6= zZ#G?IywS}mH_e4>6iYmQlXQh?slsQ^Y-9mwU5=dg(8RnLTc_<+rR@I5P;!0K{j7Y86gA}%j4{c)?S{|WW^~EgBgMHEe}Z!*4el$3 zkfx!eZMO6F4Xa;6=3`m(wM-ss;jOHc8|opojIO$>=f&8QDtzBN4ltNh$Y{k|HkFa3 zgaEwOzu?5GBb!enazF}CDbmJq`9zf1D|0DXg3^q8KLS$8(LC$M6|{{%$Hg=kTNO#M z`bToh`u$)f`+{_anO9|L4V!AKR`H|eYlqlG@Fg)X6k zVTrwAk0xFH8rQyz3BVtln077PTiNcObc`~&P3AfEKBlR6$tWOLm(J0g#Q;4?i89ETT-oaE8nmD zFQ~JImk%bh>>;wIVu`F=Y^-l?t#9HXJ%5>x=ab>!tXQdc8!LxYc;zVX7vFFiue{E> z>$==~sBu>mNm}V*bFSc$)T7|*dn+?q*(<ToQq)z<&;9%Q z$NFqK(X2x05m>G3>zX!y)r9JLs2XUvW<@-@&J(QDZB1ra?q=}kgE~A?)QK>WK&6mY zBIOWC-1^A#n>m(zFJfE~}H(m~wKK zt26?WtNTLO$~dbDd<>wjs5jP&{D?bS2cjOiE~4x8&FHDLvz@7SX-@zQbZeqUmR$iR z?DRA2UA9KzgEQ_-MC;gmcPN+2Q2Rn+N4j}7LnUT_zGa@9Re-g@%b*5ri+*J!UiqA3 z3|gb;N=11Nxs~7(M`|Gejfh5LA$o{Oo&@W36xSZs%m;_D`DB7$i+Exd>hS*kAsI_L z6nH;ci-$NOlF4>aXWVw&?hIS_cWsDZCdiRg^gndb#4q|EwNBE)ziU9I5$-GKi)z^l zZAm)M_2m$hogF0K_Um{JwERa_Ds#a986N7X#Fr=T3P{4(*7SZN-|yi4#JsQi?A}R& z8WM206E0~6E1uXLBASlPVpl*Sr#@jSL8GlQwY0i%4;j0Nh}kUD!}MEp@tF9$q-gaC z)HF__aok-8$s_6?oyDIkzg-t_vR1c^X(VO@{%4r7=258tpbcsms`c=ZO zw$36$^jiCt{s9+pR3xo8a%~>f5282bGa%pmozF4$mw1ngw0Q{DHn_lcj9vRVf(m6P z-q-BGE66qV&x&Y_nRi#?vQ1W{9mCQ5$^__FoK;Fuh0O(ldiS!Mj_Ff~uwz9xtyz)* zUXH+sGvP7B?bmkKHcTI;b=2NR@9#PQ8*%{l!~p0M>y|ONbp(Qip$8!PGfiyDCcds8 zs9pIc7JY}2crYYuMvayM4wLXvOWZjl|K`ueci}*gvjaMV4lL{UIsR-~Uw^oA?_pH? zpPNl+4{!#(iE0P=icWuIYp$!0UDFpDE6GJN9I+9%$XC>;tb91!r&yyED$!c0t$kKn z72L)EzM+K3#matsH6XGP7+Y`>({1xH_h3@*Mq>yJBkb2Sqw&G2x-6Z4we1zXqgV25 zG)Mik&Zf9X`CAw1imD;S`Z7b%B@|j#@bBwq^}mgyiw0=HK~!(VKVEgUakjdU@;`{g znmDlS9--X_4f7B~(0U!g&mD~HzUiZ#>(_2t+`;jtbX>jBBvqXtZ-hs^w*t;GH>^1tASUS*8$RWVMu9;tS7k`*-hGj_qqPr}x z#5MjytY2{k)~`5Ite@j;G#OuZjN_O@gsv$hupI^Yr{U_xHyqXdc%Ik0cI3dV;qV=@ z{MxTj+fjYvI621%w2ub$vD)!x$~{G;>n>IhFw{OX=fUOkv0>BK)-F|x2JxFgz288g zebjM$c>-dwu<+?`x7-}FwWHCER*(LM>yzshr&1x>|eEFJAba2Tm!L|B}_T{?#1RZD)JwFYL zvpUTinW+KSw6qM4{_nOwgX2VPD_rLawQHsJ-J1W>+IKZ*6QD(XhKgR zsAUk}VC)3D7h!}RibyYt$)qwUD zXSK>xQ{y?&ja%V<(LkI5sa|${y1w!b@b!>c73BgMu75`zlx0@>z=jGYVt~Q2LGYtK-3cQoszQ|3DxjV{;WB3^lG z(DcsEb#1y#bV>R&86|h_m}BLIRq(RrT&j9b1ZvOm3C;BJFj`+G4802>YDIBj%+&URq`{6{BoMwc$3<~0y%bkh z{UV4S_xNG0Gir@=>-&aas?+%*?)Eh8@M&gnUS0a<6_}|W#IEQRqCXL4>Hr%~FT4}p z)5T#{F39SaoFmYCY^fGqx4>l@&Wi|mpG&G6Al~NZ3pAm1Z%_}5(EXE!rgN<$kQe{i zDh~pdPCs7qs$m$$Zi>2Ek5krWC>0rx*(4Yy0?Wn704uSfsT4@mAtswY2yV?E#R(BE zPzkKEa~9hav$WbR@k#x-d)z$1a_BqCR$p)Y_K-GW?cPR>s8mM47w93hb9ElY;UsI|9<+1P)iwy#=a`|;WF#R>5;ZEA$s3T8Py*f;BHS^G2% zdCm2hJ-&yd>~&{wd~~9DrWXIMzorj~s{ZHZ-ort}gaWG$tZgK;ir(@V(OGFXi8x*`9MB4i8xL_O`tHy>%qPSueH-G~|I8X~vV*Q9 zQ^cu)(LpRHRPCCnnuq{aK&iiT>U$a~s*c|NoOY8L#i4>@`sermTp=wJBq{J@%zF@X zm5jb6b1)P6Xhi94SN{3%pGL>7hhTl>eZE419ejAKqMekE6rszk$yhNfs2veuT0L)j z0@3n@CK9)zbx?>I?8deKta)W0e*fF|`sj6c{}Z<#@;thG$G`oy{{7m2-n&{{*Bi&b zot)52|MuIx`?An)4_kG0@HFbbbs8O%se=gzGZFt-Pb4wn^?WAR^X00x<3*P^T|4#T z|NQN@hp4DiYt_yF#szV2FVmkIyC*mP?3_3s<^Pqxi>ON<#35!`IgM3eimtGYvj=8%D!1GR{h7@TGcnP`dBlpDC?_ zx9+?744f`8$5{O8b3MD9vHYCITl zk*s`9ATy*e5`aY z9)J#AaAYI@QKLkUhalO42w;)8lfGqsMx>tA`r)BnT}FkBW0@6;hOZc^BU#hQZQ`}C z6poVzf1J=i%?Hoc)*qaF-@5({)6{qfFUSEgS=*Y$nFwir&+u73v16{<(~l30b|bO6 z@h3w)(G>S256@#Og5njz?FG1cDb zHceN$NoHlM^!#($qZQQ4XQb|$!gv1+WgcLKO|XQ9f6xj5DI2fVRv!GgLP~ms+=bkA zryoynx@z?|*-1e6=|M|9z+h=$sCZP;1zEe*&VXcT9;H79WKa^ehcn69j$~KYG!Yb% zP1Oc9vRIcCU$k7;*I{=sGATkmkiiI$H;q=87SDQ(ZxB`Ll04-K;vKtjTeO}W@=G@~ z=G0!Qq1KazP4*p#_*(5dao*raq0O4j$hmq2uAN%95?2@`LMZGT+y zX-;rbovg1Iv3Qyi4K0`w;H06QO{We{6jR&K)&>QG4jcoogm2ID85%Mt(r^JkAAYfq z9i-?F&R05@k`?uBYWMT~VfGcDps`%9{kQ^SnLFbq>V!sf6kQ_KDZa4&olpoX(aTE9 zO5V^i*ifa#&{7ujcO;CvE2~Zra>o0fTqazx#4eAGmr68?V}nx zc-M;lLs#@4enqqgWi_f|u0;J@QiW-NtDvFT>xou&xK{8D;<#BPclRUKkc_oL^UP2` zs^%hgrvM)@p!H*q7Ok%vqIeXKB}q_V{^0TT)^9hC0OdC9Qy%P#2XIvo4LcG?^I3cW zlV4{9rkZ!Vk_T}BbfKbzNH|6d40|#5k1WWC_5Stsn7>FGh@t;nuguj;H!*# zpVD7fWKF4(FZD6@>xdNruE9$VfqMg0u7Yk>Fptp=7hj!dwhI#&`oSXrt3F1uvYyYTY#G8aHK~;zz30$nCpX&1CO#4mKj&}- zNC;k8ssD%M9ij%WdmbOdXJcB>;-3|23-Iz)$lA4&w$Axi@17e!j~4^H1g5`63bO9G zEAnDsy@c4se&tX*2t8TfYOZhncXAN=A{>Oi{J-iT^ktcYkecV-EPk&IqB;Ox$M^Tn zmptf>mOSV$t*ZRS6&4NFo(fH1r3C;iAOIpN0IQdm6gf(w(k0Ch@z_oqXiX@s;Uaci zYv*dw8nwRy8u$8|2)#P2t_V|D9feVrTnq;Z7D}tT!1o|yn2&YQ{wwCyoQ43}0nN(6 zVQCC^kw|`+jEdIzKCvRlYfL0rg+CT3aHVzPK4)hvyEc#yv`=+GYb+!IzBxY8Z^S0bfINq zvGY_iTc>1_jaF?94VU9cp9=$blz>qq)A7JGV4>IxaB_o)yTg1dY^G5^I<<;hTBp>1 zI=hXjXk-_%(|X1i9+$%kVp`fGPw$FA*Hd%)b`tZerIaC!`UO6$oN!KIX~(tuAg|2}z5$?d?<}y3(uZzib61OIA=@Zg9er;g!QY zcsO%IH(}B7goke^hwqbbmiSd&icBrSI$xraw~$6N1pSI30MmQicQb-LG471rWVDH? zvj}iKgUBfKOOMW?17};2rj$z@RKDj+C%jP? zgX+Z0gQXUj18<%+0lEAOUt=+~>Wj)MLs=yIytpk%xPO1YW4MZ4h3f;VyZ%mT1Tdl; zh79ydCS;R?*3NcBh`+Nw3z zAH)5i7SW+Jm=M?fG9Q5s)FuO<86&17GXGye(ME0INR~ge@7MPx2`grmw{-ZtcD7kl zPih|!Kmy$mSvt1IgrvfQrdqmMYgD|tRU_Zt`3Te1&nYb>{j3cU(mCwbFvOrk!_s8e z#*@^(*Ihsp(-P1$iZlLY5Qne*G*BhlUFZ*|D}NuXU=m1}MVGB6oMS+W$&BOZ_LvPRCAaFVh^jJYo_BlgR}P5!%BC>CsGS+X#vW53cb-t-EGW!F8gF59S8 zHeKtB>+7M{Dx!BogdNvdVGoeP&;Z&V^p3R{t( z)%jesD7q!Gc3%Ci_^?Ck6Sdm$SP<5sxUb6!R_f=0ahR7)X=%9*e7LP&yCsC&yLII> zQ5r+>OP3>?s@95fqjmpyBKa;ae2s=UCuumSVEwe00;qV3hwHllIZN zyJ)|VLsIzF>u?Q;j*MUH9@>h5-dBMc2g)lK!(HEt&xq+Z&FXlw+fpsWYZsN9{+9s- zP}B#{(ZeIo##kRU>oqR=psz<4?80m6jQVJ!MPNfmcuU{Bc}`ULm{IJ%!6~TyKHZoc z(OQC=e%G@+C5rX)K#OIq zi^!wMM(K8HHEwS_h1*aoj~v0?7Ji;Xmi;S;ty|vyB=N*FN~hW1(Y#xaYyWtT>}k~6 z*R0TrZ%gJb_zeo&S>Wpc}SnQ)}-`4Dp266joNfx8UHB|Fon} zbk*f%C0-SIYh)vihFXDMlZEu6!DNl(&v$0 z8rp$g{VeKNYegeyhJ>>7BGjm!`f5{cmY{_(s0*zr0>7?q>^StO18^~2V(L#+20`J{ zJE0L&Ln6Y*?YKHq1p8jwb4s!NPE<`owNTfDu{xcr8|IiXM|kYd%*5QkFRUV$(qBjz zxd*YYLaw_8URHj|e1?{E)hsZA$r-r|K~aaPB14w(`$#HyUuJuv8IM19vy1`UXK({ zP(G0L9-lZLV)gU5$>XrstMv1O%&MIfUEB)vTqexPc$!Tj?Q6tX1@8gu_K}Wm)3?NU zJ!%h`WB3z5&v>E&X*52j(-6Yo#fBkmv>rxiToULWh_V`kmZ}2BQFmf?L$qpM;J_ug z^Mn<6*2FO~ru16&6`YrSt!ICL-Qzpv+lu@s2F;9{rzWAUW#{b^$KZ3*T3$n0J#KHY zr@xZ!>^*fE>4;==S6xPu`F2eaHzSX0w?(|Wv>onHY*imNRi9DMqUjlV@LE33ja~Qv zpCs%;`&?Z{B#^p}<6_YcrxB}k>@s*RStybAZyd9KuZTUI1Lx>iA5P5x)2U;RQA@9_oYqCNeC|6drn!gBxmibUnC(3KGi0aRpaqUTs(LXdq z#HqosBa)~=1>>`(fe%u{aNaC?at){MBV;#Zx2?-2ttC%H0dsd2_Xfw8%@cteRlzk2 zj@huHNo3Gr$b!&!5+Da*Yk|+8y|_C-rLwRx4nc+lLv!@ove7Z3fKF7!3JAmie~irm zYniOE^Ob@iQ5MF);>8K693ay80w^@xSA+Zar)M3UNW_Oh=?(h|{mTsI;KUbTJnt$h zk54NM4QPvx$IQL_h@<81I-4>>OJgzWqwJzXHgs@f|PjE?VN*A@g3X%CWM4Q!(z>CXDQuC&Y z&KWdVe+}8RsW1u^xQzj53DI5{ee2?QX;?{$Egu(qS!=JA8Ys(n(zXab@HwO52FaOFwbZ{nk-y18`B zD~DUmUuoopaPzso4KFwtcknmbHkeZHRE;UP{Wo~9SmL=*gXR)!6n63vyg&&N2O*^g zky%9hkQLz0`8lE|L)%`G*APNyQlnZ?OHyuIGb_^$b89 zV}Jb8tRG?B4iq}cX8a`8kO8`|P6q zK3mQj8|C+So6;vkbcO5Nw%om+8+1cQ1OD1jZd1`O@s0&R-f?g4gm3PcH_g{{1#nK` z#G%`avV^pL!|6Mf$fWg46dv$z7SNu=zhmKRD_x!%^-teJyUqc|TSvOC7JeXH{`XEz zp*YplDE`u^>1-+b#+y#9kY7@_PfWzr3l-vEyx>+elFl$SC(fDo^)vqT*)pf3=}hnJ zizwWP$hzzHTb}COt+v`Qt=?;t&r0LXdaXF+R=~AYfm@GzA!HLY+?K9kUpYHV>8>_& z01NnhSg0cW)4RWya-IY6Sl`j5iYx`H3sR#KMEjGS;BG4hWi&6HZkg;#8eB|Du8C$jKB}nw$?UH<0L)satg*_1%Fq4p+xn*{}Fg z>Aa0AYZyi_Uigfl$3cQgfS7ne@QqjSv{r4|Q3sm_ygP~$k(_`);|8?V@fRxcOZ6$$ zsiVK%DaykFJvr$`6V{baBF40mzQ-5m!A$T`NU~aJ$$^Z{W6=#%yb$ve1Da;-GfLY! zvQF^ecXgQ)0`B3l9S6*JUB&q;|2*(lXvGr30prdYyd#7uDPmyEwE@8nMWAI)0dS0g z6GpBt%}QOW2Dw}r`pfa0oG^^wQW=^85RE3b*^&)PqZO>2(f=XtO}83Jwl%^3^%R6E zPF^W+fgm#Toa@5TVvK+Q1W61;a4Drgj^+VLXb6I}>LL0W`W5z- z^Hx`NpOb;y&CG4KZF}C{O|F_lD+>VJut#348ICM&EH~7n#rqMg8R}TQm_5+X>G%7k zZ=cO`I$t%<)f-$tS7w4FjGx*b5~mz`U#E zu09!YH9g|$^CPalFyeNaC)%fR1WwB;x6$g21iJ#_5ID{4CIqj8daX~_4!xDpsGlGA z+c=UQe$ZWTazeVGb56vf9+NqEFpj&wVR@w_h1eg2eCHO4`XC?)|sVIU2cKyGrE4d0I>_`)1JI#M9I`yEA+$%dxyanUOD9sa)ptw z5sA|1Z3nw2yGtGEUVoa+t**sy;lT%M*X-nscAf_$PYPa#*t@^k9ayKi-T?hlC#@CS z^+PqPb4EH$#M|7wJT!GbovIpD4XZI=OFf8a zgz5p-8QTJ)eGOI(P+63$g6#m@c?+x$H}dPqV!ZYA$gaWKC;_ToRD%V*o@+44v2n4$ zA_TH(Ag;x0GDOTM9| zHR-c7E34%6Vhz*vi5Ke3NFh56sB|Vx!RMS&`(+=!nLSyYS?-b20}4qRfQk3C`G<+waQj&kb^J0B zT8wam&$8!FZB9thK(~mFqQ=r)c+$qj{E_#vnA4BX?b{KQc{^$W4`~#h>H*LFdqGy9 zxpVI!?ptI8-&&j-Z|C6LU@%JWNYKw?kg#3)e*);Fsh59%lcQbkd7vakqMdgwPOPo!0s0UwV_Z0m@QcBlCF4sM!^sQjz0$`YFC1fCOi$fT{=zxvxqS+n2%w{S0HYg%`5lAj`f0FRX^DLKFrWJ zlHNmRF%ZH7o?EPmy9UxiKCiR%?caKBeqWyCoZ$ zUiRdZrEI+qQX#1<$lOvVJ82!!3NscZ^49A3|0Q10aj}yV1l~g^B*DWbxEi&lar$oi zu$3kpYEAG8S;$j{YdYv(jh>f1BG~fnkV1BZMOhIh@JbEwWINS4a7QR}WsE;}H;7xM zwQtBa`WehOcDis5B_4@8&zGuR!((%XUYHcJHM=oH!mv;$e6(-UAh)6$0+|D;p!8Ko zRG4Xc4A#h70KRT`ijyQr#p0PTQ6qjpPVx#1JCQ&GPO(o9tS0KhSYUD<`KiTQ>aRn( z^GGu>nIkNSCaSf~Blx0=!+KigNKKbsym(U+tBxC2WEvT(y^&3vVLbD#o9;);CR8v( z0E$@gUPo*#TCVb<5LV|7R%*|HC!V|!w7x{WgWh-Re+9!T9$*4=UKtHJvsUpFT@n6?ooRZPr;h!r zf`@@u65^<0caPBcY$ z-tosSv04a%r)9?eJVEGnrjM)&QM$0p!E+>>mZ)@>=|rD{czQ zfp^7kNNyXHtFE88wQdOhQ<_0QnT2!5py4B^3#ESiX!zUY=@FPTN6qUdSPOG?=$m2O z9BHqrQ-#lGeihHct>` zAgqZ68z3A{;`6yu$)m=KH`x!QAktsD^D=O>;ubrNNL%VUm>pc~){h@|PkdVZ^2?oz zOC__RTyb0^sj^44Qg&ZvMVf*0DL?lQRDG0vT6yWtdDt*O_l|}>xfKHjbU~hW^H}Uf z^dz0xgCxSF0mVr#uPOaM`7>HsNg-@G4Ja&2W-)vK_Ok%9RYl*VLC1GMgFmkh_W@PM zUV#`t$)(7;OszSA(~Lwko*CqST_TZ`mp=#)%m_p7RA(@N9!7&H`XB%O|I8?p`QUHY zStHaKdi5PV+I_eC!kj2CxFTq+ofaG5v!o4vhYB2uo^rUKhA>|HTa!y#bw?_fEfDT9 zL8!aaJ~XIdmlhrrsFP>5^R@wF#-#oa18hLbLU1Qi`H%}Fcc(lMpwc2$Iyrh~S`v*@ zHy@YKInhDAE>Hki1@k=I#SUw@n?$pBkqwK_<_H+?i0VmTmY>+nXI7vuekJoF4Ibrt z5`N1D=)q}-$N9q2bv=JwUwvBu-Xh8qdbhw`IYe9354cK&MDrA*t&+C05tIoR{$7P% zW(Fg(jL<8r>h-nWp4V)FqhP5_0Mz>E|`PoEY0t48NH`EI}0 z&!ku!L1txX8@wA}f$*9|&~A3rya4|p+Id`C$#DI=72L(dCtGN7 z?nnRoCtsm~nS1l{^3*?p9hWap{g$y#jqmP(W{;kmWe3q@o6==}W{E>xLEPYakCqM+ zmr8P%JVBd;)x)|pd-t(SsjXU-`cXCU+|a#Ry~3e6REW(NcW|c6yqgBtGPg#PO6J}q z%gAS=rTYQ>+tW=pw5e{|*n;_KQ%q5ksagG3VX47T_2KjLh4qvs7f;oTs4i_O~ z=P6`trlDf>2~@mEpyI8?1fR0Ut7*7Vbxcm87MbU`=Fc(9Z(cy?4ia4y#XOFpW2J14 z$@?V> z64OoVX81J;u$|zg_i6G-DhXs2!LF?aOP8>S77LXtEp1<;OnxnMmHRijaJQZYt8%yK z7K6c@;b+V9(s!5eK~!DO@Ii{l&Rzxh6{_1UVQv<+kAPF&;&UkSfM>S&1##MEKG}7j zz&V{v`!j{$9n}fG7@b8~+PKZ=H?sKhO;E#5>(}iy)$P}+9eYdprwo&PJx2z?qL#{! zn`0VNh}JK5C`Pf{pOQcALtR2kcPYLM+{~-qnmBu8r`KQvmA~%X4*|BGxg?(4%hNnQ zd>%U5w|ahbKfDEJb!h;AJ8D!xM&@UoCxB30uF>xCnF0H7``Th-G*9CVGlDJLb-$Cs z^9&FqT(mgHU*8C<`9FKlS}+eF_reYPzw0LL=uHaUMF0B4zJWyGrTdeCkzZU)j{liu zdEsKPZ~*cRdq)U~pwx?SA>idl2#g@LVAg!?!z?@O*r{Xz8xRDS;Vf;XP?5`3j8+sdH-ZksGkn$;z&QYN_$2McpEUR>yB z>5FDb78`Lsmg*qwAtM1dUD>eP1bb0-4mC+F zBdI3u0??ug4FbR_07*s?|Ka@XNP4#7M)B+-&!5G3>ea!s1FSW>&uj^cu_V)G|HmNFk3Rgo_R?jYJftdAzm+bqfM2G5G1JwAH6PR~R%2k??{-$aUFRZ)E>yX? zv`~$N)pU(KTWhj&6+>H`jrFB#dgxjD3ykb%wDTv^Kd&vp3(yuoPk)~mCM?;=1?$tU zjiS6TV@H)St^)=H7s7rEajiCR`KnU$ie%>&0P@?VMS$_lC6iZ>>D@ILF5t;J;mO*{ zZ%;SE{}%Ttp4?mf zlU!N$j#`(0B(^3P>nS_`A1Q8^9ybz63$dfS! zW*MvYv<>rKI^fByvZvInhe(wx9A?Tk0TBwN!~k|LBbR+DZ#M6yBMfbqyJOyMKIA`1 z<`!SvmsNGYWj{2pIC&!pcL%nMDalLEl{=D`XG&Q;Ok1D6+T1BPqb~0Y{Y~}z-OQtB z3H;uv&^>sZ{bBH&L?9&&aKeEbRUEms?EFOv(y-_k$wXh6_rz_G@lpw?*P`<2L`|eA zu~+UnrKlAOS0*baFJW9;1uFTikflY(P|q=aU3*q6CaJE~WVhI6_qEx2b{`V?F|9-^ zl(^D@KzWx>dI7fx*+fA5j9_XK{032)8L{hf@T^qoJZFbu0Trz6(qIY{&`o;|UdX_b zNR9jmToUklrob9OrhtpyHhFod7+A3RGI{eHXXhy&lT0Uoi+?@?^PUv8caINGp1FrE z3oGk!_v%HYEtB91-c|GzF&rmAj4PFP$> z3)rWSfM7V89Rxty5yY2>Q@2kI4KM_RdL={AGWgM_C3TkqN$lUA?aQ^oV0QH)yUHKC z+1X3A>%W-Q0%vu2Psb8wQc1}96RIPeiF_z&LEnFd%RfgIUd{}Qq~gs4$V*LJ`oI4# z5SlaKjBrN2P(so&S83!M8f6PNyusELNw`kkV%r647g<3}Dug63AQ>jfWL9~akK#@P zg)9!%^jVskI4uaArgDLI8D|rVP?%Iln|~^rX6|OP1fH^AQ}?m6I^wB@dqCWXlTs#i zrzb08c141!JiW!&SAS2jEfq)l)3bA4kt+r;P821?4*SIw1%-q<+4+|aiUA1TSdks9JGqG!jQGPq5Rh zWwbI?)ydyj2gA=umI&tPp)3FJ~IxGr>E3oFMgsJ{o@8 zSAClftDvh7_2ph2$A8l|E^8*Z(3k402@LFHq&c^@@CGoEXJrs$tojPdX4D_%fynn# z33K2Mse}Rb`!uB!*Ts>{$h?Zcv9B-9UZ)9dx1ecT8#akBR`s=89kvx_>z7Kc+-s)L zOi%!6Mj32e=#||e2M{9s^o1OR;*@p-vJJgb8ICuT8 zg#s8&w~eS=Th8F=8?p)KLTYD);ET^b#{eARrQ2Rvk0MN2Qx1K?%pu*_piEf zjoO0B=+O6+tD^W1^g2stj5dJ|_i>2E+c7w9bRi3I$q4R@YWiUNIe(#_(}r_@EAGKM%McYt zbJrD^upk{SKmLN3MaFBYUdfH)Kv}FbNN8z8Afk|Iu?Aef*A}ob-SB zg}^6iaA)yAFiiK49|fAEQcvB*vem6v-H6uLe*NdK|9boTum4nY!)m^{xmJYdz1vcA zxw^Tz7Jltzvv|y{u4Avk-`>Ws)QliJj&7UqJd-+$!wz{wxH2vB#mVImzQMNw*#<@5Pe(Sa*!oC44( z?xR`0y{-hMHGVO7{I_3z8EzJW?tO#~@4>%LW5TDMt~a^eiJjc25Kzn9p6HuWV-j}n zF_@duPB+}^77;wJc?Fj%dZ>!p;~_v8R809JEUV4@8oLf@`dZG*g=0P&JOo*J&15vALi@3zwkTJD&0!{cp_ljS(N5y!T1SVY$r}Lw=={wsyBrur~9$Ca*VU`|Z}Em9?M+7b%eP#^GDY&THXrji|5$ z`vM#q!L_N0mcSwaF*AS|;03VjK>&premwYcf4>+2erW;=0C+R9@H~VDGw^Z*)LUb4^(KEr)?SDosx#(JPNy{A}5zEpG(XAcr&WV z%Q|0BAXln{n1$dvj?fl_L-A%ahRqIkSX^qtS30hl7^#U%19UUGMiKtJ zczhjSSrl#}*a_IFos|5`^)Z>yvmDD| z{SSL$vmV6v(P3}p^Z*-z4^Y{}TJT9MC4%>oDmYVcJLL0C46~h&^2klzlEU2P(>|XD z{b|r{@QDn70Aa=t=|HWHHU8K&&+EY)ENYyXh~~ahv`5JOrW3@qR7O>ER*3?SqqWsS z0#HsG-ffM|lpX%J;pXPswOl@{zqucU+_HB_CpCwr8y>$D- ztN*d`AGJ57Y$^Kphu?$89|iu+t=7B2&WIloRFzek<6TF*)edt`^5jh9V2(Ki)5`?{ z$(~O{)2o@XsWTBh0KsrQBq*2|Ls_@RnHVn4U-7mlv{PgCj&&;$J7d*lw75ghxsUPd zhg+BkTdy68lzUgh^(Y0o@e#`4FP9yA!pAr77fap_pD%gbT`UO*^K41!PX|!S&0~5l znmB=PuruL^_)YUoc5PWt+{)hR`N2{7#2wxD^jg1aUH`sQ^Uf6Uu;t^IP&8Z~<~(m4 zxPTED7K~S&bZd^U^NKT2fR}-NWj@4ZQB~j z2X#2mFgd^tY9ffu_dJ7B0U1Xs%~^YIqxc;gS@zkXZ^(I^xt zK@X`f_48ctt|KJTIZ|C5FpF_i#O3y<8b?>@N0%M5cT_A@uXfg%~jc4(%v(U)epwWYC0G2u3{YP zVspcinZ>5v`(P-LtZ_qT`_wY)!I^449)!$mkEe02LcN>f#W1p2uSkwiN)|T=fpbkIn;B{YD z^aR^oGaj*j(H>b`Rn7Atd)4gyqlx8fL$CclK5l|1KCX`1o8WH)&)+mw^((?D25;5q zC=6j*mnxr}nm;DV(dKRxw8pGc)yz>dS%YW7H85C1bW7WOS$1$;;N&w0!h?M1s# z$I!3C9fw;+sejVy3}4J}9nVm-aS1o^gUc#@uIzo-$R$@`g10P;EIqgcEJlP5!za+; zCL(0yjuJ<6*0)zPahbsMS5YT{H#)GUjEI>S4f65VePb8Yx2f8iD4Gu98Hmla6m7K&`9P`b_{1zef5ody}ss zxF!)i@ASz{0dC#$t>xE6x3ccra=?(elX;1BTQ}H6L_sF-F zgKw*&Bcj`6?npd0XKe-eDb|H9W@{)Xj=hu{yIz9ruVkVDAe zLg2c~o;S4Dlc$AHicfxd!=6IxU-7??C?Db1K34B5gw#;s($wri@-k6t9U+**(sN6P zt?=zq%41^T^ct{#G5<+6+u*poWNg0R*!9RI2D`g{Vfr2r-`}pJrs(v!Hu-jGvgd4a zF*lX@{=MJCdnl~aFeZ5K+Av8$gDDoico|OeME=Lxdf3@qTQ7C;e1ap;t;KpaBV=bz zkFhFS$B<}NpD!@z(l`c!*_ZN}4UuX$HJdPeg8-fV?1g|9Jdy`QIL{mRIYL zIT>IDLM)!eHx&HjOw_0w1>8XGA2Oxg$t9VqS0Pi~Jl~pC7{D>VheH(rq?#!lYT4eM z6dHK``uH015ALo+6LDb02j4vw5%(9JqZlSp&R(>&@<23;c;O=Z8%%bS3HdbfM55mA z$b9;^lMzN>W^a76%D0lDYqE`(hs!e4Z0|cNEZ^P|G0MGxiz;~fiK6cl6bF;U`(`M< zUZEcm8`F5hw}Kvw=@u<%7?W$Xu!PSoY7&0uyRuyVu@>>_xsNW_g7sj1<+tFst6+N) zfvyHqD*tvpU~Vq>)HO+ffa{X-eiD7^T7{|a0DA3Fv^}vQh(00VeJ%R)zx)|&gB%lh z^q2`w8`1mP@^&|P&&1=$)rVcA&YMhq-wi%jqdCNXs}EB6j%BJI=f#$z? zTcGIY&D-C9qX!XE>LoX5j@cV+XeUVLCuheEfl=qpZ}^8y{NWR7Z3aywHksIDVv~v0 z01e~cTm1VK|9;26f8pO6<~O)O7J7nzzvAC-`1d>h{S*KGg@6B!f2iw*b=?NHD0_>t zwi}F%}IkWNrK!!g4^%vgl zUI8v{?^BIGc@)6Yh?Qwx6S^VR_+~T!7al*5S)XhSz=EH^--5E-&)y7y4S>-;uEDcM z`kLJW$LGwu=z?9u|0=C%n!V)4!a@Ha)ez5O8QrPix$l-vR&PT_f{3>8IKU$~>oEBo z$E7=RkI?FegF+3S-cKOS49GfmlT@2tM*FrM} zZA6BJod8VwP3Z=)=Z(-ZX;cSO$R7n5k(T{GQAG0|4h#yXbW;AIHVh%{35~r(_^wr1|LdO_hVt zPx+mUZzsCGy!Ikgb81m~C%1ZnnSJwRGg|p)YG#EK)K-PRN+VHE4>>oKLcHz z1ZObw2Job7o_CYr!zB2s2~<(a6#@U&gZyuA1N?^@+!L=KHx}aU5i1t2<$N%q<8DM^ zhQI%UmZu@Bt6_)oXvmD3m;bsVu2sb)zIFwj1z(oAm8xHL5dx)A4fCL*e7pl-LYvWn za#1>$9nT_LhO41xox^PD-hrv)C>rvhpCGhO`gj~`iapoY`f9M$Eq$~2;5~&=KFu?IShB)+^ToEuRcER?5Qxpg@AAQxXLm!Wcwrg33EM?843diS0re=mE zgnq^>wHV_?xlw|+NV4Y{Exj!|;@MFyqHhz6Amnk~;Lq;v>mQ=!Uq1{ExsMMr`T+-a5@Q83v?8^!qS;=Jyz+4S-h4s3Pc&G~P=O=Wh7v#oGXu3v zDO!X|Uo{|-4-$fp6FwSEq?^_Jx)#VJI$9a3xA}=5shfK1)Jt@NWyd7G%b_x-pQsMn z7u9lwjK)(+8qQx>SkwPxeJh+KhR&$yyd`yX{(c?$sno*iynymAue;qdm_4)dP_VlM zO6egZ{rTb{s*ys1gr`Q$lO}$Xw!T!I_7W`Bom*6DdArD_64(^FGB?Jq5W-UGXHwqi zONQut>sW5La1a94{U*LmP~!GjV);U8TX zESz;s#$64Lz_`3?2nQOx>w4w29fmReT#No~Z5?LwqdBx7yZQTOu#{);lQ^#_!4i0X z^?tnc=^zszo;1|g`$&5qgD8jDTXpFqrsn(7795Rqka2?irE{p%B5j19JkQ=OGPYRh zlIX5NVX)tVkxo+eGb!iuE=;uIJKXe27y#~Cm zgIO73ifQ^c3eUQM!o~1iH=tchh;GrEfVF+?1{4a6MemGGbw8`Mz&#$xd&mT@pY1vA zxaF*he?9b*`f5_rP447OCj4GQ1PVXWu=k-LZw7U{ zq}1n~L=%>{!XP=K0X0!Y)kyA~nJ=SnQf;zYtcxzv@(nU`4MlT}oVW=c`XFD(0YR_3 zyTwx=nAa9NqCn1c1)!YMGXg_VXaPWUNw}h_4#jGoYGj3IemzUQ$s1~JA&M8(3sk)k z`j?|ir~o%W$iMa8fKQS$GL#8G`#Bg6v!X3^Zc3e-Qnmh4!-|f8%1t;zE&9r+vrKY{ z)})&^YFus-Peij5Z6bJ~iCl3C8>2Woq4?mL`MG^|p?)?oqLzfIscz0nW_BTknBjVp zT>=w7=rACcw-h8Hg%a*M>?(_RHaGI=@A6Pm&o|ANI-n1V3wvh!#&f9|+16?#O~ z^*iiFt1GL)U>>bruC2fQ?VnexK|^1D!{crvT3uTWrm9gT`Xl6z3WcHwCD?Jl{_=~` zDYyTq%B9-Wn6bDT`+KtiZZ@&>%H}R_HozDTL1=(tRmFis+j#!ebim9Rlz|y9S>_F5 z7&oN@-FZOQKK9}B3v+pEDNw4_lBdJnFw6cs9`Y3^@|6y|2zmTZ_d1y4^*8t0n1{O} zd4lTSZbN%}1A+22D`LP_%`*TeiLmE!!mX(fiaZ zxaJ-D+25WDH{OMLNV+@&`8i0$<&X=PUg-4O;IPa7HdgC?e|^vD_4yP1+p0%Tq`;WN z;J-hGq4THE|Ly+@=>PVA7WyaKaFRlE-!5l|&^67ySuZAr3MRiv|m@aUCRuvc1P;Yxc9zICG%Hg*$id+Yk-bFAek{MN;4 z4jNUgC5>o-JYtW}-w-DszJ-UUXHY!ND?*$l_fi@&oL}Gk*`?HQ*${QD@MTfo>H1 z3c|q!4Mk;;@P-0T0OPeVMK4kYyt(hCb0S;9F!)sij3oywXIUX+8zs5{sz77EG-4^$ zrjaCQ(}=ZI^1AZ0Hn@6lT5nky5$!IVl%~~(QdFhpD@C1-7Cp=lRiXVRb#kBsNka*H zGCNnHd6Z+Q7meIG*YHeJ)I0rFRfXikTO2JYEq3ItX2Rg|W<__qXgJAT9}X@(KwuJa zGcA6w=6!Hui=9VQ<$jdEa1#2f`(oweYQ@f!VwcJB7 z2%1sN9Znwv3a~pf+kY*j`JR%@eaA;E6Gd^|-dS@%ESFXVG?vACj7b~J{-EN$^BY=c zdvCIJyBXF(^3b;;m$26lclxr^cZ0LyHM@q$-c2xNt1z$GjjDJuHePqpadwhzT<+pH zpxyl(#XQH^b0~r#se&zYpz<3nc9s+y#ZpepqAD`vidX{JZ~7{SIR*4x-}NH(ZDk?6 zQDH_P&v*Xg2Y%>V`_J#wlc)R3IxV-lMww>e8|>{JkI%Qtxt zMW929b+2%~vI_E+A1qfK8aeO+0MF9~M8nIWFZ)1DF>{ux!Yu=!6F@Jvd)}O#)qX;9 z4i}Zk>T2f($`gH!pcSfg=5lmH;Z6t2gf`&LgHBr=0Ie>HShNEa8PW1GKY~q5Qo5L9 zDQEjv%;&ZCCq;B>X=gRLqAy)28BR+3ArsCeT-zjnQ{0abT@e#`B*fIw#;nBrqpMuF zU(|DQzI;tba|ZW0X1w4mL%EU}g-*?8>cxM+AQMmBhvP=!n`Un4kA!pvRjq zQh?2{FBX`2LU)#*-iLs$O?}DwH=r;sp&9nEpY^WA@TD?{LxH3Q%h2i|U7Z%Bi%eTr zswOYnRO9^I`pp5j<8Ei+x=?);5k+~`gxNZ6mg+$R8+S%~+6vz*$$N*T5!-seU=w_7 z;8Xd#%<&|Nc)gXs6_aR_yae+(KD&YOeSZ}5G*d&z+R@w_6qa2kjGx4qD3QM4N_#G^ zNjU`W#8e*X2lw61vw8a6`F(`-1=jwq#s@&z-YiPNBl!mFl}(a>=e$&?$p8taP$ykT zrFyt!tTdUbx+PDk?k(`)F#TtNtkXRz_> zN9YS|1-nIe>14MeU?00tkGp0os_=O@vnm*8& z{0_PuZ{_ky06VW8@9jbO!n{zx7KRa>@^G&~SPM=SHsVLfNCojbzt4_nWtaazaq|c0 z2`qm=0}c7H-Go5=P)(_3mxour)yEfe10E(fU>uv9^3dIs#Vy1%3BD}fArn$jdBGzt zwgOD-(9Yu{0p~3g#Q_xe;5QG_^IY0O`Vi3fkUFYVKA6VYLwj{T7>fIBvrrlvs!v1- zXhldHjPJHnt&hT1|3{_v=wGAFXO~}hka3j&=M7Z}pF*u$EdyDWrxko<$GYVnD@6C0E-FKr=Yllt@VPy=JLT>LH0(oUm&x0v zvPx+ZG$1;yPeSnOYMRx1ykcc5X#39zLH7& zx(w6e$D{*%LT|slioX6uZyr-np%EwZ-iT%58!=}d0L)JPl+)UXuZmOKrIK_gOq!Pw zag|XlrwxlC%w2dsTlRf@C{G@K%SghO(SL|4`>9UgP?*Z+W{J$_Y4~$QMdS0@_({s(0?0xohtp!NTSR z3&?jGZSH0dvZtu^8Z#zWx1=2*Z6D~uvC@s_X+l*;40`c@3FL&jT0b_RlgfbS3Z~Cf zV7h2_477J9mt4ZNQX_=SgN=A$T$_?3wWmtTf67xu4 zo-N;*(y{bdCgc*AgFpb{1?*Bi1p&S}0C36~ zUIYL<;5$*>o`3GKx}5*Iod0jURCEYv_>}E_mmZ&D2EXf(t}h(vQ=aHk|1fjQ@<0Q_ z`v5SNGdhBGhggCv`A+{qGEF;Seg_k$uVCWjcfuXiXE86_RGSao7$o&_^gX}>6!4;V zkK{loj5tNb0wOHIbSilkL|~r_gs^AFLWr*lAo{UFh_4nv*jkznKzJzzKzJ!%bBnx? zFO4+%4|f~tp~P}Q)-0v(j6ID78c02QO_(L@j+xqdbs%YcWo@OUvWAcKRw%6)qD-D9 zQaPGY(t%A>LF342N}j>g@_cL9H(V=?#AeE8a_kA18%7YzRGP&UxFaPHT(0~MFAq>+ zCe$v^B)3Z$VOVC~k5(faWQ7B^ILA!_r7EeS!$@eSFVT!Y#O8{-7Oe5w{6+^( zXaw@(Z@fLtn;G=iXjehu?o&{B8o5Mamf3~A8>pzhfQH5=7^*LUpt9|V}gJ97-yMd2Nqz~K?FK@Jyx9UoBi$S@7++3%*073 z7(N>E@N;t-kir@YtYM;l{6}tar8>0=4vfWe_VqaD01e0eFmX88x3O3WW9ky{NoTWz zn5@Dq`gH!Jg}dRC7QXdfY~gPBYzqo6A7+K{^H628Uxts$%KPkOGhdvu1MEHMX+@uh zPkMtSsE&=r#ZC4ak*Y*%b{uW)CzJ?O*JtB5Sc_dtkQi`{zJ7U9W$}xv67)bcAymf4 z#OCj^@`fWLjN@GX4tiVmh3gHl$Rl-U6OjgeW^AGuQ4OF>u88zc3%(gpU;ReDIf;uW z@dhtSWEEstKntv{>!v91i;dsZDbG*9g%>e*Q?d;&+!Gi=7x9yA zgo-CmQq?a1bOLOmms~AYM=U;Lcve@70kDPqX{Izt`N3><-N7kjcRDZsArfULF9@ z_Yy|~9A4>UY;b>W@VjBS-wQuMkvWTWQDu(IrN9$*tV!XqRd_DhqyWXW7|;7ykzG{y zZHx+$;A3SwMtQ{AzY}*zbGSOsa<~!>98d$kWS`-0siBs*gd|B;moJPPwT8nIws2S= zn*cK__Lx~Fai574{~pn^8nKR|lOt!($h7@HM%P_LfiV&)OxR(<$gyuh$W;`1s60g; zK+$c13py53=Qf1j1PcE~#AzsUx{iGZ3NeW;Q?Llm4eiaq%!&8{BiEQnX&uN+iuw8# zd<`UlRkfAyK0xk&f~*7%)`&fh+~py; z6d;x;b>>lQq==;d)d6059G1yqQdVO+< zSt+o0$I&ST4w|?NA?i;e015|HNn2wYO}w0W-pn}k7?2P!j~rgbf0=kMZlj+=ynMbv zmptbnwJMNh!kxzX@)})>oI`bl2}4bwpfjj5fyGvtRh$t&uKP}5&f)AMsZfdwii)TF zTIzYT0P`y3102p+tPp19_w5^SJ6K?uP2^Uy+4VPkTGmuJlhfR+Bubu5CNVbJXYkcn z>Y}$Ets6+m>~g>!)bX=^^}+<)$OLO!SF3LWPQo&4cw-XGTqN0io5@eQh{^M$wkqNkw^0gl8#*P$2@|p&69t;Yb>f(#y$F_eYn=6@rJLE4et93w^dqOaQ&oj^ zhO^WT?9ZbcuLHH8*7myg7quPCqncOS>t~dYfWU}X%HX6S&Q%BEe6Jv9vvD<7nmW;GpZYl-kR! zH`%Ewc7b!amM@28ukpeEOeN(I)p(UOl1i*bxkav$PKHTSkJUs!5FYiDx6igp z2wzrEiM}621?T;s3eT%K8!umOZsom3G$hzgP`H)DA5=027$W^i-4Udzx~87llL z6#~9yOyG?3pi8E#v&f4M(8DuOIq$$Oj1j@5Oj)7DJQ&MrQsbvf88RYOy&78WlHlfcSh zz62F&f}fCJJ;_mznA;5sva4cP5Xq-}k`E6W!FRpAcG-kLGkVs9B8IZ%BBRmC(#5ld zK?u4E9;rV4_@UwNy0Vo&Fc}X-^Bxm%_ze-kYbsSP=+1`M?vc`Q_Aq>nKzuTkaOYlx z-Q=(itJ5-7{n4WgOTk^02^4ds*B<)z;j-&n;6r}UwE$ombrYS;7Vf%Ewgn8R6?VCy zy92C53qv51-4!IeOU4u~Tld+o{4B0zSxl6=y*#>~LM|o*#uu$zi076)=Pd);+R+qQ z!9#^=pyi-KrO55as2f~F+xfDk``Q2ks~4VmYF`E+0^Dd9VCMaRnHVWsvmzsKo2@u6`7&8{z4q|~+%eJvkP;Jy!&lK7b^LaOl z?n)Qkurd!Yx+!NZ>Y4_o;xi~9030%mO|Tuoi)dM(OcwpR6W0geoxN(4hq{e_W#RlP znN$`fV9Q}GtKayfK&jOT6{@cVbcHTi>T#Y=kD_Ux)bUL5HCLR#j^p4SylgV!@8c9!BFVu8BWWx9F%@nbq?I`>$_pXy9m*wfT{PQey= z#CFK=^5pM4L+LQ(f7hwA&dsNLi!A7^m-8H6-xXi1WVIND>ha`b@otzAIR1em8YqFgKVDOL|XvxD&Sh zAvdzdBha(YkOV9Ontnwm(axd8@xJg<`%d2uSYu3t?@y+?dYn_;FCaVJu@>5{ftWug z{63pNfg}9Nzo6jZz~f5*FCmikWfH=*h$xbrM*GZxqlj-NWrv)S?-89-GK!WtdE>dn zBwz{!B~|0Iz~naP!Gjuk TdP74cDB1a+0cR|Z2FL|d=w@fo`7K-0jXdn~qAl0zC zu*vK*xzsE4vS1SVC~1bbrm6ZO>Gr}fkH&MRk;qUSgW-LWzpuv>mp~Q+Imd%&!+l?& zTL=rryAdxDy-7Ulo+3UnaE>yVZ{t!I0DTSowD6%K_FrayOu0WGeE~mndKK;0$Sdgz zTM4XqA}3>po@dlD>X{y!wCjEi_DM#c2cqf~aZ5pTn2Z{TSx-#C24c&)f)pSW6+4p5 zp~*QxvX(Xp#y$50;q&f>rh~Qsp(2pTPW?us@t44Fm-q(8QjM&qHUy z1@m+FvgRp%9(IU8ifDJL{t~{8fBbl*H7ay=uFflJHPGTZwcU`K6RzPgctb>?P5@s= zm|C!=aF+#Li!F9#kNx-|sR-ayRKz$n4Q;s>xN}g{L?DZafmJ7g-Wk(P!!`p1wwn>F zFwGV3V`5ctDqK@^ACo?Ssk+&tQdIn5x$P-QKxX5o(Vxs_l@hACzs=5ILCVB(g^2O} zRlomud}N|@m9|r8r74^Xw^Ivgl7p!T_RP2eQ?^ZCB(fz+$uNPdq&pq@{UaONVaa6- zN7Yo{tyJia!Yp8M3(=I!mc#^JJ7@h}y^t{pH{)UFLp%pYiqKKT`gGBQlQMXjYS@`D(}1bf83<6BKl)&&^Q0=R zD>;}K=3LrsDLc2uIE37{m@a>1B~o8YS*=6oX6mvXv@G9aFnsFzAAL5&oH!ye?p33K zN?@j-6CQ}8yj!7T5Zqf_c_ICZ+sdQj*JWO4+h8lGkQWI)6KB7tP1xGo>DnEbEOWH& z9IiKuibYYE_a%aPl~FI9ySzp`Z(Kt=@@(tJ58M@1Jy*a8_^cY?7$Mm01c<@eFw4EfdJED9x=Uv!2}4>7 zKA3tSBt+kV&D**nj2J_ma;P%U(2nzv ze01>-*rvo$S%I$+%bGW_JO|SO=o|DC;$mHOE&!h}*ZBxA?1D%-mT8$Ua!x_(i5Bt1 z6#xo58%u$%WKBiu7Yg@5qetSU%U@6>E&i@wVT<7REvT!2jZDb)UB=(HR{`76r-|mQ zOAe?UHXu))2&C_34d3A$O|kEmagtg*U~Rm3eAetX2$qM@w8|?*t4p?RmTcQwG6GhlW8I2K zhO$UhCf&s%SeYCiyhS6y2*B=@a%(8F{U{fpaRB5K%w2#`aV!*&DL~&sRy2^+*HQ_L zo8nZ00T|skb11qibUviK4?gjLbXVwxs8q`817se>q*?CK-~!i>9W^xv%miK7oA`no zvXvB-NOstSvsf(EX7L8z=R!6a8VNFdENq#$yO<7tj)h4YZ1ZL!B z;|^BTb&cl#srlU#t%tUVh}sgNkMLj*{VMLp7OL;X`TWvmp_p^^?!_YKL6N;Um#V?5 z;MA*Nd3j?yw(__q^_;qTc4JGdomvG3vc3)WF@$1;rN8q~TZD>C_H%r>3juJGIEI8z z1N60Jk|L*mk>hbxj`PEDa0q&GoG-`4(qd3G7JJko|JH}1 zBH(WFkzTxdBF)&83Lwkt1(e(#IuIZ&M5z{Tb}OMz5ZJaA&~q670d|7o3=Yy^bX&of zF)5p%mB8GirX!!!W2d7~T9NWoz=E(2frJ-Ecp-#q(fmfO5@HXvb0tgkw}s{pfv%qN zPkkO3LctOYI4rHw{X9gBAHujs$24mw1xqp5{U4flR6}PMqsaUs%pMT^3S281J%fa$ z`>_w*#yP1r!{8Qnw|j%Gh-8U+3EAE*M+iN)o9uJKA%2N-Y&2}pHC4mGukC9^_mqQP ztq3k$B%DLnZpt+U!@BX!?o$tJEn|luwdX2M z8{{#tZIC}|4_Diqt+sKRp=tVTQadiK!<1x#+U~Tq&Duz!JW86GeK)^`U14^ZA>NCF zSVDwBI5vePVN3KDJ}snCVfwpanJ8hRJS|iRy|ITKzqG<3ZA0e0(J2}3Kr1rG4`|J1 zIswNc)g&~fhkgQpZhax)@efd~xMj~x#P_L(U$^h|XicMOsbrn}0;3W^2!zqZ=0*Mi z3{2lh9bq?I`)z=@!x_ECxm9%2H5Rq+J<98zJa$Zl`?kwX#hG?<=4T|6+YOAG(=-~S z5~-U3YRfQRZ=1u>SeOCi&YJMxxyO$L{5z?tlSwqSxYFKqO38Za6{HI05`>qX%PxoO z-VqAT>bvx!OW@M>KM%0}kpQ!h+J<{$w_R5-G@#M#D1mOpwli}xNdRoui5HE# zRB6lqX$w#+cHOnqHMde%ogA4YJ~B=HIccIkL)#dK0D$^tI0E%%o*ce;fqT<>h`T_( zi7j^#Ak5xHjy!Gih1MyLOBG2a=>lxq%V=b8 zqY(u(+nA|W{3RqQQRrhA@!96+j}cW-taXC@1!5-)<+R2XJZe zds2S5_oqsT77~O{LF|ws1M*sssZjS7q)AhilU=~mME0N|BU^n-%@0;3hFd6fk$i@f zSKIO#CiK-X=6B!CckA-q%u3~tp>)Ae!!?A}pe)fmLeSd9n9YBQkQEC`WB_U6UvY?! zEB5PSJ#AjvnwY+gOE|$?VGS;H< zW71}e*EKm)1qJsb@wi^iNL&Lkw-A06?ig5Y*?8_P(2d2GhzzkFopG*o0auOTHlhfC zM=9ZXDdWnKd)CDnRg#iuIaz3i?WLR=HWqDpoEp~jz?D&}E38OS(OwJ+9P2DrdrNG2 z3S96l@#=ImhJ)X(Oxxw&827S1?u@T=6dV-)U0ck>BQF_XFx_Pwf`9$~YoP!!U8v>n1bZZ9Hv984*h$1I){qN+c5l%g-q-KoU5#LHSiQA4(pvP0g7P zqq1y?qrT+OiHW!oM?wV5bsMhtJ1*xC7%tw)LvBN%rspztssp@6&;@9{ipuD~gj?bW zIlGjtlt)p^_k1%-fZz1_PEBPfe`u!!!2i+b7*Kszn$Bu4>tO`}9IjFFo~e$eX?fDT&`2kL^4JL9081c9e)p_P17 z>P|qq5yF>)MAZb_%sXROMbR*!lbo(i#p^H@lwI^rs2`{bDDnmKsLg7FdUsSWLzxtwY-uKP6l(`vw3&V^|{;wAhv4T7y=X6S02|G~qVIb~cD? z+v%Q=wRAKV7{4vu^f`sl%u$sNi$qvC<+e=Sg|OUYnJ%b7BDHH47ykSJsv`N0ZiT*yV${H zp6zsfigw{hg12t;&I08y%N&#QY9244z*CQ@X^K|ooL-=5XSWqLNR2Xi)CrPGa}V3LZ?PA-6S$e6wRM9mTQuzN%j3juLOAOuX=885pO6w+ir6%Hb1n=zUgj1clIg{}}y-X|y>t3`pFA9qbq|t%aLiltAAr^-lk~ z+Sy`C)akigGNIi3Q|PRKzNNFTAS!z>0!x`PW_>7#-kwgoe~#OzxX7lbTK9A9nk>sz zk)?*3f10X+5l)C%W+H`Krm3lN8%6z9^e4;0DrAH0=bCDZAoH(=_n+^&8@vUgTYa6L zqSK-G^Go)1B{9d_=3tD6Y!wN#aJk{;!U4dA>N1UUV`d9dkli+vvn!FzjlL&Sr1f1u z7qYL51|s0)sZyRCG?y(?t|hbg?A}@PSqBFx8OIcKao2{>3Hc0q&a%u~iBb@8XTv~m zS>2$nA2F=|^I8LI5!qG~$g`BNym+f&)U?@F*9tG~?8 zxbfk%faRd?TiWjvU%_WBQ82^L4SX;_QHfOE1zBQc&iT*EP%E0qL%P#>^cw&CxM6*t zfD}fODkKM1GCCJ8aQEtg>daGR2BY;st0K1Ej!%hbLi{NPlWb%U_SuH#vmF~4cuHll2?V}+u5zX9kYZW8 zchPHH&X7+>u3fP}g>ksqGnjunB@pGN<^-Q{k+`jRE%M>V53l^<6dDR^jeKzn2we_fdk}H}^WDitijcCd2rbIYKN&bGVo@ zdEB}6q$v6s+(EmX5NJv=cUwd;_^?tdWo?f4ZMsma@Av4ho0u>P@R-8SR885ZU04{l z_hGVwryZ+;B{xNO>WgZj2?vh@zgG5xn71 zH4v>nh|;+x8-kQ{>4Gb*gjY*MbhIpjkU=;`SU&QL?nMpGlDpWnZL=3HoLZ4OcZ(B6 zC9}HNhE^&TlobI`DM`ynz_35ti@=dfn|xO)$08p9MBgM&0D?fQ8~1`&1{q~u8&`RK zf~hlb32v#D6UVfyUl`zI2|o9kC!sD&>>h|I8A;DJYi}9BA&T#6aR@_-X4kb;z*988 z5u)X^tPn{`Qy6vX=zLS`Rm6KtR)iA8skd*M&+QusNftMbZj+YGAmk-b$OeN8`hD#y zTX7(7kOrh0c-{2)eO;WI?#7lqG+pbmek!1dtNLncbjtcXjvSkQ ze*gL;Sw4$XZi5Wu-MyUJNFX?41ev9aak$I!l$UO4FS98K+sW*e;(5 z=)^XrdLW=ym!Fa_3+1n@EA{NA_9(u=+^??v3yNRk=!81}8$b$8dQo;TUOpb@xI;$c zr~wVu9_~7s35iNsj%2<0EK_(kj;10#;8xjDf>KF>(x4x@xAoHD4RrZHMU^nu z9NWH}fk+-+ejJ1UAD4X0GC74f3Ckbh>aFJ;z(cu{-FZ11sd|uiUUFeNKdA)_zf(&; z0I!+%u!WN=0JujtklTV+05q(}*tG#j&ePa;FVAm$uy(2jLi`1BrVBcRgh~`Iio;>5 zJ-oiqo~*%oxTX(-UI4*JnDE4FA|?FS4Q@)mo_@-bMv&npzxhDh?&>e3pY zIvYwHNM*#k+S;h8os@06vh+y!s4~`ySah7r-;-WS8?=nL%ny=NOy7eiJ^v!a*x>W% zq$dXAE-Uil@KDVCP@!_=(Ine~qFzO98IlOmf<%j0U9#98s74`tR?zB@K!Vj#&yzFW z-6KHjar1_Ie$>vEhg8VAu0r8H>Wz{C@LM8|--y*xeiw*c3r$a`97dxNF>ax8g%m_I z=zRoQfN_8Xlimlf2Ic_f8R4i8Y&zmmPxSe~D}UgXKk&*Qc;(L}y=R`0ehO8_iq!{3 z*`7e_iZwYw(&FedNHv25IiHC8Z(SmKv)k3lNe_Jn^CN}o9KB@t?&S%t>~>- z9!0d%haFH<{>M7@7UOEeYQB^qr#Ta5#*Cuf&TmV1B||!j9PJ+2ZZ3<3L;Vu3vvFvq zMKVqK&{TjV#7^e?%&1iWvqfilsc*3K9g$Lm932vK2*)YF?ol3HHs^tiz2JT%8XI)S z*MNe>3^4l$zXy09Li|9Ni13*DpeVeNfF8uFaDE3~c)a>rocWQt{;E6lIPAFxeS8Cp zl6SNfdV~EqMhPf0BjzL)Me}okQ)#~LNbYq9Ns^DRawgN$ktsk&28xKDl8oW!LIb)5 zRX<0C&4zeq`*nSoQjxmND3g+tllYkWq>bc6KrtR0xb|v*TE^o7`V-!FnM=aER~o=YO_Y~wcs!C+L|br)4K`*zMC(t!SC*vnn{EiV(o@~O2=D-P0r8DnzTKHUGkibix!oDwlzSbVa!fN zMqTRxaE7$O8r3pTpd^YeO%)6`W0g0FHjo#JaOks?t$&JEN|J6R>~<5ioPbYl?x*T+ zgF*>j18pL5dS$dJ;1hBJ8Jkm;&soI&g?KiD1N2;-avyZvA|3f>KaHY~F2Wmngtyo5H*x0d zjbb)T*kL_Dj>S_=2Zu!T?``Bx3Ez%i2?|hf!R{H4r(o>?3p5qcZ()@&jLi8cp?C3w zr-hZ24#NGMDh0Z|_o?Sp0L?cEE! zlBl zYW!-Vuub2YpGJXJJ*3BW1-6*2eXe!r;>??Os%bW2l&n z1%%Iv;kS7km4(|x^K<9`8C#&KDy(b=Fh<7sv1dUBIoUg9J$KL;WUc~Qj?y><=r-t> zqr2!7pvzC}WW6_L8_#+L@Z3x7xx=Ve5;!?H$Z~GFt6$;fyRATV&7hf9JP&`%Hb>kV zP1vDwzDw;O;|)$KVN%hmg;CoC7;szkMaYKhu8KMH_HyD}}bzDq$kkO4=*eP%B|EFp1qDJ1I%D4Ec^WN7fT7!Ifx0s|3$_ zp`-09W_gEDO7F$0I)7(IXJp^t3LbTO&gvpFrHBiI+4LRW@g2Id;Ee$)w#XGLsQWvU z0iu_-&ZL72IXyW$>JP_N`EW*ce|v#-CD^V-v{=I1%Of*vy(~aI8Dy0DshS1~$zr9E z4?(v3{+G8+gRdqZPMQhgY7ZC&o?OABQMlcQLsgfwrb$WfJ{px3ZfxTpCn0`sG!`yt z;Oi7Gm>-CuBRtlmxRBGxS~(FMwmfxtqwn*Hpy3j9z?Emow91!Y!iJ~3VlugF{kfvHvn*G#B5iL&| zLwuvQ@98}n+>wi)bNDD|bFhE|9EnS%jJZJcEvkAX`{})ZCPZ_h9qH7n2K)D3*kNiJ zeu==7^Q0C0u2lNgBgfe!6Aw1Gf`GL)_p{t=WzxWsfe+xvh|GU)r63G&UHaNpT&-}A35l6t~&t%BO&t%*zfsyZk zz@9f*dv3D8-oK{!jV%2gc>p^f&)zl^E?$Rumjkee^=VCB$jf&w4zR2CC>om9j}MX#vv_@}*sx#We9Z zjTQX1^ul__Yq7j@yr(Pm@^)^NZN%}0BgLEDt!B%?CJ3M3x*gKFk*skR$PiT-;A|CcB zJm~lATM=(y`;%G~@yBQR%hLT`*nnFD{MfV&O=A`jdz{)G;&^nypgb-wMOZenPHwmu zp6{^_a|@#lKI_G*gTN|i0eL!m<~h5(S-03M>D2$A{q`o zpYa$H;1u>I#3()=je8!I)%6J#o;R`|;n_px4&umQD(^9;Q2Z6(1>CiHPnc}ep2uN( zRJQ=SSm?RwFok+|)>?%F*^9$bZ*!0K`9q@YMYGOBmDH1`F%0xd9`f8_KQkkTb1J-8Gx z8Yq0@*-(79+tO4GvFXAjVbJz5#c>s3lJH?3oMXUs9k9kO+}3!(yEb0%sf!nkqd=ol z;R$duyVei*JQh@y(6s9iTYyv^dB7K(4LSHQ(6IA3i+Lq*Hg;%~;VcJYUJMWn7SF)i zJ0FWe98Va)^EM#}U66!)+m*LH1-iRpj{w*RC^#4+TWUtj4(OTm<-<6Y)gLi-jPwr_ z&^S99=W%F3jw7Q$5%vziN!@f~?XSCWuicp1Zj`mH3)79M*9||b`BFEU?8b!wNq2B? zLWST4jLO}EZW8ZSHMiOXgPi~yfWM_@Uep@LTSr5qxPvw zEE*SCWks7~j?j@4|Dz9&(|!tW9Oj z{?nSIoW1t3Qbksj^&l-E4p@SKIA>e{ytPGfmiU;irBS=VY|P{ThrM@OZX;>Z1n>JP zval*OP)dLR4?2JavRR}kQ6ecyph~|)qk#z^0b~&%KqN?kqG0x4jm=(-+05?rjM>`W z?L5HV>|U(x#m<}5lkDg25s{GxfK-*LXWDGq{w@)jk#Y3!@bJUuX}}VOp~dp5gn0khcGZ&Jo3L4_r@ZV zoSSDK=^2g?IrE9m>1K2OGlaUgBE&=cfPb`SACY}@u&Kj~gbMpmb5Hn$r4v_Cw|l~l zmi$DYk4GdlTj%4LoMQ+rot3hT+5z&Xb1Hc!JD4OdWyILWfHFlG0C+q8dIOCcm^=PD zhaETMXTMd?Ii5=IvfpqrAxmY3}5RLS;2{>8y zHQnLQ$a(3c%a;=U**Puq7d*-!`C;gCdumN}Cjmvqx->yms;Sd;|J9d3#Q8&slaMM>r%P9|Fk%{<-Jw z3EOF`i#Dk>NE8O!pw&Lt?&U9swOefk(e@8*9bv18rGujL?iNOlmqX4lLPLEYxW5GM z%g}vksemt2TWgo&v*bJx7A*W1TP3}Hl!ehqc+ism4f&?p?ymvA0dpJy<58KSggJ`R ze%;7SW09Gzw9GU$GE)~a(`2 ?4ok6#<>VFGD&bUj~pOqs{nh)*$Zed&O19<^8%L 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 E0Gu + {(pool.startup_parameters?.length || pool.quarantined_params?.length) && ( +

+ +
+ )} + {evalResult && evalResult.reasons.length > 0 && (
- {(pool.startup_parameters?.length || pool.quarantined_params?.length) && ( + {pool.startup_parameters && pool.startup_parameters.length > 0 && (
- +
)} @@ -686,29 +683,16 @@ function ScalingBlock({ row }: { row: PoolScalingRowDto | null }) { // Operator-supplied PostgreSQL GUCs that pg_doorman injects into each new // backend's StartupMessage. Source is the cascade layer (`general`, `pool`, -// `auth_query`) that contributed the winning value. Quarantined entries are -// keys pg_doorman is currently NOT sending — PG rejected them on previous -// startup attempts, and pg_doorman parks them for `quarantine_ttl` before -// retrying. While a key is quarantined, `RESET ALL` / `DISCARD ALL` on a -// client session restores PG's built-in default rather than the operator -// configured value. +// `auth_query`) that contributed the winning value. If PG rejects one of +// these at backend startup, the client sees the PG error directly — +// pg_doorman does not retry or silently strip the key. function StartupParametersBlock({ parameters, - quarantined, }: { parameters?: StartupParameter[]; - quarantined?: string[]; }) { - const quarantinedSet = new Set(quarantined ?? []); const rows = parameters ?? []; - // A pool can have an empty effective cascade while still parking keys that - // every level used to inject and the operator since removed; surface those - // as separate rows so the operator can correlate the gauge to the actual - // configuration source. - const quarantinedOnly = (quarantined ?? []).filter( - (k) => !rows.some((r) => r.parameter === k), - ); - if (rows.length === 0 && quarantinedOnly.length === 0) { + if (rows.length === 0) { return (

No operator-supplied startup parameters configured for this pool. @@ -726,30 +710,7 @@ function StartupParametersBlock({ {p.parameter} = {p.value} - - source: {p.source} - {quarantinedSet.has(p.parameter) && ( - - QUARANTINED — not sent to backend - - )} - - - - ))} - {quarantinedOnly.map((key) => ( -

  • - - {key} - - source: - - - QUARANTINED — config no longer carries this key - - + source: {p.source}
  • ))} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 7c67f63ad..0cd8bfddf 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -90,9 +90,6 @@ export interface PoolDto { // each new backend StartupMessage. Backend omits this field when the // cascade is empty for the pool's user. startup_parameters?: StartupParameter[]; - // Subset of `startup_parameters` parameter names parked in the per-pool - // quarantine. Backend omits this field when nothing is quarantined. - quarantined_params?: string[]; } export interface StartupParameter { diff --git a/pg_doorman.toml b/pg_doorman.toml index f665701bb..564d3055f 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -410,19 +410,6 @@ hba = [] # PostgreSQL Startup Parameters (operator-defined GUCs) # -------------------------------------------------------------------------- -# Consecutive PostgreSQL startup rejections for the same -# parameter before pg_doorman stops including it in the -# StartupMessage for this pool. Paired with -# startup_parameter_quarantine_ttl. -# Default: 3 -startup_parameter_quarantine_threshold = 3 - -# TTL (milliseconds) for a quarantined parameter. On expiry -# pg_doorman retries the key in the next backend StartupMessage -# and resets the rejection counter. -# Default: 300000 -startup_parameter_quarantine_ttl = 300000 - # Baseline PostgreSQL GUCs that pg_doorman adds to every backend # StartupMessage. Pools override values per key; auth_query in # passthrough mode can override them per user. Config load diff --git a/pg_doorman.yaml b/pg_doorman.yaml index 8df48a57f..3344b9f2f 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -450,19 +450,6 @@ general: # PostgreSQL Startup Parameters (operator-defined GUCs) # -------------------------------------------------------------------------- - # Consecutive PostgreSQL startup rejections for the same - # parameter before pg_doorman stops including it in the - # StartupMessage for this pool. Paired with - # startup_parameter_quarantine_ttl. - # Default: 3 - startup_parameter_quarantine_threshold: 3 - - # TTL (milliseconds) for a quarantined parameter. On expiry - # pg_doorman retries the key in the next backend StartupMessage - # and resets the rejection counter. - # Default: 300000 - startup_parameter_quarantine_ttl: 300000 - # Baseline PostgreSQL GUCs that pg_doorman adds to every backend # StartupMessage. Pools override values per key; auth_query in # passthrough mode can override them per user. Config load diff --git a/src/admin/show.rs b/src/admin/show.rs index ee7f2f2c3..fe0f6dd39 100644 --- a/src/admin/show.rs +++ b/src/admin/show.rs @@ -824,9 +824,7 @@ where /// resolved per pool through the `general` -> pool -> auth_query cascade. /// /// Each row lists one parameter for one pool, with the layer that contributed -/// the winning value (`general`, `pool`, `auth_query`) and a flag telling -/// whether the key is currently in the per-pool quarantine — backends spawn -/// without that key while it is. +/// the winning value (`general`, `pool`, `auth_query`). pub async fn show_startup_parameters(stream: &mut T) -> Result<(), Error> where T: tokio::io::AsyncWrite + std::marker::Unpin, @@ -837,7 +835,6 @@ where ("parameter", DataType::Text), ("value", DataType::Text), ("source", DataType::Text), - ("quarantined", DataType::Text), ]; let mut res = BytesMut::new(); @@ -848,25 +845,14 @@ where entries.sort_by(|a, b| (&a.0.db, &a.0.user).cmp(&(&b.0.db, &b.0.user))); for (identifier, pool) in entries { - let quarantined: std::collections::HashSet = pool - .database - .quarantined_startup_parameters() - .into_iter() - .collect(); let effective = pool.database.effective_startup_parameters_with_sources(); for (parameter, (value, source)) in effective { - let is_quarantined = if quarantined.contains(¶meter) { - "yes" - } else { - "no" - }; res.put(data_row(&[ identifier.user.clone(), identifier.db.clone(), parameter, value, source.as_str().to_string(), - is_quarantined.to_string(), ])); } } diff --git a/src/app/generate/annotated.rs b/src/app/generate/annotated.rs index 06d11383a..a01176b7e 100644 --- a/src/app/generate/annotated.rs +++ b/src/app/generate/annotated.rs @@ -1033,22 +1033,6 @@ fn write_general_section(w: &mut ConfigWriter, config: &Config) { w.separator(fi, f.section_title("startup_parameters").get(w.russian)); w.blank(); - write_field_comment(w, fi, "general", "startup_parameter_quarantine_threshold"); - w.kv( - fi, - "startup_parameter_quarantine_threshold", - &w.num_val(g.startup_parameter_quarantine_threshold), - ); - w.blank(); - - write_field_comment(w, fi, "general", "startup_parameter_quarantine_ttl"); - w.kv( - fi, - "startup_parameter_quarantine_ttl", - &w.num_val(g.startup_parameter_quarantine_ttl), - ); - w.blank(); - write_field_comment(w, fi, "general", "startup_parameters"); match w.format { ConfigFormat::Toml => { diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index 0c94b1936..2116ee9c9 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -1149,41 +1149,6 @@ fields: - For authentication methods other than `trust`, PgDoorman performs the corresponding challenge/response with the client. - For Talos/JWT/PAM flows configured at the pool/user level, `trust` still bypasses the client password prompt; however, those modes may be used when `trust` does not match. - startup_parameter_quarantine_threshold: - config: - en: | - Consecutive PostgreSQL startup rejections for the same - parameter before pg_doorman stops including it in the - StartupMessage for this pool. Paired with - startup_parameter_quarantine_ttl. - ru: | - Сколько подряд отказов PostgreSQL по одному и тому же - параметру нужно получить, прежде чем pg_doorman временно - перестанет включать его в StartupMessage для этого пула. - Связан с startup_parameter_quarantine_ttl. - doc: | - Counts consecutive `ErrorResponse`s from PG during backend startup that name the same `startup_parameters` key. Once the counter reaches this threshold, the key is quarantined and excluded from subsequent StartupMessages for this pool until `startup_parameter_quarantine_ttl` elapses. - - A value of `0` is clamped to `1` at runtime (quarantine on the first rejection). The intent of this knob is to absorb transient configuration errors without blackholing a key after a single backend hiccup; raise it for pools where startup errors are expected to recover on their own, lower it (down to 1) when an operator misconfiguration should drop the offending key immediately. - default: "3" - - startup_parameter_quarantine_ttl: - config: - en: | - TTL (milliseconds) for a quarantined parameter. On expiry - pg_doorman retries the key in the next backend StartupMessage - and resets the rejection counter. - ru: | - На сколько миллисекунд параметр помещается в карантин. После - истечения TTL pg_doorman снова попробует отправить его в - StartupMessage при следующем запуске бэкенда; счётчик отказов - сбрасывается. - doc: | - Quarantine release is **TTL-only**: pg_doorman stops sending the key while it is quarantined, so a subsequent successful backend startup carries no information about whether the operator-supplied value was actually fixed. Releasing on success would oscillate between "quarantined" and "rejected again" as soon as the operator hadn't updated the config. - - On expiry the bookkeeping for the key is dropped: the next rejection starts counting from zero. Lower this knob to retry sooner after operator config changes; raise it to give the operator more time to react before pg_doorman tries again. - default: "300000" - startup_parameters: config: en: | @@ -1214,7 +1179,9 @@ fields: Validation enforced at config load: reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`) are refused; keys must match PG GUC naming (`^[A-Za-z_][A-Za-z0-9_.]*$`); values must not contain null bytes; per-level size must fit the operator budget. The full merged cascade is re-checked at every backend spawn against PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) cap; when the merge would not fit, pg_doorman drops all operator-supplied keys for that spawn and logs. - If PG keeps rejecting a particular operator-supplied key at backend startup (sqlstate `22023` / `42704` / `42501`), pg_doorman quarantines that key for the pool. See `startup_parameter_quarantine_threshold` and `startup_parameter_quarantine_ttl` for tuning, `SHOW POOLS`' `quarantined_params` column for visibility, and the `pg_doorman_backend_startup_parameter_errors_total` / `pg_doorman_backend_startup_parameter_quarantined` metrics for alerting. + If PostgreSQL rejects a parameter at backend startup, pg_doorman returns the PG `ErrorResponse` to the client unchanged — there is no retry, no per-key quarantine, no silent fallback. The client sees the same SQLSTATE and message it would have seen connecting to PG directly. The cumulative count is exported as `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}`; the parameter name and username are on the corresponding warn log line. + + Effective per-pool cascade can be inspected through the admin SQL console (`SHOW STARTUP_PARAMETERS` returns one row per `(pool, key)` with the contributing layer) and through the `/api/pools` REST endpoint that the Web UI consumes. default: "{} (empty)" pool: @@ -1539,7 +1506,7 @@ fields: doc: | Per-pool map of PostgreSQL configuration parameters. Validation rules match those documented for [`general.startup_parameters`](#startup-parameters): reserved keys, GUC naming, null bytes, and the operator budget within PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) `StartupMessage` cap. - In the cascade `general` → `pool` → `auth_query`, this layer overrides general per key, and an auth_query passthrough entry overrides this. Dedicated-mode `auth_query` pools ignore the per-user column on purpose. See [`general.startup_parameters`](#startup-parameters) for the full behaviour description, quarantine knobs, and observability. + In the cascade `general` → `pool` → `auth_query`, this layer overrides general per key, and an auth_query passthrough entry overrides this. Dedicated-mode `auth_query` pools ignore the per-user column on purpose. See [`general.startup_parameters`](#startup-parameters) for the full behaviour description and observability surface. default: "{} (empty)" user: diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index c4e896644..a0119e628 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -441,6 +441,22 @@ impl AuthQueryExecutor { if text.is_empty() { return std::collections::HashMap::new(); } + // Reject oversize input before serde_json walks the whole row. The + // operator budget covers the merged cascade pg_doorman ships in + // StartupMessage, so a single auth_query row that already exceeds + // that budget on its own can never produce a sendable map. Capping + // here keeps a misbehaving DB row from forcing pg_doorman to + // allocate megabytes of serde_json::Value tree on every auth_query + // refresh. + let max_bytes = crate::config::startup_parameters::MAX_OPERATOR_BUDGET; + if text.len() > max_bytes { + warn!( + "[{username}@{pool_name}] auth_query startup_parameters: raw column is {} bytes, \ + exceeding operator budget {max_bytes}; parameters ignored", + text.len() + ); + return std::collections::HashMap::new(); + } let parsed: serde_json::Value = match serde_json::from_str(text) { Ok(v) => v, Err(e) => { @@ -810,10 +826,16 @@ impl AuthQueryCache { /// Cheap, sync, non-fetching lookup of the per-user startup_parameters /// map. Returns `None` when the username has no cached entry yet (e.g. - /// pool prewarm fires before any client has authenticated) or when the - /// cached entry is negative. Never triggers a PG fetch and never - /// initializes the executor; intended for the backend-spawn hot path - /// where blocking on auth_query I/O would be unacceptable. + /// pool prewarm fires before any client has authenticated), when the + /// cached entry is negative, or when the cached entry has lived past + /// `cache_ttl` / `cache_failure_ttl`. Never triggers a PG fetch and + /// never initializes the executor; intended for the backend-spawn hot + /// path where blocking on auth_query I/O would be unacceptable. + /// + /// The TTL check exists so that a backend spawned by the replenishment + /// loop or anticipation path does not pin stale per-user GUCs after + /// the operator changed the `auth_query` row and the operator-visible + /// cache rotation moment has already passed. pub fn peek_startup_parameters( &self, username: &str, @@ -822,6 +844,9 @@ impl AuthQueryCache { if entry.is_negative { return None; } + if entry.is_expired(&self.cache_ttl, &self.cache_failure_ttl) { + return None; + } Some(entry.startup_parameters.clone()) } @@ -1277,6 +1302,22 @@ mod tests { assert!(r.is_empty()); } + #[test] + fn parse_startup_parameters_oversize_text_returns_empty() { + // HIGH #9 regression guard: pathological auth_query row should not + // make serde_json walk megabytes of JSON. The raw text cap matches + // `MAX_OPERATOR_BUDGET`, so anything past that returns empty before + // we even start parsing. Drop the same value into a giant string + // so the byte length crosses the cap independently of JSON shape. + let cap = crate::config::startup_parameters::MAX_OPERATOR_BUDGET; + let bytes = "a".repeat(cap + 1); + let r = AuthQueryExecutor::parse_startup_parameters_text(Some(&bytes), "u", "p"); + assert!( + r.is_empty(), + "oversize raw column must be rejected before serde_json walks it" + ); + } + #[test] fn parse_startup_parameters_invalid_guc_name_dropped() { // Keys with spaces fail the shared `is_valid_guc_name` check used @@ -1372,6 +1413,31 @@ mod tests { assert!(cache.peek_startup_parameters("ghost").is_none()); } + #[tokio::test] + async fn peek_startup_parameters_returns_none_for_expired_entry() { + // HIGH #7 regression guard: a positive cache entry that has lived + // past `cache_ttl` must not pin a stale per-user startup parameter + // onto a backend the replenishment loop spawns later. Mirrors + // `test_cache_ttl_expiration` but exercises the peek path the + // backend-spawn hot path uses. + let fetcher = Arc::new(MockFetcher::new()); + fetcher.add_user_with_params("alice", "md5abc123", &[("work_mem", "64MB")]); + let mut config = test_config(); + config.cache_ttl = Duration::from_millis(50); + + let cache = make_cache(fetcher, &config); + cache.get_or_fetch("alice").await.unwrap().unwrap(); + // Sanity: peek sees the fresh entry. + assert!(cache.peek_startup_parameters("alice").is_some()); + + tokio::time::sleep(std::time::Duration::from_millis(80)).await; + + assert!( + cache.peek_startup_parameters("alice").is_none(), + "peek must return None once cache_ttl has elapsed for the entry" + ); + } + #[tokio::test] async fn peek_startup_parameters_positive_entry_returns_map() { let fetcher = Arc::new(MockFetcher::new()); diff --git a/src/config/general.rs b/src/config/general.rs index ca442d80a..d5406b1a6 100644 --- a/src/config/general.rs +++ b/src/config/general.rs @@ -269,23 +269,15 @@ pub struct General { #[serde(default, skip_serializing)] pub pg_hba: Option, - /// Consecutive backend startup rejections for the same parameter before - /// pg_doorman quarantines it for this pool. See - /// `startup_parameter_quarantine_ttl`. Default `3`. - #[serde(default = "General::default_startup_parameter_quarantine_threshold")] - pub startup_parameter_quarantine_threshold: u32, - - /// TTL (milliseconds) for a quarantined parameter; on expiry pg_doorman - /// retries including it in the StartupMessage. Default `300000` (5 min). - #[serde(default = "General::default_startup_parameter_quarantine_ttl")] - pub startup_parameter_quarantine_ttl: u64, - /// Operator-supplied PostgreSQL configuration parameters added to /// backend `StartupMessage`s. The general map is the baseline; /// pool-level settings override per key, and passthrough `auth_query` /// rows can override per user. Config load validates reserved keys, /// GUC names, null bytes, and this level's size; the merged cascade is - /// checked again before each backend startup. + /// checked again before each backend startup. If PostgreSQL rejects an + /// operator-supplied parameter at backend startup, the client receives + /// the PG error unchanged — pg_doorman never substitutes its own + /// retry, fallback, or per-key quarantine for the backend's verdict. #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] pub startup_parameters: std::collections::BTreeMap, } @@ -478,14 +470,6 @@ impl General { "/tmp/pg_doorman.pid".to_string() } - pub fn default_startup_parameter_quarantine_threshold() -> u32 { - 3 - } - - pub fn default_startup_parameter_quarantine_ttl() -> u64 { - 300_000 - } - /// Test-only builder that produces a `General` with the two /// prepared-cache knobs explicitly set and everything else at /// defaults. Lets tests outside this module exercise resolution @@ -613,9 +597,6 @@ impl Default for General { Self::default_query_interner_anon_idle_ttl_seconds(), hba: Self::default_hba(), pg_hba: None, - startup_parameter_quarantine_threshold: - Self::default_startup_parameter_quarantine_threshold(), - startup_parameter_quarantine_ttl: Self::default_startup_parameter_quarantine_ttl(), startup_parameters: std::collections::BTreeMap::new(), daemon_pid_file: Self::default_daemon_pid_file(), syslog_prog_name: None, @@ -796,11 +777,4 @@ client_prepared_statements_cache_size = 2048 Some(2048), ); } - - #[test] - fn startup_parameter_quarantine_defaults_are_sane() { - let g = General::default(); - assert_eq!(g.startup_parameter_quarantine_threshold, 3); - assert_eq!(g.startup_parameter_quarantine_ttl, 300_000); - } } diff --git a/src/pool/inner.rs b/src/pool/inner.rs index f919fee79..fb5f78799 100644 --- a/src/pool/inner.rs +++ b/src/pool/inner.rs @@ -1545,13 +1545,6 @@ impl Pool { self.inner.server_pool.is_paused() } - /// Operator-supplied startup_parameters currently parked in the - /// per-pool quarantine. Delegates to the `ServerPool` snapshot so - /// SHOW POOLS can render the same set the Prometheus gauge exposes. - pub fn quarantined_startup_parameters(&self) -> Vec { - self.inner.server_pool.quarantined_startup_parameters() - } - /// Effective merged startup_parameters cascade keyed by parameter, with /// the layer that contributed each winning value. Delegates to /// `ServerPool` so admin `SHOW STARTUP_PARAMETERS` and the @@ -1565,23 +1558,6 @@ impl Pool { .effective_startup_parameters_with_sources() } - /// Forward a live update of quarantine threshold/TTL to the shared - /// `QuarantineState`. The pool hash does not include these - /// general-level knobs, so reload calls this on every pool it kept. - pub fn update_quarantine_knobs(&self, threshold: u32, ttl: std::time::Duration) { - self.inner - .server_pool - .update_quarantine_knobs(threshold, ttl); - } - - /// Drop expired quarantine bookkeeping and clear the matching gauges so - /// that an idle pool does not strand the Prometheus - /// `pg_doorman_backend_startup_parameter_quarantined` series at 1 long - /// after TTL elapsed. - pub fn reconcile_quarantine_gauges(&self) { - self.inner.server_pool.reconcile_quarantine_gauges(); - } - /// Bumps reconnect epoch and drains all idle connections. /// Returns the new epoch value. pub fn reconnect(&self) -> u32 { diff --git a/src/pool/mod.rs b/src/pool/mod.rs index d8b5f39a7..96e69f03e 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -5,7 +5,7 @@ use once_cell::sync::{Lazy, OnceCell}; use parking_lot::{Mutex, RwLock}; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use std::sync::Arc; use crate::config::{ @@ -68,6 +68,13 @@ pub type PoolMap = HashMap; /// This is atomic and safe and read-optimized. /// The pool is recreated dynamically when the config is reloaded. pub static POOLS: Lazy> = Lazy::new(|| ArcSwap::from_pointee(HashMap::default())); + +/// Hash of the previous reload's `general.startup_parameters` map. Used by +/// `ConnectionPool::from_config` to recognize when a SIGHUP changed the +/// general-level baseline so dynamic auth_query pools can be drained — the +/// per-pool reuse hash already folds in the baseline, but dynamic pools are +/// carried over by identifier rather than rebuilt from the same path. +static PREVIOUS_GENERAL_STARTUP_HASH: AtomicU64 = AtomicU64::new(0); pub static CANCELED_PIDS: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(HashSet::new()))); @@ -319,8 +326,35 @@ impl ConnectionPool { ); } + // Hashing each pool's effective config against (Pool, general + // startup_parameters baseline) folds general-level GUC changes into + // the same reuse decision pg_doorman already uses for pool-level + // changes. Without this, a SIGHUP that only edits + // `general.startup_parameters` would leave every idle backend + // pinned to the previous `reset_val` until the connection rotates + // through `lifetime_ms`, so clients would see mixed defaults from + // the same pool depending on which backend they got. + let general_startup_hash = { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + config.general.startup_parameters.hash(&mut hasher); + hasher.finish() + }; + let previous_general_startup_hash = + PREVIOUS_GENERAL_STARTUP_HASH.swap(general_startup_hash, Ordering::Relaxed); + // The static defaults to `0`, which collides with the empty-map + // hash on a fresh process; treat that special case as "no prior + // value" so the first reload never falsely claims a change. + let general_startup_parameters_changed = previous_general_startup_hash != 0 + && previous_general_startup_hash != general_startup_hash; for (pool_name, pool_config) in &config.pools { - let new_pool_hash_value = pool_config.hash_value(); + let new_pool_hash_value = { + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + hasher.write_u64(pool_config.hash_value()); + hasher.write_u64(general_startup_hash); + hasher.finish() + }; let server_tls_config = build_server_tls_for_pool(pool_config, &config.general)?; // There is one pool per database/user pair. @@ -335,18 +369,6 @@ impl ConnectionPool { && pool.address.server_tls.as_ref() == server_tls_config.as_ref() { info!("[{}@{}] config unchanged", user.username, pool_name); - // Quarantine threshold and TTL live in `general` and - // therefore do not influence the pool hash. A SIGHUP - // that only retunes those knobs would otherwise leave - // every kept pool running with stale values, so push - // the new numbers into the shared QuarantineState - // here while the pool is still being reused. - pool.database.update_quarantine_knobs( - config.general.startup_parameter_quarantine_threshold, - std::time::Duration::from_millis( - config.general.startup_parameter_quarantine_ttl, - ), - ); new_pools.insert(identifier.clone(), pool.clone()); continue; } @@ -754,6 +776,21 @@ impl ConnectionPool { } } + // 2b. general.startup_parameters changed: drain every dynamic pool + // so the next auth_query lookup builds fresh backends with the + // new baseline. Static pools are already handled by the pool + // reuse hash above, which folds in `general_startup_hash`. + if general_startup_parameters_changed { + info!( + "general.startup_parameters changed on reload — collecting all dynamic pools for recycle" + ); + for id in DYNAMIC_POOLS.load().iter() { + if !pools_to_remove.contains(id) { + pools_to_remove.push(id.clone()); + } + } + } + // 3. Carry over surviving dynamic pools let old_pools = POOLS.load(); for id in DYNAMIC_POOLS.load().iter() { diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 4eb822187..e3e5f35fa 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -83,12 +83,6 @@ pub struct ServerPool { /// Notify to wake up clients blocked on PAUSE. resume_notify: Notify, - - /// Shared quarantine state for operator-supplied startup_parameters. - /// One instance per pool so the rejection counter accumulates across - /// every backend spawn this pool issues; per-call construction would - /// reset the counter on each spawn and never trip the threshold. - startup_parameter_quarantine: Arc, } impl std::fmt::Debug for ServerPool { @@ -111,10 +105,6 @@ impl std::fmt::Debug for ServerPool { "connection_counter", &self.connection_counter.load(Ordering::Relaxed), ) - .field( - "startup_parameter_quarantine", - &self.startup_parameter_quarantine, - ) .finish() } } @@ -139,17 +129,6 @@ impl ServerPool { session_mode: bool, fallback_state: Option>, ) -> ServerPool { - // The quarantine knobs are global (general.*) so we read them at - // construction time; subsequent RELOADs that touch only the knobs - // do not redirect the existing pool's quarantine because RELOAD - // builds a new ServerPool whenever a pool's hash changes. - let cfg = crate::config::config_arc(); - let startup_parameter_quarantine = - Arc::new(crate::server::quarantine::QuarantineState::new( - cfg.general.startup_parameter_quarantine_threshold, - Duration::from_millis(cfg.general.startup_parameter_quarantine_ttl), - )); - ServerPool { address, user: user.clone(), @@ -170,7 +149,6 @@ impl ServerPool { resume_notify: Notify::new(), session_mode, fallback_state, - startup_parameter_quarantine, } } @@ -261,7 +239,6 @@ impl ServerPool { self.application_name.clone(), self.session_mode, startup_parameters.clone(), - self.startup_parameter_quarantine.clone(), ), ) .await; @@ -271,7 +248,11 @@ impl ServerPool { // StartupMessage. The socket is dead after FATAL, so retry needs a fresh // TCP connection. We retry on any startup failure (matching libpq), but // skip retry on transport-level errors (ConnectError, ServerUnavailableError) - // since TLS cannot help when the server was never reached. + // since TLS cannot help when the server was never reached. Startup + // parameter rejections (PG ErrorResponse on a key we sent) are also + // not TLS-fixable, but they surface as `ServerStartupError` with the + // PG sqlstate intact — the client receives the same message PG would + // have produced, so the retry is a no-op rather than a hazard. // // Reference: PostgreSQL docs, "SSL Support" → sslmode parameter. let should_tls_retry = match &result { @@ -318,7 +299,6 @@ impl ServerPool { self.application_name.clone(), self.session_mode, startup_parameters, - self.startup_parameter_quarantine.clone(), ), ) .await; @@ -361,37 +341,6 @@ impl ServerPool { &self.address } - /// Snapshot of operator-supplied startup_parameter names that this - /// pool currently strips from `StartupMessage`. Used by SHOW POOLS - /// (admin console) so operators can see which knobs a pool is - /// parking without correlating logs to Prometheus. - pub fn quarantined_startup_parameters(&self) -> Vec { - self.startup_parameter_quarantine.snapshot_quarantined() - } - - /// Live-update the quarantine threshold and TTL so a SIGHUP that only - /// touches `general.startup_parameter_quarantine_*` takes effect on the - /// reused pool. The pool hash does not include these general-level - /// knobs, so without this hook a reload would silently leave the pool - /// running with the previous values. - pub fn update_quarantine_knobs(&self, threshold: u32, ttl: std::time::Duration) { - self.startup_parameter_quarantine - .update_knobs(threshold, ttl); - } - - /// Drop bookkeeping for quarantined keys whose TTL has elapsed and clear - /// the matching Prometheus gauge series. Called from the SHOW POOLS / - /// metrics-collection path so an idle pool with no backend churn does - /// not leave a stale `pg_doorman_backend_startup_parameter_quarantined` - /// at 1 long after the underlying TTL expired. - pub fn reconcile_quarantine_gauges(&self) { - for key in self.startup_parameter_quarantine.reconcile_expired() { - crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED - .with_label_values(&[&self.address.pool_name, &key]) - .set(0); - } - } - /// Inspect the operator-supplied cascade with the source layer kept for /// each key. Used by the admin `SHOW STARTUP_PARAMETERS` command and the /// `/api/pools` JSON so operators can answer "where did `work_mem=64MB` @@ -869,7 +818,6 @@ impl ServerPool { self.application_name.clone(), self.session_mode, startup_parameters.clone(), - self.startup_parameter_quarantine.clone(), ), ) .await; @@ -919,7 +867,6 @@ impl ServerPool { self.application_name.clone(), self.session_mode, startup_parameters, - self.startup_parameter_quarantine.clone(), ), ) .await; diff --git a/src/server/mod.rs b/src/server/mod.rs index e77fa48fc..16e937948 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -5,7 +5,6 @@ pub(crate) mod cleanup; pub(crate) mod parameters; pub(crate) mod prepared_statements; pub(crate) mod protocol_io; -pub mod quarantine; pub(crate) mod startup_cancel; pub(crate) mod startup_error; pub(crate) mod stream; diff --git a/src/server/quarantine.rs b/src/server/quarantine.rs deleted file mode 100644 index 7d032f128..000000000 --- a/src/server/quarantine.rs +++ /dev/null @@ -1,334 +0,0 @@ -//! Per-pool quarantine for operator-supplied startup parameters that PG keeps -//! rejecting at backend startup. -//! -//! Lifecycle: -//! 1. Backend `Server::startup` issues StartupMessage with N parameters. -//! 2. PG ErrorResponse arrives; parsed parameter name is reported via -//! [`QuarantineState::record_rejection`]. -//! 3. After N consecutive rejections of the same parameter -//! (`startup_parameter_quarantine_threshold`), the key is quarantined for -//! TTL ms. -//! 4. [`QuarantineState::filter_active_keys`] is called on every subsequent -//! backend spawn and skips quarantined keys. -//! 5. TTL only releases keys (never a "success": we skipped them, so success -//! is meaningless evidence). - -use std::collections::HashMap; -use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; -use std::sync::Mutex; -use std::time::{Duration, Instant}; - -#[derive(Debug, Clone)] -pub struct QuarantineEntry { - pub reject_count: u32, - pub quarantined_until: Option, - pub last_sqlstate: String, -} - -#[derive(Debug)] -pub struct QuarantineState { - // Hot-reloadable: a SIGHUP that changes - // general.startup_parameter_quarantine_{threshold,ttl} takes effect on - // the next call without recreating the pool. Reads use Relaxed since the - // values are advisory observability knobs, not safety invariants. - threshold: AtomicU32, - ttl_ms: AtomicU64, - entries: Mutex>, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum RecordOutcome { - /// Counter advanced but threshold not yet reached. - Counting { reject_count: u32 }, - /// This call moved the parameter into quarantine. - JustQuarantined, - /// Already quarantined; this is a refresher with a fresh deadline. - AlreadyQuarantined, -} - -impl QuarantineState { - pub fn new(threshold: u32, ttl: Duration) -> Self { - Self { - threshold: AtomicU32::new(threshold.max(1)), - ttl_ms: AtomicU64::new(ttl.as_millis() as u64), - entries: Mutex::new(HashMap::new()), - } - } - - /// Update the threshold/TTL knobs without recreating the pool. Called - /// from the SIGHUP reload path when only the general-level knobs change - /// and pool hash would not otherwise force a rebuild. - pub fn update_knobs(&self, threshold: u32, ttl: Duration) { - self.threshold.store(threshold.max(1), Ordering::Relaxed); - self.ttl_ms.store(ttl.as_millis() as u64, Ordering::Relaxed); - } - - fn threshold(&self) -> u32 { - self.threshold.load(Ordering::Relaxed) - } - - fn ttl(&self) -> Duration { - Duration::from_millis(self.ttl_ms.load(Ordering::Relaxed)) - } - - /// Record a backend-startup rejection for `key`. Returns the resulting - /// outcome for caller-side logging and metrics. - pub fn record_rejection(&self, key: &str, sqlstate: &str) -> RecordOutcome { - let now = Instant::now(); - let ttl = self.ttl(); - let threshold = self.threshold(); - let mut entries = self.entries.lock().expect("quarantine mutex"); - let entry = entries - .entry(key.to_owned()) - .or_insert_with(|| QuarantineEntry { - reject_count: 0, - quarantined_until: None, - last_sqlstate: sqlstate.to_owned(), - }); - entry.last_sqlstate = sqlstate.to_owned(); - if entry.quarantined_until.map(|d| d > now).unwrap_or(false) { - entry.quarantined_until = Some(now + ttl); - return RecordOutcome::AlreadyQuarantined; - } - entry.reject_count = entry.reject_count.saturating_add(1); - if entry.reject_count >= threshold { - entry.quarantined_until = Some(now + ttl); - return RecordOutcome::JustQuarantined; - } - RecordOutcome::Counting { - reject_count: entry.reject_count, - } - } - - /// Strip currently-quarantined keys from the operator-supplied map and - /// drop the bookkeeping for entries whose TTL has expired (so the next - /// rejection starts counting afresh). - /// - /// Returns the list of keys whose quarantine just expired in this call; - /// callers should use it to clear corresponding Prometheus gauges. - pub fn filter_active_keys( - &self, - params: &mut std::collections::BTreeMap, - ) -> Vec { - let now = Instant::now(); - let mut entries = self.entries.lock().expect("quarantine mutex"); - let mut released: Vec = Vec::new(); - let mut to_drop: Vec = Vec::new(); - for (key, entry) in entries.iter_mut() { - match entry.quarantined_until { - Some(deadline) if deadline > now => { - params.remove(key); - } - Some(_) => { - entry.quarantined_until = None; - entry.reject_count = 0; - to_drop.push(key.clone()); - released.push(key.clone()); - } - None => {} - } - } - for k in &to_drop { - entries.remove(k); - } - released - } - - /// Return currently-quarantined parameter names (for SHOW POOLS / metrics). - pub fn snapshot_quarantined(&self) -> Vec { - let now = Instant::now(); - let entries = self.entries.lock().expect("quarantine mutex"); - entries - .iter() - .filter_map(|(k, e)| match e.quarantined_until { - Some(d) if d > now => Some(k.clone()), - _ => None, - }) - .collect() - } - - /// Drop bookkeeping for entries whose quarantine TTL has expired and - /// return their names. Used by the SHOW POOLS / metrics-collection path - /// so the Prometheus quarantine gauge clears for an idle pool that no - /// longer sees backend spawns; without this hook the gauge would stay - /// stuck at 1 until the next backend creation triggered - /// `filter_active_keys`. - pub fn reconcile_expired(&self) -> Vec { - let now = Instant::now(); - let mut entries = self.entries.lock().expect("quarantine mutex"); - let mut released: Vec = Vec::new(); - let mut to_drop: Vec = Vec::new(); - for (key, entry) in entries.iter_mut() { - if let Some(deadline) = entry.quarantined_until { - if deadline <= now { - entry.quarantined_until = None; - entry.reject_count = 0; - to_drop.push(key.clone()); - released.push(key.clone()); - } - } - } - for k in &to_drop { - entries.remove(k); - } - released - } - - /// Reset the partial-rejection counters for keys that pg_doorman just - /// successfully sent in a backend StartupMessage (the backend reached - /// `ReadyForQuery`). This keeps the threshold model honest: only N - /// *consecutive* rejections of the same key arm quarantine, not N - /// rejections spread across a sea of healthy startups. - /// - /// Keys currently quarantined (`quarantined_until = Some(_)`) are not - /// touched; their TTL is the only release path. By definition such keys - /// were not in the sent map at all, so a successful startup says nothing - /// about whether the underlying problem with them was fixed. - pub fn record_success(&self, sent_keys: &std::collections::BTreeMap) { - if sent_keys.is_empty() { - return; - } - let mut entries = self.entries.lock().expect("quarantine mutex"); - for k in sent_keys.keys() { - if let Some(entry) = entries.get_mut(k) { - if entry.quarantined_until.is_none() { - entry.reject_count = 0; - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::BTreeMap; - - #[test] - fn first_rejections_count_up() { - let q = QuarantineState::new(3, Duration::from_secs(60)); - assert_eq!( - q.record_rejection("foo", "42704"), - RecordOutcome::Counting { reject_count: 1 } - ); - assert_eq!( - q.record_rejection("foo", "42704"), - RecordOutcome::Counting { reject_count: 2 } - ); - assert_eq!( - q.record_rejection("foo", "42704"), - RecordOutcome::JustQuarantined - ); - } - - #[test] - fn extra_rejection_after_quarantine_is_refresher() { - let q = QuarantineState::new(2, Duration::from_secs(60)); - let _ = q.record_rejection("foo", "42704"); - let _ = q.record_rejection("foo", "42704"); - assert_eq!( - q.record_rejection("foo", "42704"), - RecordOutcome::AlreadyQuarantined - ); - } - - #[test] - fn filter_strips_active_quarantines() { - let q = QuarantineState::new(1, Duration::from_secs(60)); - let _ = q.record_rejection("bad", "42704"); - let mut params: BTreeMap = [ - ("bad".to_string(), "x".to_string()), - ("ok".to_string(), "y".to_string()), - ] - .into_iter() - .collect(); - let released = q.filter_active_keys(&mut params); - assert!(!params.contains_key("bad")); - assert!(params.contains_key("ok")); - assert!(released.is_empty(), "active quarantine should not release"); - } - - #[test] - fn expired_quarantine_is_released_and_counter_resets() { - let q = QuarantineState::new(1, Duration::from_millis(10)); - let _ = q.record_rejection("bad", "42704"); - std::thread::sleep(Duration::from_millis(25)); - let mut params: BTreeMap = - [("bad".to_string(), "x".to_string())].into_iter().collect(); - let released = q.filter_active_keys(&mut params); - assert!( - params.contains_key("bad"), - "expired quarantine should not strip the key" - ); - assert_eq!(released, vec!["bad".to_string()]); - // counter reset; next rejection re-starts counting and (with threshold=1) - // immediately quarantines. - assert_eq!( - q.record_rejection("bad", "42704"), - RecordOutcome::JustQuarantined - ); - } - - #[test] - fn snapshot_lists_only_active() { - let q = QuarantineState::new(1, Duration::from_secs(60)); - let _ = q.record_rejection("a", "42704"); - let _ = q.record_rejection("b", "22023"); - let mut s = q.snapshot_quarantined(); - s.sort(); - assert_eq!(s, vec!["a".to_string(), "b".to_string()]); - } - - #[test] - fn threshold_zero_is_clamped_to_one() { - let q = QuarantineState::new(0, Duration::from_secs(60)); - assert_eq!( - q.record_rejection("foo", "42704"), - RecordOutcome::JustQuarantined - ); - } - - #[test] - fn record_success_resets_counter_for_non_quarantined_key() { - let q = QuarantineState::new(3, Duration::from_secs(60)); - // counter = 1, not yet quarantined. - let _ = q.record_rejection("a", "42704"); - let sent: BTreeMap = - [("a".to_string(), "x".to_string())].into_iter().collect(); - q.record_success(&sent); - // Next rejection counts from 1 again, not 2 (counter was reset). - assert_eq!( - q.record_rejection("a", "42704"), - RecordOutcome::Counting { reject_count: 1 } - ); - } - - #[test] - fn record_success_preserves_quarantine_state() { - let q = QuarantineState::new(1, Duration::from_secs(60)); - // threshold=1 -> already quarantined. - let _ = q.record_rejection("a", "42704"); - let sent: BTreeMap = - [("a".to_string(), "x".to_string())].into_iter().collect(); - // Success on the same key (e.g., somebody else removed the bad value). - // record_success must not touch the quarantine deadline because the - // key was never actually sent in this successful startup. - q.record_success(&sent); - assert_eq!(q.snapshot_quarantined(), vec!["a".to_string()]); - } - - #[test] - fn record_success_ignores_unknown_keys() { - let q = QuarantineState::new(3, Duration::from_secs(60)); - let _ = q.record_rejection("known", "42704"); - let sent: BTreeMap = [("never_seen".to_string(), "x".to_string())] - .into_iter() - .collect(); - q.record_success(&sent); - // "known" counter untouched: next rejection counts to 2. - assert_eq!( - q.record_rejection("known", "42704"), - RecordOutcome::Counting { reject_count: 2 } - ); - } -} diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index aadfe535b..fa500be07 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -1,7 +1,7 @@ // Implementation of the PostgreSQL server (database) protocol. // Standard library imports -use std::collections::{HashMap, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; use std::num::NonZeroUsize; use std::string::ToString; use std::sync::Arc; @@ -163,6 +163,12 @@ pub struct Server { /// Per-connection lifetime override (ms). Set on fallback connections so /// they expire before the local backend recovers. pub(crate) override_lifetime_ms: Option, + + /// Names of GUCs that pg_doorman injected through `startup_parameters` + /// for this backend. They become `pg_settings.reset_val` for the + /// session, so `sync_parameters` must not push a client-side value over + /// them on checkout — the operator decision wins over the client. + operator_managed_startup_keys: HashSet, } impl std::fmt::Display for Server { @@ -691,7 +697,16 @@ impl Server { } pub async fn sync_parameters(&mut self, parameters: &ServerParameters) -> Result<(), Error> { - let parameter_diff = self.server_parameters.compare_params(parameters); + let mut parameter_diff = self.server_parameters.compare_params(parameters); + + // Keys the operator injected through `startup_parameters` are + // authoritative for the session: they are pg_settings.reset_val on + // the backend and the operator chose them on purpose. The client + // may still issue an explicit SET later, but the per-checkout sync + // from `client.server_parameters` must not silently overwrite an + // operator-supplied default with a client-side value (e.g. an + // application_name baked into a connection string). + parameter_diff.retain(|k, _| !self.operator_managed_startup_keys.contains(k)); if parameter_diff.is_empty() { return Ok(()); @@ -742,12 +757,12 @@ impl Server { /// Perform the authentication and return the server in a ready for query state. /// /// `startup_parameters` is the cascade-resolved operator-supplied map - /// (general → pool → auth_query, more specific wins). Keys currently held - /// in `quarantine` are stripped before the StartupMessage is sent; any - /// PG ErrorResponse with a startup-parameter SQLSTATE - /// (`22023` / `42704` / `42501`) is fed back into the quarantine so a - /// persistently-failing key is dropped from subsequent backend spawns - /// for the configured TTL. + /// (general → pool → auth_query, more specific wins). It goes onto the + /// wire as part of `StartupMessage` exactly as the operator configured + /// it. If PG rejects any of those parameters, the resulting + /// `ErrorResponse` is surfaced to the client unchanged — pg_doorman + /// behaves like the PostgreSQL backend itself rather than masking the + /// failure with retries or per-key quarantine. #[allow(clippy::too_many_arguments)] pub async fn startup( address: &Address, @@ -761,7 +776,6 @@ impl Server { application_name: String, session_mode: bool, startup_parameters: std::collections::BTreeMap, - quarantine: std::sync::Arc, ) -> Result { let config = get_config(); @@ -807,18 +821,7 @@ impl Server { let auth_started = Instant::now(); let mut startup_started: Option = None; - // Strip currently-quarantined keys from the operator-supplied set - // right before we serialize the StartupMessage. Any keys whose TTL - // expired during this call are reported back so we can flip their - // Prometheus gauge series back to 0 (the alert rule fires on the - // gauge, not on log scraping). - let mut startup_parameters_sent = startup_parameters; - let released_keys = quarantine.filter_active_keys(&mut startup_parameters_sent); - for k in &released_keys { - crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED - .with_label_values(&[&address.pool_name, k]) - .set(0); - } + let startup_parameters_sent = startup_parameters; startup( &mut stream, @@ -926,14 +929,14 @@ impl Server { } } - // ErrorResponse. Read the message body and parse it. When the - // SQLSTATE belongs to the startup-parameter family and the - // operator-supplied map was non-empty, feed the failing - // parameter name into the per-pool quarantine. Finally, - // preserve the pre-quarantine error classification so the - // Patroni-assisted fallback path still treats transient PG - // unavailability (SQLSTATE class `57P`) as a route-elsewhere - // signal rather than a startup misconfiguration. + // ErrorResponse. Read the message body and parse it. PG's + // verdict is the operator's verdict — we surface the + // sqlstate and message to the client unchanged. The one + // exception is the Patroni-assisted fallback path: SQLSTATE + // class `57P*` (server unavailable) needs its dedicated + // `ServerUnavailableError` classification so the fallback + // discovery can route around the failed node instead of + // returning a startup error to the client. 'E' => { let mut bytes = read_message_data(&mut stream, code as u8, len).await?; let _ = bytes.get_u8(); @@ -945,13 +948,6 @@ impl Server { )); }; - // SQLSTATE class 57P (`57P*`) signals transient PG - // unavailability — the Patroni-assisted fallback path - // needs the original classification to route around it, - // and a transient outage on the local backend is not a - // startup-parameter misconfiguration. Surface it first - // so the quarantine logic below cannot misclassify a - // node-down event as a bad GUC. if msg.code.starts_with("57P") { return Err(Error::ServerUnavailableError( msg.message, @@ -959,61 +955,32 @@ impl Server { )); } - // PG-side `ErrorResponse` on every other SQLSTATE. The - // "matched a sent key" heuristic is stronger than an - // SQLSTATE allowlist: PG covers startup-time GUC failures - // under at least `22023` (invalid_parameter_value), - // `42704` (undefined_object), `42501` (insufficient_ - // privilege) and `55P02` (cant_change_runtime_param), - // and the list drifts across versions and extensions. - // If `extract_parameter_name` returned a key pg_doorman - // actually sent, treat it as a startup-parameter failure - // regardless of the code; otherwise leave the error - // alone — `ALTER ROLE … SET` and similar paths fail on - // parameters pg_doorman never touched and must not - // quarantine an operator key on our side. + // Warn the operator when PG rejected a parameter we + // actually sent — this is the signal that + // general/pool/auth_query has a typo or a value the + // PG instance does not accept. We do not change the + // error path; the client still receives the PG + // sqlstate so the operator sees the same failure + // their application would see talking to PG directly. if !startup_parameters_sent.is_empty() { if let Some(param_name) = crate::server::startup_error::extract_parameter_name(&msg.message) { if startup_parameters_sent.contains_key(¶m_name) { - let outcome = quarantine.record_rejection(¶m_name, &msg.code); warn!( - "[{}@{}] backend startup rejected: parameter=\"{}\" \ - sqlstate={} message=\"{}\" outcome={:?}", + "[{}@{}] PG rejected operator-supplied startup \ + parameter=\"{}\" sqlstate={} message=\"{}\"; the \ + error is being forwarded to the client. Fix the \ + parameter in general/pool/auth_query.", address.username, address.pool_name, param_name, msg.code, msg.message, - outcome, ); crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL - .with_label_values(&[ - &address.pool_name, - ¶m_name, - &msg.code, - ]) + .with_label_values(&[&address.pool_name, &msg.code]) .inc(); - if matches!( - outcome, - crate::server::quarantine::RecordOutcome::JustQuarantined - ) { - crate::web::metrics::BACKEND_STARTUP_PARAMETER_QUARANTINED - .with_label_values(&[&address.pool_name, ¶m_name]) - .set(1); - } - } else { - info!( - "[{}@{}] backend startup rejected by an unrelated PG \ - startup parameter \"{}\" (pg_doorman did not send this \ - key); sqlstate={} message=\"{}\"", - address.username, - address.pool_name, - param_name, - msg.code, - msg.message, - ); } } } @@ -1087,11 +1054,8 @@ impl Server { phase_started.elapsed().as_secs_f64(), ); - // PG accepted every operator-supplied parameter we sent. - // Reset partial-rejection counters for those keys so that - // the threshold model stays "N consecutive rejections", - // not "N rejections ever". - quarantine.record_success(&startup_parameters_sent); + let operator_managed_startup_keys: HashSet = + startup_parameters_sent.keys().cloned().collect(); let server = Server { address: address.to_owned(), @@ -1131,6 +1095,7 @@ impl Server { pending_large_message: None, close_reason: None, override_lifetime_ms: None, + operator_managed_startup_keys, }; server.stats.update_process_id(process_id); server.stats.set_tls(connected_with_tls); diff --git a/src/stats/pool.rs b/src/stats/pool.rs index 8bb2d3968..ed31ffd42 100644 --- a/src/stats/pool.rs +++ b/src/stats/pool.rs @@ -203,15 +203,6 @@ pub struct PoolStats { /// Configured maximum pool size (from user config or default) pub pool_size: u32, - - /// Names of operator-supplied startup parameters that are currently - /// parked in the per-pool quarantine. Snapshot taken at pool-stats - /// build time; rendered as a comma-separated text column in SHOW - /// POOLS so an operator at `psql` can see at a glance which knobs a - /// pool is dropping from StartupMessage without leaving the prompt. - /// Empty for pools whose `startup_parameters` map is empty or where - /// no parameter has crossed the quarantine threshold. - pub quarantined_params: Vec, } #[derive(Debug, Clone)] @@ -307,7 +298,6 @@ impl PoolStats { fallback_active: false, source_generation: 0, pool_size: 0, - quarantined_params: Vec::new(), } } @@ -381,7 +371,6 @@ impl PoolStats { ("paused", DataType::Text), ("fallback_active", DataType::Text), ("oldest_active_age_ms", DataType::Numeric), - ("quarantined_params", DataType::Text), ] } @@ -456,7 +445,6 @@ impl PoolStats { Cow::Borrowed(if self.paused { "1" } else { "0" }), Cow::Borrowed(if self.fallback_active { "1" } else { "0" }), Cow::Owned(self.oldest_active_age_ms.to_string()), - Cow::Owned(self.quarantined_params.join(",")), ] } @@ -630,17 +618,6 @@ impl PoolStats { current.prepared_statements_bytes = cache.memory_usage() as u64; } - // Reconcile expired quarantine bookkeeping and clear the - // corresponding Prometheus gauges before snapshotting. Pools - // that never see another backend spawn would otherwise leave - // stale series at 1 indefinitely, since the regular release - // path only fires inside `Server::startup`. - pool.database.reconcile_quarantine_gauges(); - // Snapshot the currently-parked startup_parameters so SHOW - // POOLS reflects the same set of parameters the Prometheus - // gauge already exposes through the metrics endpoint. - current.quarantined_params = pool.database.quarantined_startup_parameters(); - // Load statistics for SHOW STATS command current.total_xact_count = address.total.xact_count.load(Ordering::Relaxed); current.total_query_count = address.total.query_count.load(Ordering::Relaxed); @@ -851,21 +828,6 @@ mod tests { assert_eq!(row[14].as_ref(), "5", "column 14 should be avg_errors"); } - /// SHOW POOLS header advertises a `quarantined_params` column so the - /// admin console reflects the same state that - /// `pg_doorman_backend_startup_parameter_quarantined` exposes through - /// the metrics endpoint. A missing column there means operators have - /// to leave `psql` to triage a quarantined parameter. - #[test] - fn show_pools_header_advertises_quarantined_params_column() { - let header = PoolStats::generate_show_pools_header(); - let names: Vec<&str> = header.iter().map(|(n, _)| *n).collect(); - assert!( - names.contains(&"quarantined_params"), - "SHOW POOLS header missing quarantined_params column: {names:?}" - ); - } - /// SHOW POOLS row width must match the header width on every config, /// otherwise the admin console renders misaligned columns. The risk /// shows up when only one of header or row is touched when a new @@ -878,23 +840,17 @@ mod tests { p90: 0, p50: 0, }; - let mut stats = PoolStats::new_with_percentiles( + let stats = PoolStats::new_with_percentiles( PoolIdentifier::new("shop", "alice"), PoolMode::Transaction, percentile.clone(), percentile.clone(), percentile, ); - stats.quarantined_params = vec!["search_path".to_string(), "work_mem".to_string()]; let header = PoolStats::generate_show_pools_header(); let row = stats.generate_show_pools_row(); assert_eq!(header.len(), row.len(), "header/row width mismatch"); - let idx = header - .iter() - .position(|(n, _)| *n == "quarantined_params") - .expect("quarantined_params column registered"); - assert_eq!(row[idx].as_ref(), "search_path,work_mem"); } /// Both entry points must agree on shape when fed the same global diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index 45e30f5ab..7c3b55679 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -459,58 +459,31 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// Counter for every backend startup attempt that PostgreSQL rejected /// because of an operator-supplied `startup_parameters` entry. Tracked -/// per pool/parameter so dashboards can spot a single bad knob in a -/// large fleet, and split by `sqlstate` (`22023` invalid_value, `42704` -/// undefined_object, `42501` insufficient_privilege) so the alerting -/// rule can distinguish a typo from a permission denial. The failing -/// username is on the corresponding warn log line; it is intentionally -/// left out of the label set so dynamic auth_query pools cannot blow up -/// the series count when many roles share a broken config. Increments -/// once per failed StartupMessage, regardless of whether this rejection -/// happens to be the one that crosses the quarantine threshold. +/// per pool and split by `sqlstate` so dashboards can tell whether a +/// pool is hitting `22023` (invalid value), `42704` (undefined object), +/// `42501` (insufficient privilege), `55P02` (cant change runtime +/// parameter), or a fresh PG code under the startup-parameter family. +/// The failing parameter name and username are on the corresponding warn +/// log line; they are intentionally left out of the label set so a +/// dynamic auth_query pool cannot blow up the series count when many +/// roles share a broken config. pub(crate) static BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL: Lazy = Lazy::new(|| { let counter = IntCounterVec::new( Opts::new( "pg_doorman_backend_startup_parameter_errors_total", "Cumulative count of backend startup attempts rejected by \ PostgreSQL because of an operator-supplied startup_parameters \ - entry. Labels: pool, parameter, sqlstate (22023 invalid_value \ - / 42704 undefined_object / 42501 insufficient_privilege). \ - Counts every rejection, not just the one that crossed the \ - quarantine threshold. The failing username is available in the \ - corresponding warn log line.", + entry. Labels: pool, sqlstate (PostgreSQL SQLSTATE on the \ + returned ErrorResponse). The failing parameter name and \ + username are available in the corresponding warn log line.", ), - &["pool", "parameter", "sqlstate"], + &["pool", "sqlstate"], ) .unwrap(); REGISTRY.register(Box::new(counter.clone())).unwrap(); counter }); -/// One series per `(pool, parameter)` pair that pg_doorman currently -/// strips from the StartupMessage because its consecutive-rejection -/// counter crossed `general.startup_parameter_quarantine_threshold`. -/// Value is `1` while the entry is parked and flips back to `0` exactly -/// once when its TTL expires (the next backend spawn attempts the -/// parameter again from scratch). No mid-flight clearing on its own: -/// even if the operator fixes the underlying issue, the gauge stays at -/// `1` until `general.startup_parameter_quarantine_ttl` elapses. -pub(crate) static BACKEND_STARTUP_PARAMETER_QUARANTINED: Lazy = Lazy::new(|| { - let gauge = IntGaugeVec::new( - Opts::new( - "pg_doorman_backend_startup_parameter_quarantined", - "1 while pg_doorman has quarantined an operator-supplied \ - startup_parameter for a pool (consecutive rejections crossed \ - general.startup_parameter_quarantine_threshold). Cleared to 0 \ - only when the quarantine TTL expires.", - ), - &["pool", "parameter"], - ) - .unwrap(); - REGISTRY.register(Box::new(gauge.clone())).unwrap(); - gauge -}); - /// Counter for protocol-level large-message streaming events. pg_doorman /// drops to byte-stream forwarding when a server message of type DataRow /// ('D') or CopyData ('d') exceeds max_message_size — see diff --git a/src/web/routes/collect/pools.rs b/src/web/routes/collect/pools.rs index 6b53e7ccf..c00955829 100644 --- a/src/web/routes/collect/pools.rs +++ b/src/web/routes/collect/pools.rs @@ -73,7 +73,6 @@ pub(crate) fn collect_pools() -> PoolsDto { startup_parameters: StartupParameterDto::from_resolved( pool.database.effective_startup_parameters_with_sources(), ), - quarantined_params: pool.database.quarantined_startup_parameters(), }; pools.push(dto); } diff --git a/src/web/routes/dto.rs b/src/web/routes/dto.rs index 0c4a4d14e..d5b5a9a98 100644 --- a/src/web/routes/dto.rs +++ b/src/web/routes/dto.rs @@ -156,14 +156,6 @@ pub(crate) struct PoolDto { /// has no operator overrides for this user. #[serde(skip_serializing_if = "Vec::is_empty")] pub startup_parameters: Vec, - /// Subset of startup parameter names currently parked by the per-pool - /// quarantine. Backends spawn without these keys while the entry lives; - /// PG `RESET ALL` / `DISCARD ALL` therefore restore PG defaults rather - /// than the operator-configured values for the duration of the quarantine. - /// Mirrors `SHOW POOLS.quarantined_params` and the - /// `pg_doorman_backend_startup_parameter_quarantined` gauge. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub quarantined_params: Vec, } /// One entry in `PoolDto.startup_parameters`. The `source` field tells the diff --git a/tests/bdd/features/startup-parameters.feature b/tests/bdd/features/startup-parameters.feature index ebe8d76d5..a558a56e2 100644 --- a/tests/bdd/features/startup-parameters.feature +++ b/tests/bdd/features/startup-parameters.feature @@ -222,7 +222,6 @@ Feature: Per-pool startup_parameters admin_username = "admin" admin_password = "admin" pg_hba.content = "host all all 127.0.0.1/32 md5" - startup_parameter_quarantine_threshold = 999 [pools.example_db] server_host = "127.0.0.1" @@ -238,10 +237,10 @@ Feature: Per-pool startup_parameters pool_size = 2 """ Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" fails - And pg_doorman log contains "backend startup rejected" + And pg_doorman log contains "PG rejected operator-supplied startup parameter" And pg_doorman log contains "nonexistent_guc_zzz" - Scenario: quarantine after consecutive rejections strips the bad key on next attempt + Scenario: every subsequent connect fails the same way until the operator fixes the config Given PostgreSQL started with pg_hba.conf: """ local all all trust @@ -257,8 +256,6 @@ Feature: Per-pool startup_parameters admin_username = "admin" admin_password = "admin" pg_hba.content = "host all all 127.0.0.1/32 md5" - startup_parameter_quarantine_threshold = 1 - startup_parameter_quarantine_ttl = 60000 [pools.example_db] server_host = "127.0.0.1" @@ -273,17 +270,14 @@ Feature: Per-pool startup_parameters password = "md58a67a0c805a5ee0384ea28e0dea557b6" pool_size = 2 """ - # First connection trips the quarantine threshold and fails. + # PostgreSQL rejects the unknown GUC at backend startup; pg_doorman + # forwards the rejection to the client. The very next connect repeats + # the same failure - no quarantine, no silent strip, no graceful + # recovery: the operator must fix the parameter in the config for the + # pool to start working. Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" fails - # After quarantine engages, the bad key is stripped and a fresh backend can - # be created; the connection now succeeds and the SHOW returns the default - # because nonexistent_guc_yyy never reached PostgreSQL. - Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" succeeds - # SHOW POOLS surfaces the quarantined parameter name so an operator - # triaging from psql can see what pg_doorman is parking. - When we create admin session "adm" to pg_doorman as "admin" with password "admin" - And we execute "SHOW POOLS" on admin session "adm" and store response - Then admin session "adm" response should contain "nonexistent_guc_yyy" + Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" fails + And pg_doorman log contains "nonexistent_guc_yyy" Scenario: RESET ALL restores startup_parameters defaults Given PostgreSQL started with pg_hba.conf: @@ -551,7 +545,7 @@ Feature: Per-pool startup_parameters -U admin -d pgdoorman -A -t -c 'SHOW STARTUP_PARAMETERS' """ Then the command should succeed - # Default psql -A -t output is pipe-delimited: user|database|parameter|value|source|quarantined. + # Default psql -A -t output is pipe-delimited: user|database|parameter|value|source. # The general baseline shows up as source=general; the pool override as source=pool. - And the command output should contain "statement_timeout|10s|general|no" - And the command output should contain "plan_cache_mode|force_custom_plan|pool|no" + And the command output should contain "statement_timeout|10s|general" + And the command output should contain "plan_cache_mode|force_custom_plan|pool" From bb95309fdd62b5e2d301ee643bbdaab3fcf6309d Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 08:16:20 +0300 Subject: [PATCH 27/72] fix(bdd): enable log capture in the every-connect-fails scenario The PG-rejection scenario added in 3e09aad asserts on pg_doorman log output with And pg_doorman log contains, but skipped the Given pg_doorman log capture enabled step that wires the log tap into the test runtime. The step panics on every CI run; local cargo test happened to pass because a previous scenario had primed the tap. EOF --- tests/bdd/features/startup-parameters.feature | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/bdd/features/startup-parameters.feature b/tests/bdd/features/startup-parameters.feature index a558a56e2..b24f83dc6 100644 --- a/tests/bdd/features/startup-parameters.feature +++ b/tests/bdd/features/startup-parameters.feature @@ -241,7 +241,8 @@ Feature: Per-pool startup_parameters And pg_doorman log contains "nonexistent_guc_zzz" Scenario: every subsequent connect fails the same way until the operator fixes the config - Given PostgreSQL started with pg_hba.conf: + Given pg_doorman log capture enabled + And PostgreSQL started with pg_hba.conf: """ local all all trust host all all 127.0.0.1/32 trust From c2efe793e9dffac2d823eddca113d438343d55e1 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 08:40:07 +0300 Subject: [PATCH 28/72] perf(startup_parameters): cache pool baseline, single-alloc StartupMessage, short-circuit empty operator path Three per-backend-spawn hot spots flagged by the performance review, addressed without changing observable behaviour. * ServerPool now caches the general+pool startup_parameters baseline as Arc> at construction. The reload path rebuilds the pool whenever either layer's hash changes, so the snapshot is immutable for the pool's lifetime. resolved_startup_ parameters returns Cow: the common no-auth-query-overlay path returns Cow::Borrowed(&*base) and allocates no BTreeMap on the spawn hot path. Server::startup now takes the map by reference, so neither the plain attempt nor the sslmode=allow TLS retry has to clone it before re-use. * messages::protocol::startup builds the StartupMessage into a single BytesMut sized via full_packet_bytes; the previous shape paid two BytesMut allocations and one full-body memcpy per spawn just to prepend the length prefix. A debug_assert_eq catches drift between the size helper and the actual write. * operator_managed_startup_keys is HashSet::new() when the operator cascade is empty (no backing-table allocation), and sync_parameters short-circuits the retain pass on the same is_empty check. Pools not using the feature pay zero per-checkout cost for the operator- wins guard. --- src/messages/protocol.rs | 68 ++++++++++++--------- src/pool/server_pool.rs | 111 +++++++++++++++++++++-------------- src/server/server_backend.rs | 27 ++++++--- 3 files changed, 125 insertions(+), 81 deletions(-) diff --git a/src/messages/protocol.rs b/src/messages/protocol.rs index 5a2b103d6..f990dfb40 100644 --- a/src/messages/protocol.rs +++ b/src/messages/protocol.rs @@ -7,6 +7,7 @@ use bytes::{Buf, BufMut, BytesMut}; use md5::{Digest, Md5}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; // Internal crate imports +use crate::config::startup_parameters::full_packet_bytes; use crate::errors::Error; use crate::messages::socket::{write_all, write_all_flush}; use crate::messages::types::DataType; @@ -178,15 +179,28 @@ pub async fn startup( where S: tokio::io::AsyncWrite + std::marker::Unpin, { - let mut bytes = BytesMut::new(); - - // Protocol version - bytes.put_i32(196608); // Version 3.0 - - // User - bytes.put(&b"user\0"[..]); - bytes.put_slice(user.as_bytes()); - bytes.put_u8(0); + // Pre-compute the exact wire size so the whole StartupMessage fits in + // one allocation and the length prefix can be written in place. The + // previous shape (`BytesMut::new()` body, then a second `with_capacity` + // copy for the length prefix) paid two allocations and a memcpy per + // backend spawn. + let total_size = full_packet_bytes( + user.as_str(), + database, + application_name.as_str(), + extra_params, + ); + let mut startup = BytesMut::with_capacity(total_size); + + // Length prefix (includes itself). + startup.put_i32(total_size as i32); + // Protocol version 3.0. + startup.put_i32(196608); + + // User. + startup.put(&b"user\0"[..]); + startup.put_slice(user.as_bytes()); + startup.put_u8(0); // Application name. Operator-supplied value in `extra_params` wins over // the pg_doorman-managed default. @@ -194,34 +208,34 @@ where .get("application_name") .map(String::as_str) .unwrap_or(application_name.as_str()); - bytes.put(&b"application_name\0"[..]); - bytes.put_slice(effective_app_name.as_bytes()); - bytes.put_u8(0); + startup.put(&b"application_name\0"[..]); + startup.put_slice(effective_app_name.as_bytes()); + startup.put_u8(0); - // Database - bytes.put(&b"database\0"[..]); - bytes.put_slice(database.as_bytes()); - bytes.put_u8(0); + // Database. + startup.put(&b"database\0"[..]); + startup.put_slice(database.as_bytes()); + startup.put_u8(0); // Operator-supplied extras (already-handled application_name is skipped). for (key, value) in extra_params { if key == "application_name" { continue; } - bytes.put_slice(key.as_bytes()); - bytes.put_u8(0); - bytes.put_slice(value.as_bytes()); - bytes.put_u8(0); + startup.put_slice(key.as_bytes()); + startup.put_u8(0); + startup.put_slice(value.as_bytes()); + startup.put_u8(0); } - bytes.put_u8(0); // Parameter list terminator - - let len = bytes.len() as i32 + 4i32; - - let mut startup = BytesMut::with_capacity(len as usize); + // Parameter-list terminator. + startup.put_u8(0); - startup.put_i32(len); - startup.put(bytes); + debug_assert_eq!( + startup.len(), + total_size, + "full_packet_bytes drifted from the actual startup serializer" + ); match stream.write_all(&startup).await { Ok(_) => Ok(()), diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index e3e5f35fa..38903be6a 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -4,6 +4,7 @@ //! server connections. It handles connect timeouts, lifetime checks, alive //! checks, pause/resume, and reconnect epoch management. +use std::collections::BTreeMap; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -11,6 +12,7 @@ use std::time::Duration; use log::{debug, info, warn}; use tokio::sync::{Notify, Semaphore}; +use crate::config::startup_parameters as sp; use crate::config::{Address, User}; use crate::errors::Error; use crate::patroni::types::Role; @@ -83,6 +85,16 @@ pub struct ServerPool { /// Notify to wake up clients blocked on PAUSE. resume_notify: Notify, + + /// `general.startup_parameters` merged with this pool's + /// `pool.startup_parameters` — the baseline that every backend spawn + /// from this pool ships in `StartupMessage`, before the optional + /// per-user auth_query overlay is applied. Cached once at pool + /// construction: the reload path rebuilds the pool whenever either + /// layer's hash changes (see `ConnectionPool::from_config`), so this + /// view is immutable for the lifetime of the pool object. Shared as + /// `Arc` so backend spawns can borrow it without per-call cloning. + base_startup_parameters: Arc>, } impl std::fmt::Debug for ServerPool { @@ -129,6 +141,23 @@ impl ServerPool { session_mode: bool, fallback_state: Option>, ) -> ServerPool { + // Merge general+pool startup_parameters once at construction. The + // reload path rebuilds this pool whenever either layer's hash + // changes (see `ConnectionPool::from_config`), so this snapshot + // stays valid for the pool's lifetime. Per-user auth_query overlay + // is layered on top per spawn inside `resolved_startup_parameters`. + let base_startup_parameters = { + let cfg = crate::config::config_arc(); + let mut merged: std::collections::BTreeMap = + cfg.general.startup_parameters.clone(); + if let Some(pool_cfg) = cfg.pools.get(&address.pool_name) { + for (k, v) in &pool_cfg.startup_parameters { + merged.insert(k.clone(), v.clone()); + } + } + Arc::new(merged) + }; + ServerPool { address, user: user.clone(), @@ -149,6 +178,7 @@ impl ServerPool { resume_notify: Notify::new(), session_mode, fallback_state, + base_startup_parameters, } } @@ -238,7 +268,7 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - startup_parameters.clone(), + &startup_parameters, ), ) .await; @@ -298,7 +328,7 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - startup_parameters, + &startup_parameters, ), ) .await; @@ -377,44 +407,39 @@ impl ServerPool { } /// Resolve the operator-supplied startup_parameters map that this pool - /// will hand to `Server::startup` for one backend spawn. The cascade is - /// `general` -> pool -> (optional) auth_query per-user entry, with the - /// more specific level winning per key. + /// will hand to `Server::startup` for one backend spawn. /// - /// The general and pool maps are read live from the current config - /// snapshot, so a RELOAD that only changes operator-supplied parameters - /// can take effect on the next backend spawn without rebuilding the - /// pool. The auth_query layer is consulted only when this pool has a - /// passthrough auth_query state and a cached entry for the pool's - /// username already exists; dedicated mode pools never consult the - /// cache here, even though the cache write path already clears the map - /// in that mode, because the safety net is cheaper than another bug - /// hunt. - fn resolved_startup_parameters(&self) -> std::collections::BTreeMap { - let cfg = crate::config::config_arc(); - let pool_cfg = cfg.pools.get(&self.address.pool_name); - - let pool_params = pool_cfg - .map(|p| &p.startup_parameters) - .cloned() - .unwrap_or_default(); - + /// Fast path (no per-user auth_query overlay): returns + /// `Cow::Borrowed(&self.base_startup_parameters)` — zero allocation. + /// Slow path (passthrough auth_query with a populated cache entry): + /// clones the cached base and layers the per-user overrides on top. + /// + /// Both paths validate the merged cascade against the operator budget + /// and the exact PG `MAX_STARTUP_PACKET_LENGTH`; an overflow returns an + /// empty map for this spawn and logs, so the backend connects with PG + /// defaults rather than failing every retry. + fn resolved_startup_parameters(&self) -> std::borrow::Cow<'_, BTreeMap> { // Look up the per-user auth_query entry only when the pool runs in // passthrough auth_query mode (no shared server_user). In dedicated // mode the shared backend serves multiple dynamic users, so no // single per-user override could be honoured. - let auth_query_params = match super::get_auth_query_state(&self.address.pool_name) { + let auth_query_overlay = match super::get_auth_query_state(&self.address.pool_name) { Some(state) if !state.config.is_dedicated_mode() => { state.peek_startup_parameters(&self.user.username) } _ => None, }; + let has_overlay = auth_query_overlay.as_ref().is_some_and(|m| !m.is_empty()); - let merged = super::startup_resolver::resolve( - &cfg.general.startup_parameters, - &pool_params, - auth_query_params.as_ref(), - ); + let merged: std::borrow::Cow<'_, BTreeMap> = if has_overlay { + let mut owned = (*self.base_startup_parameters).clone(); + for (k, v) in auth_query_overlay.as_ref().unwrap() { + owned.insert(k.clone(), v.clone()); + } + std::borrow::Cow::Owned(owned) + } else { + std::borrow::Cow::Borrowed(&*self.base_startup_parameters) + }; // Per-level validation in `Config::validate` does not see the merged // cascade, and the per-level guard does not see the user/database/ @@ -422,7 +447,7 @@ impl ServerPool { // checks, in order: // // 1. Fast path: just the operator-supplied pairs against the - // `MAX_OPERATOR_BUDGET`. This catches cascade overflows without + // operator budget. This catches cascade overflows without // knowing the username/database length. // 2. Exact-length check: the full StartupMessage pg_doorman is about // to put on the wire, against PG's `MAX_STARTUP_PACKET_LENGTH`. @@ -430,12 +455,8 @@ impl ServerPool { // includes the length prefix, version, `user`, effective // `application_name` and `database` so a long `application_name` // cannot slip a near-budget operator set over the cap. - // - // On overflow drop all operator-supplied keys for this spawn and log; - // the backend connects with server defaults rather than failing on - // every retry. - let body_bytes = crate::config::startup_parameters::serialized_bytes(&merged); - if body_bytes > crate::config::startup_parameters::MAX_OPERATOR_BUDGET { + let body_bytes = sp::serialized_bytes(&merged); + if body_bytes > sp::MAX_OPERATOR_BUDGET { warn!( "[{}@{}] effective startup_parameters serialize to {} bytes, exceeding \ operator budget {} (PG cap {}); all operator-supplied parameters dropped \ @@ -443,23 +464,23 @@ impl ServerPool { self.user.username, self.address.pool_name, body_bytes, - crate::config::startup_parameters::MAX_OPERATOR_BUDGET, - crate::config::startup_parameters::MAX_STARTUP_PACKET_SIZE, + sp::MAX_OPERATOR_BUDGET, + sp::MAX_STARTUP_PACKET_SIZE, ); - return std::collections::BTreeMap::new(); + return std::borrow::Cow::Owned(BTreeMap::new()); } let username_for_wire = self .user .server_username .as_deref() .unwrap_or(self.user.username.as_str()); - let packet_bytes = crate::config::startup_parameters::full_packet_bytes( + let packet_bytes = sp::full_packet_bytes( username_for_wire, &self.database, &self.application_name, &merged, ); - if packet_bytes > crate::config::startup_parameters::MAX_STARTUP_PACKET_SIZE { + if packet_bytes > sp::MAX_STARTUP_PACKET_SIZE { warn!( "[{}@{}] effective StartupMessage size {} bytes exceeds PG cap {} once \ user/database/application_name are included; all operator-supplied \ @@ -467,9 +488,9 @@ impl ServerPool { self.user.username, self.address.pool_name, packet_bytes, - crate::config::startup_parameters::MAX_STARTUP_PACKET_SIZE, + sp::MAX_STARTUP_PACKET_SIZE, ); - return std::collections::BTreeMap::new(); + return std::borrow::Cow::Owned(BTreeMap::new()); } merged } @@ -817,7 +838,7 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - startup_parameters.clone(), + &startup_parameters, ), ) .await; @@ -866,7 +887,7 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - startup_parameters, + &startup_parameters, ), ) .await; diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index fa500be07..c3cfbb033 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -705,8 +705,11 @@ impl Server { // may still issue an explicit SET later, but the per-checkout sync // from `client.server_parameters` must not silently overwrite an // operator-supplied default with a client-side value (e.g. an - // application_name baked into a connection string). - parameter_diff.retain(|k, _| !self.operator_managed_startup_keys.contains(k)); + // application_name baked into a connection string). Skip the + // retain pass when no operator keys are tracked. + if !self.operator_managed_startup_keys.is_empty() { + parameter_diff.retain(|k, _| !self.operator_managed_startup_keys.contains(k)); + } if parameter_diff.is_empty() { return Ok(()); @@ -775,7 +778,7 @@ impl Server { server_prepared_statement_cache_size: usize, application_name: String, session_mode: bool, - startup_parameters: std::collections::BTreeMap, + startup_parameters: &std::collections::BTreeMap, ) -> Result { let config = get_config(); @@ -821,14 +824,12 @@ impl Server { let auth_started = Instant::now(); let mut startup_started: Option = None; - let startup_parameters_sent = startup_parameters; - startup( &mut stream, username.clone(), database, application_name.clone(), - &startup_parameters_sent, + startup_parameters, ) .await?; @@ -962,11 +963,11 @@ impl Server { // error path; the client still receives the PG // sqlstate so the operator sees the same failure // their application would see talking to PG directly. - if !startup_parameters_sent.is_empty() { + if !startup_parameters.is_empty() { if let Some(param_name) = crate::server::startup_error::extract_parameter_name(&msg.message) { - if startup_parameters_sent.contains_key(¶m_name) { + if startup_parameters.contains_key(¶m_name) { warn!( "[{}@{}] PG rejected operator-supplied startup \ parameter=\"{}\" sqlstate={} message=\"{}\"; the \ @@ -1054,8 +1055,16 @@ impl Server { phase_started.elapsed().as_secs_f64(), ); + // No backing-table allocation for pools that don't use + // the feature: HashSet::new() is empty until the first + // insert. Operators on the typical zero-param path pay + // no per-spawn HashSet cost. let operator_managed_startup_keys: HashSet = - startup_parameters_sent.keys().cloned().collect(); + if startup_parameters.is_empty() { + HashSet::new() + } else { + startup_parameters.keys().cloned().collect() + }; let server = Server { address: address.to_owned(), From 3aabbfe178d4b459438f8388b5bc5ea2aff0d283 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 08:42:52 +0300 Subject: [PATCH 29/72] fix(metrics): keep backend_startup_parameter_errors_total accurate under non-English lc_messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgreSQL servers running with localized `lc_messages` translate the prose of an ErrorResponse, but the parameter name itself is still wrapped in double quotes regardless of locale. The previous code only matched the canonical English phrase `parameter ""`, so on a non-English PG every operator-supplied GUC rejection was a blind spot: the warn log fired with the raw localized text and the pg_doorman_backend_startup_parameter_errors_total counter stayed at zero, hiding the failure from any dashboard or alert built on it. Add a locale-independent fallback: when the English template does not match, scan the M-field for any operator-supplied key wrapped in double quotes. PG always quotes the parameter name, so a quoted match is a reliable signal even when the surrounding prose is translated. Bare substring search is deliberately avoided so unrelated prose that happens to mention the word cannot poison the counter. The metric doc-comment now spells out the contract: counter increments only when a sent key is identified by either the English template or the quoted-substring fallback. If both fail — typically because the PG error is unrelated to startup_parameters — the counter does not move, so an operator reading a non-zero rate can act on it without chasing red herrings. SQLSTATE class 57P stays excluded (it goes through the Patroni-assisted fallback), and the parameter name and username remain off the Prometheus labels so dynamic auth_query roles cannot inflate series count. --- src/server/server_backend.rs | 66 +++++++++++++++++++++------------- src/server/startup_error.rs | 70 ++++++++++++++++++++++++++++++++++-- src/web/metrics/mod.rs | 51 ++++++++++++++++++-------- 3 files changed, 144 insertions(+), 43 deletions(-) diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index c3cfbb033..f4f3e1c23 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -956,33 +956,49 @@ impl Server { )); } - // Warn the operator when PG rejected a parameter we - // actually sent — this is the signal that - // general/pool/auth_query has a typo or a value the - // PG instance does not accept. We do not change the - // error path; the client still receives the PG - // sqlstate so the operator sees the same failure - // their application would see talking to PG directly. + // Identify the failing parameter. Two-step lookup so the + // counter is not silently zeroed on PostgreSQL servers + // running with non-English `lc_messages`: + // + // 1. Parse the canonical English `parameter ""` + // phrase. Most production fleets keep PG default + // locale, so this is the common case. + // 2. If parsing failed or returned a key we did not + // send, scan the message body for any operator- + // supplied key surrounded by double quotes. PG + // quotes the parameter name in every locale, even + // when the surrounding prose is translated. + // + // When either step finds a sent key, we warn-log with + // the parameter name and increment the per-pool + // counter. The client still receives PG's verbatim + // sqlstate either way; this whole branch is + // observability for the operator, not control flow. if !startup_parameters.is_empty() { - if let Some(param_name) = + let matched_key = crate::server::startup_error::extract_parameter_name(&msg.message) - { - if startup_parameters.contains_key(¶m_name) { - warn!( - "[{}@{}] PG rejected operator-supplied startup \ - parameter=\"{}\" sqlstate={} message=\"{}\"; the \ - error is being forwarded to the client. Fix the \ - parameter in general/pool/auth_query.", - address.username, - address.pool_name, - param_name, - msg.code, - msg.message, - ); - crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL - .with_label_values(&[&address.pool_name, &msg.code]) - .inc(); - } + .filter(|n| startup_parameters.contains_key(n)) + .or_else(|| { + crate::server::startup_error::match_sent_key_in_message( + &msg.message, + startup_parameters.keys(), + ) + }); + if let Some(param_name) = matched_key { + warn!( + "[{}@{}] PG rejected operator-supplied startup \ + parameter=\"{}\" sqlstate={} message=\"{}\"; the \ + error is being forwarded to the client. Fix the \ + parameter in general/pool/auth_query.", + address.username, + address.pool_name, + param_name, + msg.code, + msg.message, + ); + crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL + .with_label_values(&[&address.pool_name, &msg.code]) + .inc(); } } diff --git a/src/server/startup_error.rs b/src/server/startup_error.rs index 09e4bf44f..ab6cd22f2 100644 --- a/src/server/startup_error.rs +++ b/src/server/startup_error.rs @@ -33,12 +33,40 @@ pub fn extract_parameter_name(message: &str) -> Option { Some(rest[..end].to_owned()) } +/// Locale-independent fallback for `extract_parameter_name`: scan the +/// PG ErrorResponse `M` field for any of the operator-supplied keys +/// pg_doorman actually sent, looking for the standard PG double-quoted +/// form (`"key"`). PG quotes the parameter name in every locale even when +/// the surrounding prose is translated, so a hit on `"name"` is a +/// reliable signal that the failing key is `name`. +/// +/// Returns the first match in iteration order of `sent_keys`. Used by +/// `Server::startup` to keep the +/// `pg_doorman_backend_startup_parameter_errors_total` counter usable +/// against PG servers with non-English `lc_messages`. +pub fn match_sent_key_in_message<'a, I>(message: &str, sent_keys: I) -> Option +where + I: IntoIterator, +{ + for key in sent_keys { + // Match the GUC name surrounded by double quotes. Bare substring + // search would false-positive on prose like "key is wrong" when + // `key` happens to be the parameter name; the quote markers in + // PG ErrorResponse messages are stable across locales. + let needle = format!("\"{key}\""); + if message.contains(&needle) { + return Some(key.clone()); + } + } + None +} + /// Handles error response during server startup. /// /// Currently unused: `Server::startup` inlines the equivalent logic so it -/// can also inspect the M-field for the failing parameter name and feed -/// it into the quarantine. Kept in tree as a reference helper for any -/// future startup path that needs the pre-quarantine behavior verbatim. +/// can also surface the failing parameter name into the warn log. Kept +/// in tree as a reference helper for any future startup path that needs +/// the verbatim PG-error-passthrough behaviour. #[allow(dead_code)] pub(crate) async fn handle_startup_error( stream: &mut StreamInner, @@ -148,4 +176,40 @@ mod parameter_extractor_tests { Some("auto_explain.log_min_duration".into()) ); } + + #[test] + fn match_sent_key_finds_quoted_key_in_localized_message() { + // Hypothetical Russian lc_messages output: prose is translated, + // PG still wraps the parameter name in double quotes. + let sent = vec!["plan_cache_mode".to_string(), "work_mem".to_string()]; + let msg = r#"параметр "plan_cache_mode" не существует"#; + assert_eq!( + match_sent_key_in_message(msg, &sent), + Some("plan_cache_mode".into()) + ); + } + + #[test] + fn match_sent_key_returns_none_when_no_quoted_match() { + let sent = vec!["plan_cache_mode".to_string()]; + // The key appears in prose but not quoted — must not match, + // otherwise unrelated PG errors mentioning the word would + // poison the counter. + let msg = "some unrelated startup error mentions plan_cache_mode somewhere"; + assert!(match_sent_key_in_message(msg, &sent).is_none()); + } + + #[test] + fn match_sent_key_skips_keys_not_in_message() { + let sent = vec![ + "first_key".to_string(), + "second_key".to_string(), + "third_key".to_string(), + ]; + let msg = r#"FATAL: invalid value for parameter "second_key""#; + assert_eq!( + match_sent_key_in_message(msg, &sent), + Some("second_key".into()) + ); + } } diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index 7c3b55679..155cc6ea2 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -457,25 +457,46 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| counter }); -/// Counter for every backend startup attempt that PostgreSQL rejected -/// because of an operator-supplied `startup_parameters` entry. Tracked -/// per pool and split by `sqlstate` so dashboards can tell whether a -/// pool is hitting `22023` (invalid value), `42704` (undefined object), -/// `42501` (insufficient privilege), `55P02` (cant change runtime -/// parameter), or a fresh PG code under the startup-parameter family. -/// The failing parameter name and username are on the corresponding warn -/// log line; they are intentionally left out of the label set so a -/// dynamic auth_query pool cannot blow up the series count when many -/// roles share a broken config. +/// Counts backend startup attempts pg_doorman aborted because PostgreSQL +/// returned an `ErrorResponse` that names a key the pool actually sent in +/// `StartupMessage`. Labels: +/// +/// * `pool` — `@` identifier. +/// * `sqlstate` — PG SQLSTATE on the rejection (`22023`, `42704`, +/// `42501`, `55P02`, or any other code under the startup-parameter +/// family — pg_doorman does not pre-filter by SQLSTATE). +/// +/// SQLSTATE class `57P*` (server unavailable) is excluded: those +/// `ErrorResponse`s are surfaced as `ServerUnavailableError` to drive the +/// Patroni-assisted fallback path before the counter branch is reached. +/// +/// Identification of the failing key is best-effort: pg_doorman first +/// parses the canonical English `parameter ""` phrase, then falls +/// back to scanning the M-field for any sent key wrapped in double +/// quotes (PG keeps the quote markers across all `lc_messages` locales). +/// If both heuristics fail — typically because the PG error is unrelated +/// to the sent map at all — the counter does NOT increment. Operator +/// reading a non-zero rate can be confident the issue is on a key they +/// configured; the per-line warn log carries the parameter name and +/// username for triage. +/// +/// The parameter name and username are intentionally NOT in the label +/// set so a dynamic `auth_query` pool that mints per-tenant roles cannot +/// blow up Prometheus series count by reading user input into labels. pub(crate) static BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL: Lazy = Lazy::new(|| { let counter = IntCounterVec::new( Opts::new( "pg_doorman_backend_startup_parameter_errors_total", - "Cumulative count of backend startup attempts rejected by \ - PostgreSQL because of an operator-supplied startup_parameters \ - entry. Labels: pool, sqlstate (PostgreSQL SQLSTATE on the \ - returned ErrorResponse). The failing parameter name and \ - username are available in the corresponding warn log line.", + "Cumulative count of backend startup attempts pg_doorman \ + aborted because PostgreSQL ErrorResponse identified a key \ + this pool sent in StartupMessage (operator-supplied \ + startup_parameters cascade). Labels: pool, sqlstate. \ + SQLSTATE class 57P (server unavailable) is excluded — \ + those rejections take the Patroni-assisted fallback path \ + instead. The failing parameter name and username are in \ + the corresponding warn log line; kept out of the label set \ + so dynamic auth_query roles cannot inflate Prometheus \ + series count.", ), &["pool", "sqlstate"], ) From 93e384b6e56d96ef00b3fc81d8accdbcd42aafa3 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 08:56:27 +0300 Subject: [PATCH 30/72] fix(startup_parameters): forward PG sqlstate verbatim + counter for pre-wire drops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multi-discipline review (DBA + DevOps + Tech writer) flagged two observability gaps that this commit closes. DBA P0: forward verbatim PG sqlstate. The previous shape squashed every PG ErrorResponse during backend startup into Error::ServerStartupError(format!("{}: {}", code, msg)) — a plain String — and the checkout site at client/transaction.rs mapped every failure to the generic 53300 too_many_connections fallback. So a client connecting to a pool whose operator-supplied startup_parameters carried an unknown GUC saw 53300 instead of the real 42704 undefined_object PG would return on a direct connection. Documentation promised the PG-native error; the code did not deliver. New typed Error::ServerStartupParameterRejection { sqlstate, message, server_identifier } carries the verbatim PG payload. Server::startup emits it when the failing parameter parses out of the ErrorResponse and matches a key the pool actually sent. The checkout path in client/transaction.rs intercepts the variant and forwards (sqlstate, message) to error_response, so the client receives the same PG sqlstate it would have seen connecting to PostgreSQL directly. SQLSTATE class 57P (server unavailable) is still routed through ServerUnavailableError to drive the Patroni-assisted fallback path before this branch is reached. DevOps P0: counter for the four silent pre-wire drop paths. New pg_doorman_startup_parameters_dropped_total{pool, reason} fires on the four cases where pg_doorman drops operator-supplied entries before sending StartupMessage — events that the *_errors_total counter could not see because PostgreSQL never had a chance to reject: * reason=cascade_budget_exceeded: merged general+pool+auth_query map exceeded the operator budget (9 488 bytes). Every operator-supplied key dropped for that spawn; backend connected with PG defaults. * reason=packet_cap_exceeded: full StartupMessage including user, application_name and database would exceed PG's MAX_STARTUP_PACKET_LENGTH (10 000 bytes). Same drop-all behaviour. * reason=auth_query_oversize: the per-user auth_query startup_parameters text column exceeded the operator budget at parse time; the per-user overlay was ignored. * reason=auth_query_invalid_entry: an individual JSON entry failed validation (reserved key, bad GUC name, null byte, non-string value). Incremented per offending entry. All four cases continue to emit a warn-log line for human triage; the counter exists so dashboards and alerts can spot the silent drop without log scraping. The reason label is a bounded enum, so cardinality stays under control. Tech writer P0: EN reference docs and Prometheus reference now list the new fields and metrics. src/app/generate/docs.rs grew an explicit entry for general.startup_parameters and pools..startup_parameters in the field arrays, plus a new "Operator-supplied startup_parameters" subsection in the metrics table that describes both counters and their label sets. --- src/app/errors.rs | 21 +++++++++++ src/app/generate/docs.rs | 13 +++++++ src/auth/auth_query.rs | 9 +++++ src/client/transaction.rs | 39 ++++++++++++++++++++ src/pool/server_pool.rs | 22 +++++++----- src/server/server_backend.rs | 70 +++++++++++++++++++----------------- src/web/metrics/mod.rs | 44 +++++++++++++++++++++++ 7 files changed, 177 insertions(+), 41 deletions(-) diff --git a/src/app/errors.rs b/src/app/errors.rs index 798a832e9..60c02993d 100644 --- a/src/app/errors.rs +++ b/src/app/errors.rs @@ -21,6 +21,19 @@ pub enum Error { /// FATAL with SQLSTATE 57P01/57P02/57P03: backend accepted the connection /// but is shutting down or starting up. ServerUnavailableError(String, ServerIdentifier), + /// PostgreSQL rejected the StartupMessage because of an operator-supplied + /// `startup_parameters` entry — typo, bad value, insufficient privilege, + /// or postmaster-only knob. Carries the verbatim PG `SQLSTATE` and + /// `M`-field so the client receives the PG-native error rather than the + /// generic `53300` connection-pool fallback. SQLSTATE class `57P` does + /// NOT use this variant — those go through `ServerUnavailableError` to + /// drive the Patroni-assisted fallback path before this branch is + /// reached. + ServerStartupParameterRejection { + sqlstate: String, + message: String, + server_identifier: ServerIdentifier, + }, ServerStartupReadParameters(String), BadConfig(String), AllServersDown, @@ -144,6 +157,14 @@ impl std::fmt::Display for Error { Error::ServerUnavailableError(error, server_identifier) => { write!(f, "Backend unavailable: {error} for {server_identifier}") } + Error::ServerStartupParameterRejection { + sqlstate, + message, + server_identifier, + } => write!( + f, + "PostgreSQL rejected operator-supplied startup parameter (sqlstate {sqlstate}): {message} for {server_identifier}" + ), Error::ServerStartupReadParameters(msg) => { write!(f, "Failed to read server parameters: {msg}") } diff --git a/src/app/generate/docs.rs b/src/app/generate/docs.rs index d3a3d4bf8..6a987bf1b 100644 --- a/src/app/generate/docs.rs +++ b/src/app/generate/docs.rs @@ -261,6 +261,7 @@ fn write_general_fields(out: &mut String, f: &FieldsData) { "hba", "pg_hba", "pooler_check_query", + "startup_parameters", ]; for name in &fields { @@ -291,6 +292,7 @@ fn write_pool_fields(out: &mut String, f: &FieldsData) { "reserve_pool_size", "reserve_pool_timeout", "min_guaranteed_pool_size", + "startup_parameters", ]; for name in &fields { @@ -468,6 +470,17 @@ fn write_prometheus_metrics_section(out: &mut String) { let _ = writeln!(out, "| `pg_doorman_auth_query_executor` | DEPRECATED, removed in 3.10. Gauge mirror of `pg_doorman_auth_query_executor_total`. |"); let _ = writeln!(out, "| `pg_doorman_auth_query_dynamic_pools` | Auth query dynamic pool lifecycle metrics by type and database. Types include: `current` (currently active dynamic pools), `created` (total pools created since startup), `destroyed` (total pools garbage-collected or removed on RELOAD). Only relevant in passthrough mode. |\n"); + // Operator-supplied startup_parameters + let _ = writeln!(out, "### Operator-supplied startup_parameters\n"); + let _ = writeln!( + out, + "Surface PG-side rejections and pre-wire drops of the operator-supplied cascade. Operators set `general.startup_parameters` / `pools..startup_parameters` or the per-user auth_query column; if PostgreSQL refuses a value (typo, wrong privilege, postmaster-only knob) the rejection lands on `pg_doorman_backend_startup_parameter_errors_total`. The companion `pg_doorman_startup_parameters_dropped_total` flags cases where pg_doorman aborted the cascade before sending it (over-budget, oversize auth_query JSON, bad entry in the JSON object) — same surface, different stage.\n" + ); + let _ = writeln!(out, "| Metric | Description |"); + let _ = writeln!(out, "|--------|-------------|"); + let _ = writeln!(out, "| `pg_doorman_backend_startup_parameter_errors_total` | Counter by `(pool, sqlstate)`. Increments once per backend startup where PostgreSQL returned an `ErrorResponse` naming a key pg_doorman sent in `StartupMessage` (operator-supplied cascade). SQLSTATE class `57P` is excluded — those rejections go through `ServerUnavailableError` and the Patroni-assisted fallback path instead. The failing parameter name and username are in the corresponding warn log line; they are kept off the label set so a dynamic auth_query pool cannot inflate Prometheus series count. Identification is best-effort: pg_doorman first parses the canonical English `parameter \"\"` phrase, then falls back to scanning the M-field for any sent key wrapped in double quotes (locale-stable). If both heuristics fail, the counter does not move. |"); + let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman dropped operator-supplied entries *before* the `StartupMessage` went on the wire — the failure mode the `*_errors_total` counter cannot see because PG never had a chance to reject. `reason` values: `cascade_budget_exceeded` (merged map exceeded operator budget), `packet_cap_exceeded` (full packet would exceed PG's `MAX_STARTUP_PACKET_LENGTH`), `auth_query_oversize` (the auth_query text column exceeded the operator budget at parse time), `auth_query_invalid_entry` (one entry failed validation: reserved key, bad GUC name, null byte, non-string value). |\n"); + // Server Metrics let _ = writeln!(out, "### Server Metrics\n"); let _ = writeln!(out, "| Metric | Description |"); diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index a0119e628..0cf66eafd 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -455,6 +455,9 @@ impl AuthQueryExecutor { exceeding operator budget {max_bytes}; parameters ignored", text.len() ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[pool_name, "auth_query_oversize"]) + .inc(); return std::collections::HashMap::new(); } let parsed: serde_json::Value = match serde_json::from_str(text) { @@ -483,6 +486,9 @@ impl AuthQueryExecutor { probe.insert(k.clone(), s.clone()); if let Err(e) = crate::config::startup_parameters::validate(&probe, &scope) { warn!("[{pool_name}] {e}"); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[pool_name, "auth_query_invalid_entry"]) + .inc(); continue; } out.insert(k, s); @@ -500,6 +506,9 @@ impl AuthQueryExecutor { "[{username}@{pool_name}] auth_query startup_parameters: value for '{k}' \ is {kind}, not string; ignored" ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[pool_name, "auth_query_invalid_entry"]) + .inc(); } } } diff --git a/src/client/transaction.rs b/src/client/transaction.rs index 903fbe520..4c0aa5487 100644 --- a/src/client/transaction.rs +++ b/src/client/transaction.rs @@ -759,6 +759,45 @@ where // Mirrors the SQLSTATE in the ErrorResponse below // so the per-pool breakdown reflects checkout // failures alongside PG-side errors. + // + // Special case: PG itself rejected the + // operator-supplied startup_parameters cascade. + // Forward the verbatim sqlstate/message so the + // client receives the same PG-native error it + // would have seen connecting to PG directly, + // instead of the generic 53300 + // (too_many_connections) checkout-fallback. + // Same shape as the rest of the branch (reset + // buffered state on 'S', error_response, log, + // return), only the SQLSTATE and message differ. + if let crate::pool::PoolError::Backend( + Error::ServerStartupParameterRejection { + sqlstate, + message: pg_message, + .. + }, + ) = &err + { + current_pool.address.stats.error_with_sqlstate(sqlstate); + self.stats.checkout_error(); + + if message[0] as char == 'S' { + self.reset_buffered_state(); + } + + error_response(&mut self.write, pg_message, sqlstate).await?; + + error!( + "[{}@{} #c{}] PG rejected startup_parameters: sqlstate={} {}", + self.username, + self.pool_name, + self.connection_id, + sqlstate, + pg_message, + ); + return Err(Error::AllServersDown); + } + current_pool.address.stats.error_with_sqlstate("53300"); self.stats.checkout_error(); diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 38903be6a..76c9addd5 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -429,16 +429,16 @@ impl ServerPool { } _ => None, }; - let has_overlay = auth_query_overlay.as_ref().is_some_and(|m| !m.is_empty()); - let merged: std::borrow::Cow<'_, BTreeMap> = if has_overlay { - let mut owned = (*self.base_startup_parameters).clone(); - for (k, v) in auth_query_overlay.as_ref().unwrap() { - owned.insert(k.clone(), v.clone()); + let merged: std::borrow::Cow<'_, BTreeMap> = match auth_query_overlay { + Some(overlay) if !overlay.is_empty() => { + let mut owned = (*self.base_startup_parameters).clone(); + for (k, v) in &overlay { + owned.insert(k.clone(), v.clone()); + } + std::borrow::Cow::Owned(owned) } - std::borrow::Cow::Owned(owned) - } else { - std::borrow::Cow::Borrowed(&*self.base_startup_parameters) + _ => std::borrow::Cow::Borrowed(&*self.base_startup_parameters), }; // Per-level validation in `Config::validate` does not see the merged @@ -467,6 +467,9 @@ impl ServerPool { sp::MAX_OPERATOR_BUDGET, sp::MAX_STARTUP_PACKET_SIZE, ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[self.address.pool_name.as_str(), "cascade_budget_exceeded"]) + .inc(); return std::borrow::Cow::Owned(BTreeMap::new()); } let username_for_wire = self @@ -490,6 +493,9 @@ impl ServerPool { packet_bytes, sp::MAX_STARTUP_PACKET_SIZE, ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[self.address.pool_name.as_str(), "packet_cap_exceeded"]) + .inc(); return std::borrow::Cow::Owned(BTreeMap::new()); } merged diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index f4f3e1c23..2e1affdfe 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -957,8 +957,8 @@ impl Server { } // Identify the failing parameter. Two-step lookup so the - // counter is not silently zeroed on PostgreSQL servers - // running with non-English `lc_messages`: + // observability does not silently regress on PostgreSQL + // servers running with non-English `lc_messages`: // // 1. Parse the canonical English `parameter ""` // phrase. Most production fleets keep PG default @@ -969,37 +969,41 @@ impl Server { // quotes the parameter name in every locale, even // when the surrounding prose is translated. // - // When either step finds a sent key, we warn-log with - // the parameter name and increment the per-pool - // counter. The client still receives PG's verbatim - // sqlstate either way; this whole branch is - // observability for the operator, not control flow. - if !startup_parameters.is_empty() { - let matched_key = - crate::server::startup_error::extract_parameter_name(&msg.message) - .filter(|n| startup_parameters.contains_key(n)) - .or_else(|| { - crate::server::startup_error::match_sent_key_in_message( - &msg.message, - startup_parameters.keys(), - ) - }); - if let Some(param_name) = matched_key { - warn!( - "[{}@{}] PG rejected operator-supplied startup \ - parameter=\"{}\" sqlstate={} message=\"{}\"; the \ - error is being forwarded to the client. Fix the \ - parameter in general/pool/auth_query.", - address.username, - address.pool_name, - param_name, - msg.code, - msg.message, - ); - crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL - .with_label_values(&[&address.pool_name, &msg.code]) - .inc(); - } + // When either step finds a sent key, we warn-log, + // increment the per-pool counter, AND return the new + // typed `ServerStartupParameterRejection` so the + // checkout path can forward the PG sqlstate to the + // client verbatim (instead of substituting the generic + // 53300 "pool exhausted" fallback that every other + // server startup failure receives). + let matched_key = if startup_parameters.is_empty() { + None + } else { + crate::server::startup_error::extract_parameter_name(&msg.message) + .filter(|n| startup_parameters.contains_key(n)) + .or_else(|| { + crate::server::startup_error::match_sent_key_in_message( + &msg.message, + startup_parameters.keys(), + ) + }) + }; + if let Some(param_name) = matched_key { + warn!( + "[{}@{}] PG rejected operator-supplied startup \ + parameter=\"{}\" sqlstate={} message=\"{}\"; the \ + error is being forwarded to the client. Fix the \ + parameter in general/pool/auth_query.", + address.username, address.pool_name, param_name, msg.code, msg.message, + ); + crate::web::metrics::BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL + .with_label_values(&[&address.pool_name, &msg.code]) + .inc(); + return Err(Error::ServerStartupParameterRejection { + sqlstate: msg.code, + message: msg.message, + server_identifier: server_identifier.clone(), + }); } return Err(Error::ServerStartupError( diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index 155cc6ea2..2ba894db0 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -483,6 +483,50 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// The parameter name and username are intentionally NOT in the label /// set so a dynamic `auth_query` pool that mints per-tenant roles cannot /// blow up Prometheus series count by reading user input into labels. +/// Counts cases where pg_doorman dropped operator-supplied +/// `startup_parameters` *before* the StartupMessage went on the wire — +/// the failure mode the per-pool `*_errors_total` counter cannot see +/// because PG never had a chance to reject. Labels: +/// +/// * `pool` — `@` identifier. +/// * `reason` — bounded enum: +/// * `cascade_budget_exceeded` — the merged general+pool+auth_query +/// map exceeded the operator budget (`MAX_OPERATOR_BUDGET`, 9 488 +/// bytes). Every operator-supplied key was dropped for that spawn +/// and the backend got PG defaults instead. +/// * `packet_cap_exceeded` — the full StartupMessage including user, +/// application_name and database would exceed PG's +/// `MAX_STARTUP_PACKET_LENGTH` (10 000 bytes). Same drop-all +/// behaviour. +/// * `auth_query_oversize` — the auth_query `startup_parameters` +/// text column for some username exceeded the operator budget at +/// parse time, so the per-user overlay is ignored. +/// * `auth_query_invalid_entry` — an individual entry in the +/// auth_query JSON failed validation (reserved key, bad GUC name, +/// null byte, non-string value). Incremented per offending entry. +/// +/// All four cases also emit a `warn!` log line for human triage; the +/// counter exists so dashboards and alerts can spot the silent drop +/// without log scraping. +pub(crate) static STARTUP_PARAMETERS_DROPPED_TOTAL: Lazy = Lazy::new(|| { + let counter = IntCounterVec::new( + Opts::new( + "pg_doorman_startup_parameters_dropped_total", + "Cumulative count of operator-supplied startup_parameters \ + entries pg_doorman dropped before sending StartupMessage. \ + Labels: pool, reason (cascade_budget_exceeded, \ + packet_cap_exceeded, auth_query_oversize, \ + auth_query_invalid_entry). Distinct from \ + pg_doorman_backend_startup_parameter_errors_total which \ + counts PG-side rejections after StartupMessage.", + ), + &["pool", "reason"], + ) + .unwrap(); + REGISTRY.register(Box::new(counter.clone())).unwrap(); + counter +}); + pub(crate) static BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL: Lazy = Lazy::new(|| { let counter = IntCounterVec::new( Opts::new( From b70e313c21782f9df8a9ccaf1219ec67aed34744 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 09:25:40 +0300 Subject: [PATCH 31/72] refactor(startup_parameters): explicit ServerPool dependency, closure-based peek, &str startup, single SQLSTATE classifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architectural and performance fixes from the multi-discipline review. Architect P1.1 + Perf P1.2 — ServerPool dependency made explicit. ServerPool::new now takes the merged general+pool startup_parameters baseline as an Arc parameter. Callers in from_config / dynamic.rs / test helpers compute the merge from the same `config` snapshot they hashed for the reuse decision, so there is no in-constructor config_arc() read that could race a parallel reload. The dependency is now visible at the function signature. Architect P1.3 — single SQLSTATE classifier. extract classify_pg_startup_error in src/server/startup_error.rs as the one place that maps a PG ErrorResponse code/message into the right Error variant: ServerUnavailableError for class 57P (so the Patroni-assisted fallback wins), ServerStartupParameterRejection when a sent key matches the message (forward PG sqlstate verbatim to the client), and ServerStartupError otherwise. handle_startup_error (previously dead-code allow-listed) now delegates to the helper so the rule cannot drift across call sites. Perf P1.1 — closure-based peek_startup_parameters. AuthQueryCache and AuthQueryState now hand the cached HashMap reference to a closure instead of cloning it. ServerPool::resolved_startup_parameters merges the per-user overlay directly into its owned BTreeMap inside the closure on the slow path, and skips the merge entirely on the fast (empty overlay) path, keeping Cow::Borrowed of the cached Arc base. One DashMap shard read-lock, zero HashMap clones on the backend-spawn hot path. Perf P1.3 — protocol::startup borrows user / application_name as &str. Caller in Server::startup no longer clones them before serialization (saves ~2 small String allocs per backend spawn). The existing length-prefix-in-place packet builder (single BytesMut::with_capacity sized by full_packet_bytes) is unchanged. startup_resolver::resolve is now marked #[allow(dead_code)] and documented as the canonical-reference variant of the cascade rule; the production hot path uses ServerPool::base_startup_parameters directly. The unit tests around `resolve` are kept as the executable specification of the merge contract. --- src/auth/auth_query.rs | 100 +++++++++++----------- src/messages/protocol.rs | 56 +++++-------- src/pool/auth_query_state.rs | 12 ++- src/pool/dynamic.rs | 14 ++++ src/pool/inner.rs | 2 + src/pool/mod.rs | 30 +++++++ src/pool/server_pool.rs | 148 +++++++++++++-------------------- src/pool/startup_resolver.rs | 7 ++ src/server/server_backend.rs | 64 ++++---------- src/server/startup_error.rs | 86 +++++++++++++++---- tests/bdd/pool_bench_helper.rs | 2 + 11 files changed, 280 insertions(+), 241 deletions(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index 0cf66eafd..f96430985 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -33,20 +33,17 @@ const MAX_USERNAME_LEN: usize = 63; // PasswordFetcher trait (allows mocking AuthQueryExecutor in unit tests) // --------------------------------------------------------------------------- -/// Password hash plus the per-user startup_parameters map surfaced by -/// auth_query. The map is empty when the SQL did not return the optional -/// `startup_parameters` column, when the column was NULL/empty, or when -/// every entry failed validation. +/// Password hash plus the per-user startup_parameters map returned by +/// auth_query. The map is empty when the optional column is absent, NULL, +/// empty, or fully rejected by validation. pub type Credentials = (String, std::collections::HashMap); /// Trait for fetching credentials from PostgreSQL. /// `AuthQueryExecutor` implements this; tests and benchmarks can substitute a mock. /// -/// `fetch` returns just the password hash; `fetch_credentials` additionally -/// returns the per-user startup_parameters map parsed from the optional -/// auth_query result column. The default implementation pairs the password -/// with an empty map, which is the correct semantics for any fetcher that -/// does not surface per-user parameters. +/// `fetch` returns the password hash. `fetch_credentials` also returns the +/// optional per-user startup parameter map. Fetchers that do not support that +/// column use the default empty map. pub trait PasswordFetcher: Send + Sync { fn fetch<'a>( &'a self, @@ -423,13 +420,10 @@ impl AuthQueryExecutor { Self::parse_startup_parameters_text(raw.as_deref(), username, pool_name) } - /// Parse the optional `startup_parameters` JSON-object value shipped by - /// the auth_query SQL. Each string-typed entry becomes a per-user GUC; - /// reserved keys, syntactically invalid GUC names, and non-string values - /// are dropped with a warning. Unparseable JSON and JSON that is not an - /// object both yield an empty map with a warning. Never fails: this is - /// operator-supplied configuration data, and a malformed entry must not - /// block authentication. + /// Parse the optional `startup_parameters` JSON object returned by + /// auth_query. Valid string entries become per-user GUCs. Invalid keys, + /// non-string values, malformed JSON, and non-object JSON are logged and + /// ignored; authentication still continues. fn parse_startup_parameters_text( text: Option<&str>, username: &str, @@ -441,13 +435,9 @@ impl AuthQueryExecutor { if text.is_empty() { return std::collections::HashMap::new(); } - // Reject oversize input before serde_json walks the whole row. The - // operator budget covers the merged cascade pg_doorman ships in - // StartupMessage, so a single auth_query row that already exceeds - // that budget on its own can never produce a sendable map. Capping - // here keeps a misbehaving DB row from forcing pg_doorman to - // allocate megabytes of serde_json::Value tree on every auth_query - // refresh. + // Reject oversize input before serde_json allocates the Value tree. + // A single auth_query row above the operator budget cannot produce a + // sendable startup map. let max_bytes = crate::config::startup_parameters::MAX_OPERATOR_BUDGET; if text.len() > max_bytes { warn!( @@ -536,7 +526,7 @@ pub struct CacheEntry { /// for SCRAM passthrough to backend PG (Step 6). /// None for MD5 users or before first SCRAM auth. pub client_key: Option>, - /// Per-user startup parameters surfaced by the auth_query optional + /// Per-user startup parameters returned by the optional auth_query /// `startup_parameters` JSON column. Empty when the column is absent, /// empty/NULL, or filtered out in dedicated auth_query mode. pub startup_parameters: std::collections::HashMap, @@ -811,8 +801,7 @@ impl AuthQueryCache { } /// Clear all entries (called on RELOAD when auth_query config changes). - /// Also resets dedicated-mode warning suppression so an operator who - /// fixes their config and reloads gets fresh warnings next time. + /// Also resets dedicated-mode warning suppression after reload. pub fn clear(&self) { self.entries.clear(); self.locks.clear(); @@ -833,22 +822,21 @@ impl AuthQueryCache { .and_then(|e| e.client_key.clone()) } - /// Cheap, sync, non-fetching lookup of the per-user startup_parameters - /// map. Returns `None` when the username has no cached entry yet (e.g. - /// pool prewarm fires before any client has authenticated), when the - /// cached entry is negative, or when the cached entry has lived past - /// `cache_ttl` / `cache_failure_ttl`. Never triggers a PG fetch and - /// never initializes the executor; intended for the backend-spawn hot - /// path where blocking on auth_query I/O would be unacceptable. + /// Synchronous lookup of the cached per-user startup_parameters map. + /// Returns `None` when there is no positive, unexpired cache entry. This + /// never queries PostgreSQL or initializes the executor. /// - /// The TTL check exists so that a backend spawned by the replenishment - /// loop or anticipation path does not pin stale per-user GUCs after - /// the operator changed the `auth_query` row and the operator-visible - /// cache rotation moment has already passed. - pub fn peek_startup_parameters( + /// The TTL check prevents replenishment and anticipation from using stale + /// per-user GUCs after the auth_query row should have expired. + pub fn peek_startup_parameters( &self, username: &str, - ) -> Option> { + f: impl FnOnce(&std::collections::HashMap) -> R, + ) -> Option { + // Closure-based to avoid cloning the cached HashMap on every + // backend spawn. The DashMap shard read-lock is held only for the + // duration of `f`; consumers merge the overlay directly into their + // owned destination map instead of through an intermediate clone. let entry = self.entries.get(username)?; if entry.is_negative { return None; @@ -856,7 +844,7 @@ impl AuthQueryCache { if entry.is_expired(&self.cache_ttl, &self.cache_failure_ttl) { return None; } - Some(entry.startup_parameters.clone()) + Some(f(&entry.startup_parameters)) } /// Number of cached entries (for metrics/admin). @@ -1404,12 +1392,26 @@ mod tests { // peek_startup_parameters: sync, non-fetching lookup used by backend spawn // --------------------------------------------------------------------- + // Closure-based API tested by snapshotting the borrowed HashMap into + // an owned one when an existing assertion needs to inspect contents. + // Generic over the cache's fetcher because the test harness uses a + // `MockFetcher` rather than the production `AuthQueryExecutor`. + fn peek_snapshot( + cache: &AuthQueryCache, + username: &str, + ) -> Option> + where + F: PasswordFetcher, + { + cache.peek_startup_parameters(username, |m| m.clone()) + } + #[tokio::test] async fn peek_startup_parameters_missing_user_returns_none() { let fetcher = Arc::new(MockFetcher::new()); let config = test_config(); let cache = make_cache(fetcher, &config); - assert!(cache.peek_startup_parameters("alice").is_none()); + assert!(peek_snapshot(&cache, "alice").is_none()); } #[tokio::test] @@ -1419,7 +1421,7 @@ mod tests { let config = test_config(); let cache = make_cache(fetcher, &config); assert!(cache.get_or_fetch("ghost").await.unwrap().is_none()); - assert!(cache.peek_startup_parameters("ghost").is_none()); + assert!(peek_snapshot(&cache, "ghost").is_none()); } #[tokio::test] @@ -1436,13 +1438,13 @@ mod tests { let cache = make_cache(fetcher, &config); cache.get_or_fetch("alice").await.unwrap().unwrap(); - // Sanity: peek sees the fresh entry. - assert!(cache.peek_startup_parameters("alice").is_some()); + // Verify that peek sees the entry before it expires. + assert!(peek_snapshot(&cache, "alice").is_some()); tokio::time::sleep(std::time::Duration::from_millis(80)).await; assert!( - cache.peek_startup_parameters("alice").is_none(), + peek_snapshot(&cache, "alice").is_none(), "peek must return None once cache_ttl has elapsed for the entry" ); } @@ -1459,7 +1461,7 @@ mod tests { let cache = make_cache(fetcher, &config); cache.get_or_fetch("alice").await.unwrap().unwrap(); - let params = cache.peek_startup_parameters("alice").unwrap(); + let params = peek_snapshot(&cache, "alice").unwrap(); assert_eq!(params.get("work_mem").map(String::as_str), Some("64MB")); assert_eq!( params.get("statement_timeout").map(String::as_str), @@ -1469,9 +1471,7 @@ mod tests { #[tokio::test] async fn peek_startup_parameters_dedicated_mode_returns_empty() { - // Dedicated mode filters cached entries to drop per-user params. - // peek must surface the filtered state: a present-but-empty map, - // not None, because the user is still cached. + // Dedicated mode keeps the user cached but removes per-user params. let fetcher = Arc::new(MockFetcher::new()); fetcher.add_user_with_params("alice", "md5abc123", &[("work_mem", "64MB")]); let mut config = test_config(); @@ -1481,7 +1481,7 @@ mod tests { let cache = make_cache(fetcher, &config); cache.get_or_fetch("alice").await.unwrap().unwrap(); - let params = cache.peek_startup_parameters("alice").unwrap(); + let params = peek_snapshot(&cache, "alice").unwrap(); assert!(params.is_empty()); } } diff --git a/src/messages/protocol.rs b/src/messages/protocol.rs index f990dfb40..b8b53a7ba 100644 --- a/src/messages/protocol.rs +++ b/src/messages/protocol.rs @@ -162,34 +162,23 @@ pub fn simple_query(query: &str) -> BytesMut { /// Send startup message to the server. /// -/// Required parameters (`user`, `application_name`, `database`) are written -/// first in their historical wire order, so the byte stream is unchanged when -/// `extra_params` is empty. Operator-supplied values from `extra_params` are -/// appended in the map's iteration order (BTreeMap → lexicographic). If -/// `extra_params` contains an `application_name` key, it overrides the -/// `application_name` argument so an operator-supplied value wins over the -/// pg_doorman-managed default. +/// Required parameters (`user`, `application_name`, `database`) keep their +/// historical wire order when `extra_params` is empty. Additional parameters +/// are appended in BTreeMap order. `extra_params["application_name"]` +/// overrides the default application name. pub async fn startup( stream: &mut S, - user: String, + user: &str, database: &str, - application_name: String, + application_name: &str, extra_params: &std::collections::BTreeMap, ) -> Result<(), Error> where S: tokio::io::AsyncWrite + std::marker::Unpin, { - // Pre-compute the exact wire size so the whole StartupMessage fits in - // one allocation and the length prefix can be written in place. The - // previous shape (`BytesMut::new()` body, then a second `with_capacity` - // copy for the length prefix) paid two allocations and a memcpy per - // backend spawn. - let total_size = full_packet_bytes( - user.as_str(), - database, - application_name.as_str(), - extra_params, - ); + // Pre-compute the wire size so the StartupMessage fits in one allocation + // and the length prefix can be written in place. + let total_size = full_packet_bytes(user, database, application_name, extra_params); let mut startup = BytesMut::with_capacity(total_size); // Length prefix (includes itself). @@ -207,7 +196,7 @@ where let effective_app_name = extra_params .get("application_name") .map(String::as_str) - .unwrap_or(application_name.as_str()); + .unwrap_or(application_name); startup.put(&b"application_name\0"[..]); startup.put_slice(effective_app_name.as_bytes()); startup.put_u8(0); @@ -345,7 +334,7 @@ pub fn md5_hash_second_pass(hash: &str, salt: &[u8]) -> Vec { } /// Send password challenge response to the server. -/// This is the MD5 challenge. +/// Handles the MD5 challenge. pub async fn md5_password( stream: &mut S, user: &str, @@ -857,7 +846,7 @@ pub fn insert_close_complete_after_last_close_complete( } /// Insert ParseComplete messages before each ParameterDescription ('t') message. -/// This is used for the Describe flow when Parse was skipped due to caching. +/// Used by the Describe flow when Parse was skipped due to caching. /// /// Describe response for a statement is: /// - ParameterDescription ('t') followed by RowDescription ('T') or NoData ('n') @@ -994,7 +983,7 @@ mod startup_tests { ); params.insert("work_mem".to_string(), "64MB".to_string()); - startup(&mut buf, "alice".into(), "appdb", "myapp".into(), ¶ms) + startup(&mut buf, "alice", "appdb", "myapp", ¶ms) .await .expect("startup"); @@ -1025,15 +1014,9 @@ mod startup_tests { #[tokio::test] async fn startup_with_empty_params_keeps_pre_feature_format() { let mut buf: Vec = Vec::new(); - startup( - &mut buf, - "alice".into(), - "appdb", - "myapp".into(), - &BTreeMap::new(), - ) - .await - .expect("startup"); + startup(&mut buf, "alice", "appdb", "myapp", &BTreeMap::new()) + .await + .expect("startup"); let body = &buf[8..]; let s = String::from_utf8_lossy(body); assert!(s.contains("user\0alice")); @@ -1047,7 +1030,7 @@ mod startup_tests { let mut params = BTreeMap::new(); params.insert("application_name".to_string(), "operator_app".to_string()); - startup(&mut buf, "alice".into(), "appdb", "ignored".into(), ¶ms) + startup(&mut buf, "alice", "appdb", "ignored", ¶ms) .await .expect("startup"); @@ -1059,8 +1042,7 @@ mod startup_tests { assert!(!s.contains("application_name\0ignored")); } - /// Belt-and-suspenders: ensure the length prefix matches the byte count - /// PG reads — anything off here breaks every connection. + /// The length prefix must match the byte count PostgreSQL reads. #[tokio::test] async fn startup_length_prefix_matches_body() { let mut buf: Vec = Vec::new(); @@ -1068,7 +1050,7 @@ mod startup_tests { params.insert("k1".to_string(), "v1".to_string()); params.insert("k2".to_string(), "v2".to_string()); - startup(&mut buf, "u".into(), "d", "a".into(), ¶ms) + startup(&mut buf, "u", "d", "a", ¶ms) .await .expect("startup"); diff --git a/src/pool/auth_query_state.rs b/src/pool/auth_query_state.rs index 1bbf512b9..75491470a 100644 --- a/src/pool/auth_query_state.rs +++ b/src/pool/auth_query_state.rs @@ -107,10 +107,16 @@ impl AuthQueryState { /// username has no cached entry. Used on the backend-spawn hot path /// where blocking on a PG roundtrip would defeat the point of the /// cache; cold lookups intentionally surface as "no per-user override". - pub fn peek_startup_parameters( + /// Pass the cached per-user startup_parameters map (when present and + /// fresh) to `f` and return its result. `f` borrows the HashMap; no + /// clone happens on the backend-spawn hot path. Returns `None` if the + /// auth_query executor hasn't been lazily initialized yet, the entry + /// is absent / negative, or the entry's TTL has elapsed. + pub fn peek_startup_parameters( &self, username: &str, - ) -> Option> { - self.cache_cell.get()?.peek_startup_parameters(username) + f: impl FnOnce(&std::collections::HashMap) -> R, + ) -> Option { + self.cache_cell.get()?.peek_startup_parameters(username, f) } } diff --git a/src/pool/dynamic.rs b/src/pool/dynamic.rs index dc2f7fc82..4f1738600 100644 --- a/src/pool/dynamic.rs +++ b/src/pool/dynamic.rs @@ -122,6 +122,19 @@ pub fn create_dynamic_pool( let fallback_state = super::build_fallback_state(pool_name, pool_config, &config.general); + // Merge general+pool startup_parameters baseline from the same config + // snapshot. Dynamic auth_query pools follow the same lifecycle as + // static pools: rebuilt on RELOAD when the underlying base changes + // (see `general_startup_parameters_changed` in pool/mod.rs). + let base_startup_parameters = { + let mut merged: std::collections::BTreeMap = + config.general.startup_parameters.clone(); + for (k, v) in &pool_config.startup_parameters { + merged.insert(k.clone(), v.clone()); + } + std::sync::Arc::new(merged) + }; + let manager = ServerPool::new( address.clone(), user.clone(), @@ -143,6 +156,7 @@ pub fn create_dynamic_pool( config.general.query_wait_timeout.as_std(), pool_mode == PoolMode::Session, fallback_state, + base_startup_parameters, ); let queue_strategy = match config.general.server_round_robin { diff --git a/src/pool/inner.rs b/src/pool/inner.rs index fb5f78799..26b929e8a 100644 --- a/src/pool/inner.rs +++ b/src/pool/inner.rs @@ -2094,6 +2094,7 @@ mod tests { Duration::from_secs(5), false, None, + Arc::new(std::collections::BTreeMap::new()), ); Pool::builder(server_pool) .coordinator(Some(coord)) @@ -2243,6 +2244,7 @@ mod tests { Duration::from_secs(5), false, None, + Arc::new(std::collections::BTreeMap::new()), ); let pool = Pool::builder(server_pool) .pool_name("test_db".to_string()) diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 96e69f03e..60339935a 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -457,6 +457,25 @@ impl ConnectionPool { let fallback_state = build_fallback_state(pool_name, pool_config, &config.general); + // Merge general+pool startup_parameters from the same + // `config` snapshot we hashed above. ServerPool keeps this + // as Arc for the rest of its life — the reload + // path rebuilds the pool whenever either layer's hash + // changes, so the snapshot stays valid until then. Passing + // it in explicitly (rather than letting ServerPool::new + // call config_arc() again) closes a narrow race where a + // second reload between this iteration and constructor + // execution would write a different baseline to the pool + // than the one the reuse hash captured. + let base_startup_parameters = { + let mut merged: std::collections::BTreeMap = + config.general.startup_parameters.clone(); + for (k, v) in &pool_config.startup_parameters { + merged.insert(k.clone(), v.clone()); + } + Arc::new(merged) + }; + let manager = ServerPool::new( address.clone(), user.clone(), @@ -478,6 +497,7 @@ impl ConnectionPool { config.general.query_wait_timeout.as_std(), pool_mode == PoolMode::Session, fallback_state, + base_startup_parameters, ); let queue_strategy = match config.general.server_round_robin { @@ -631,6 +651,15 @@ impl ConnectionPool { let fallback_state = build_fallback_state(pool_name, pool_config, &config.general); + let base_startup_parameters = { + let mut merged: std::collections::BTreeMap = + config.general.startup_parameters.clone(); + for (k, v) in &pool_config.startup_parameters { + merged.insert(k.clone(), v.clone()); + } + Arc::new(merged) + }; + let manager = ServerPool::new( address.clone(), shared_user.clone(), @@ -652,6 +681,7 @@ impl ConnectionPool { config.general.query_wait_timeout.as_std(), pool_mode == PoolMode::Session, fallback_state, + base_startup_parameters, ); let queue_strategy = match config.general.server_round_robin { diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 76c9addd5..36f688636 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -140,24 +140,8 @@ impl ServerPool { query_wait_timeout: Duration, session_mode: bool, fallback_state: Option>, + base_startup_parameters: Arc>, ) -> ServerPool { - // Merge general+pool startup_parameters once at construction. The - // reload path rebuilds this pool whenever either layer's hash - // changes (see `ConnectionPool::from_config`), so this snapshot - // stays valid for the pool's lifetime. Per-user auth_query overlay - // is layered on top per spawn inside `resolved_startup_parameters`. - let base_startup_parameters = { - let cfg = crate::config::config_arc(); - let mut merged: std::collections::BTreeMap = - cfg.general.startup_parameters.clone(); - if let Some(pool_cfg) = cfg.pools.get(&address.pool_name) { - for (k, v) in &pool_cfg.startup_parameters { - merged.insert(k.clone(), v.clone()); - } - } - Arc::new(merged) - }; - ServerPool { address, user: user.clone(), @@ -274,15 +258,10 @@ impl ServerPool { .await; // libpq sslmode=allow: PostgreSQL has no protocol-level "TLS required" - // signal — pg_hba rejects plain connections via FATAL 28000 only after - // StartupMessage. The socket is dead after FATAL, so retry needs a fresh - // TCP connection. We retry on any startup failure (matching libpq), but - // skip retry on transport-level errors (ConnectError, ServerUnavailableError) - // since TLS cannot help when the server was never reached. Startup - // parameter rejections (PG ErrorResponse on a key we sent) are also - // not TLS-fixable, but they surface as `ServerStartupError` with the - // PG sqlstate intact — the client receives the same message PG would - // have produced, so the retry is a no-op rather than a hazard. + // signal. pg_hba rejects plain connections with FATAL 28000 after the + // StartupMessage, and the socket cannot be reused after that. Match + // libpq by retrying startup failures over TLS, but skip transport + // failures where no PostgreSQL startup response was received. // // Reference: PostgreSQL docs, "SSL Support" → sslmode parameter. let should_tls_retry = match &result { @@ -371,17 +350,11 @@ impl ServerPool { &self.address } - /// Inspect the operator-supplied cascade with the source layer kept for - /// each key. Used by the admin `SHOW STARTUP_PARAMETERS` command and the - /// `/api/pools` JSON so operators can answer "where did `work_mem=64MB` - /// come from for this pool" without reading the live config plus the - /// auth_query cache by hand. + /// Return the effective startup parameter cascade with the winning source + /// layer for each key. Used by `SHOW STARTUP_PARAMETERS` and `/api/pools`. /// - /// Unlike [`Self::resolved_startup_parameters`], this method does **not** - /// apply the runtime budget-overflow drop: it reports the cascade as - /// configured even when the merged body would not fit in PG's startup - /// packet, so the operator sees the misconfiguration through this view - /// instead of an empty result. + /// This does not apply the runtime budget-overflow drop. It reports the + /// configured cascade so the operator can see the oversized input. pub fn effective_startup_parameters_with_sources( &self, ) -> std::collections::BTreeMap @@ -393,12 +366,14 @@ impl ServerPool { .map(|p| &p.startup_parameters) .cloned() .unwrap_or_default(); - let auth_query_params = match super::get_auth_query_state(&self.address.pool_name) { - Some(state) if !state.config.is_dedicated_mode() => { - state.peek_startup_parameters(&self.user.username) - } - _ => None, - }; + // Snapshot the per-user auth_query map once for the admin/web view. + let auth_query_params: Option> = + match super::get_auth_query_state(&self.address.pool_name) { + Some(state) if !state.config.is_dedicated_mode() => { + state.peek_startup_parameters(&self.user.username, |m| m.clone()) + } + _ => None, + }; super::startup_resolver::resolve_with_sources( &cfg.general.startup_parameters, &pool_params, @@ -409,52 +384,50 @@ impl ServerPool { /// Resolve the operator-supplied startup_parameters map that this pool /// will hand to `Server::startup` for one backend spawn. /// - /// Fast path (no per-user auth_query overlay): returns - /// `Cow::Borrowed(&self.base_startup_parameters)` — zero allocation. - /// Slow path (passthrough auth_query with a populated cache entry): - /// clones the cached base and layers the per-user overrides on top. + /// Without a per-user auth_query overlay, this borrows the cached base + /// map. With an overlay, it clones the base map and applies the user + /// values. /// - /// Both paths validate the merged cascade against the operator budget - /// and the exact PG `MAX_STARTUP_PACKET_LENGTH`; an overflow returns an - /// empty map for this spawn and logs, so the backend connects with PG - /// defaults rather than failing every retry. + /// The merged map is checked against the operator budget and the full + /// PostgreSQL startup-packet limit. On overflow, pg_doorman logs and sends + /// no operator-supplied parameters for this backend startup. fn resolved_startup_parameters(&self) -> std::borrow::Cow<'_, BTreeMap> { // Look up the per-user auth_query entry only when the pool runs in // passthrough auth_query mode (no shared server_user). In dedicated // mode the shared backend serves multiple dynamic users, so no // single per-user override could be honoured. - let auth_query_overlay = match super::get_auth_query_state(&self.address.pool_name) { + // + // The closure runs under a DashMap shard read lock. Return `None` for + // an empty overlay so the caller can keep borrowing the base map. + let auth_query_state = super::get_auth_query_state(&self.address.pool_name); + let merged: std::borrow::Cow<'_, BTreeMap> = match auth_query_state { Some(state) if !state.config.is_dedicated_mode() => { - state.peek_startup_parameters(&self.user.username) - } - _ => None, - }; - - let merged: std::borrow::Cow<'_, BTreeMap> = match auth_query_overlay { - Some(overlay) if !overlay.is_empty() => { - let mut owned = (*self.base_startup_parameters).clone(); - for (k, v) in &overlay { - owned.insert(k.clone(), v.clone()); + let overlay_applied = + state.peek_startup_parameters(&self.user.username, |overlay| { + if overlay.is_empty() { + None + } else { + let mut owned = (*self.base_startup_parameters).clone(); + for (k, v) in overlay { + owned.insert(k.clone(), v.clone()); + } + Some(owned) + } + }); + match overlay_applied.flatten() { + Some(owned) => std::borrow::Cow::Owned(owned), + None => std::borrow::Cow::Borrowed(&*self.base_startup_parameters), } - std::borrow::Cow::Owned(owned) } _ => std::borrow::Cow::Borrowed(&*self.base_startup_parameters), }; - // Per-level validation in `Config::validate` does not see the merged - // cascade, and the per-level guard does not see the user/database/ - // application_name fields the wire layer always adds. Two cheap - // checks, in order: + // Per-level validation does not see the merged cascade or the + // user/database/application_name fields added by the wire layer. Check + // both limits here: // - // 1. Fast path: just the operator-supplied pairs against the - // operator budget. This catches cascade overflows without - // knowing the username/database length. - // 2. Exact-length check: the full StartupMessage pg_doorman is about - // to put on the wire, against PG's `MAX_STARTUP_PACKET_LENGTH`. - // Mirrors the layout in `crate::messages::protocol::startup` and - // includes the length prefix, version, `user`, effective - // `application_name` and `database` so a long `application_name` - // cannot slip a near-budget operator set over the cap. + // 1. Operator-supplied pairs against the reserved operator budget. + // 2. Full StartupMessage size against PG's `MAX_STARTUP_PACKET_LENGTH`. let body_bytes = sp::serialized_bytes(&merged); if body_bytes > sp::MAX_OPERATOR_BUDGET { warn!( @@ -553,8 +526,8 @@ impl ServerPool { (Ok(conn), _) => Ok(conn), (Err(err), super::fallback::TargetSource::WhitelistCache) => { // Cached host was stale; wipe it and try with full discovery - // exactly once more. Bounded retry — discovery round failure - // surfaces directly without a third try. + // exactly once more. If discovery fails too, return that + // failure without a third attempt. info!( "[{}@{}] fallback: whitelist round failed ({err}), retrying with fresh discovery", self.address.username, self.address.pool_name, @@ -729,8 +702,8 @@ impl ServerPool { /// Race `Server::startup` against `targets` in parallel. On first Ok /// return `Some(server)` (winner is whitelisted as a side effect). On /// full exhaustion mark every loser unhealthy, record reasons into - /// `summary`, and return `None` — the caller advances to the next wave - /// or surfaces the aggregate. + /// `summary`, and return `None`; the caller advances to the next wave or + /// returns the aggregate error. async fn race_wave( &self, fallback: &super::fallback::FallbackState, @@ -1096,12 +1069,12 @@ impl FailureSummary { /// Race `futures` and return the first `Ok`, together with its index in the /// input slice. If every future yields `Err`, return all errors with their -/// original indices — the caller decides how to surface them (per-host -/// cooldown, log aggregation). Pending futures are dropped on first +/// original indices; the caller decides how to use them for per-host cooldown +/// and log aggregation. Pending futures are dropped on first /// success, which cancels the in-flight `Server::startup` for the losing /// candidates: their TCP sockets go away under us; the kernel finishes the -/// half-open handshake asynchronously. This is intentional — the -/// user-facing requirement is "first successful sync wins", and chasing +/// half-open handshake asynchronously. The user-facing requirement is +/// "first successful sync wins", and chasing /// graceful disconnect on every loser would gate the winner on the slowest /// loser. async fn race_first_success<'a, T: 'a, E: 'a>( @@ -1191,8 +1164,7 @@ mod tests { #[test] fn lifetime_exceeded_skipped_when_under_pressure() { // A connection well past its budget is kept alive when the caller - // signals pressure. This is the whole point of the new flag: a - // working connection must not be closed mid-storm. + // signals pressure. A working connection must not be closed mid-storm. let metrics = metrics_with_lifetime(1); thread::sleep(Duration::from_millis(5)); assert!(lifetime_exceeded(&metrics, true).is_none()); @@ -1229,9 +1201,9 @@ mod tests { async fn startup_with_timeout_returns_connect_error_on_deadline() { // Simulates a server that opened TCP but never replies to // StartupMessage: the inner future never resolves. We expect - // `startup_with_timeout` to surface this as `ConnectError`, which is - // what callers treat as a transport-level failure (triggers fallback - // on the main path; marks the candidate unhealthy on the fallback path). + // `startup_with_timeout` to return `ConnectError`, which callers treat + // as a transport-level failure (triggers fallback on the main path; + // marks the candidate unhealthy on the fallback path). let pending = std::future::pending::>(); let result = startup_with_timeout(Duration::from_millis(20), "1.2.3.4", 5432, pending).await; diff --git a/src/pool/startup_resolver.rs b/src/pool/startup_resolver.rs index c30b26918..962b53f35 100644 --- a/src/pool/startup_resolver.rs +++ b/src/pool/startup_resolver.rs @@ -12,6 +12,13 @@ use std::collections::{BTreeMap, HashMap}; /// `auth_query` (static user), and also for dedicated-mode auth_query pools /// where one shared backend serves multiple dynamic users so per-user /// parameters cannot be honoured. +/// +/// The production hot path goes through +/// [`ServerPool::resolved_startup_parameters`] using a cached +/// `Arc` for the general+pool base; this function is the +/// pure-cascade variant kept as the canonical reference of the merge +/// rule and exercised by unit tests. +#[allow(dead_code)] pub fn resolve( general: &BTreeMap, pool: &BTreeMap, diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index 2e1affdfe..9cf0a0ee5 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -310,7 +310,7 @@ impl Server { } /// Drains any remaining data from the server that hasn't been read yet. - /// This is used to synchronize the connection state when data is unexpectedly available. + /// Used to synchronize connection state when data is unexpectedly available. /// All received data is discarded (sent to a sink). pub async fn wait_available(&mut self) { if !self.is_data_available() { @@ -507,7 +507,7 @@ impl Server { } /// Sets the number of expected responses in async mode. - /// This is calculated from the batch operations before sending to server. + /// Calculated from the batch operations before sending to the server. #[inline(always)] pub fn set_expected_responses(&mut self, count: u32) { self.expected_responses = count; @@ -699,14 +699,8 @@ impl Server { pub async fn sync_parameters(&mut self, parameters: &ServerParameters) -> Result<(), Error> { let mut parameter_diff = self.server_parameters.compare_params(parameters); - // Keys the operator injected through `startup_parameters` are - // authoritative for the session: they are pg_settings.reset_val on - // the backend and the operator chose them on purpose. The client - // may still issue an explicit SET later, but the per-checkout sync - // from `client.server_parameters` must not silently overwrite an - // operator-supplied default with a client-side value (e.g. an - // application_name baked into a connection string). Skip the - // retain pass when no operator keys are tracked. + // Do not let values from the client startup packet overwrite + // operator-supplied startup defaults during checkout sync. if !self.operator_managed_startup_keys.is_empty() { parameter_diff.retain(|k, _| !self.operator_managed_startup_keys.contains(k)); } @@ -759,13 +753,10 @@ impl Server { /// Pretend to be the Postgres client and connect to the server given host, port and credentials. /// Perform the authentication and return the server in a ready for query state. /// - /// `startup_parameters` is the cascade-resolved operator-supplied map - /// (general → pool → auth_query, more specific wins). It goes onto the - /// wire as part of `StartupMessage` exactly as the operator configured - /// it. If PG rejects any of those parameters, the resulting - /// `ErrorResponse` is surfaced to the client unchanged — pg_doorman - /// behaves like the PostgreSQL backend itself rather than masking the - /// failure with retries or per-key quarantine. + /// `startup_parameters` is the resolved cascade + /// (`general` -> pool -> auth_query). It is sent in the backend + /// `StartupMessage`. If PostgreSQL rejects a value, pg_doorman forwards + /// the `ErrorResponse` unchanged. #[allow(clippy::too_many_arguments)] pub async fn startup( address: &Address, @@ -826,9 +817,9 @@ impl Server { startup( &mut stream, - username.clone(), + username.as_str(), database, - application_name.clone(), + application_name.as_str(), startup_parameters, ) .await?; @@ -930,14 +921,9 @@ impl Server { } } - // ErrorResponse. Read the message body and parse it. PG's - // verdict is the operator's verdict — we surface the - // sqlstate and message to the client unchanged. The one - // exception is the Patroni-assisted fallback path: SQLSTATE - // class `57P*` (server unavailable) needs its dedicated - // `ServerUnavailableError` classification so the fallback - // discovery can route around the failed node instead of - // returning a startup error to the client. + // ErrorResponse during startup. Keep SQLSTATE class 57P + // on the fallback path; other startup errors are forwarded + // to the client as PostgreSQL returned them. 'E' => { let mut bytes = read_message_data(&mut stream, code as u8, len).await?; let _ = bytes.get_u8(); @@ -956,26 +942,12 @@ impl Server { )); } - // Identify the failing parameter. Two-step lookup so the - // observability does not silently regress on PostgreSQL - // servers running with non-English `lc_messages`: + // Identify the failing parameter for logs and metrics. // - // 1. Parse the canonical English `parameter ""` - // phrase. Most production fleets keep PG default - // locale, so this is the common case. - // 2. If parsing failed or returned a key we did not - // send, scan the message body for any operator- - // supplied key surrounded by double quotes. PG - // quotes the parameter name in every locale, even - // when the surrounding prose is translated. - // - // When either step finds a sent key, we warn-log, - // increment the per-pool counter, AND return the new - // typed `ServerStartupParameterRejection` so the - // checkout path can forward the PG sqlstate to the - // client verbatim (instead of substituting the generic - // 53300 "pool exhausted" fallback that every other - // server startup failure receives). + // First parse the common English `parameter ""` + // phrase, then fall back to looking for any sent key in + // double quotes. The fallback covers translated + // `lc_messages` where PostgreSQL still quotes the name. let matched_key = if startup_parameters.is_empty() { None } else { diff --git a/src/server/startup_error.rs b/src/server/startup_error.rs index ab6cd22f2..133560bc5 100644 --- a/src/server/startup_error.rs +++ b/src/server/startup_error.rs @@ -33,6 +33,61 @@ pub fn extract_parameter_name(message: &str) -> Option { Some(rest[..end].to_owned()) } +/// Classify a PostgreSQL `ErrorResponse` received during backend startup +/// into the right pg_doorman `Error` variant. Centralizes the SQLSTATE → +/// Error mapping so the rule stays in one place across `Server::startup`, +/// `handle_startup_error`, and anywhere else the startup path needs to +/// react to a PG-side rejection. +/// +/// Decision order: +/// +/// 1. SQLSTATE class `57P*` (server unavailable / shutting down / starting +/// up / cannot connect now) → `ServerUnavailableError`. Drives the +/// Patroni-assisted fallback path; must win over the startup-parameter +/// branch so a node-down rejection cannot be misclassified as a bad +/// operator GUC. +/// 2. If `sent_keys` is non-empty AND the PG message names a key in that +/// set (English `parameter ""` template, with a +/// locale-independent fallback that scans for any sent key wrapped in +/// double quotes) → `ServerStartupParameterRejection { sqlstate, +/// message, server_identifier }`. Lets the checkout site forward the +/// PG sqlstate verbatim to the client, instead of the generic 53300 +/// pool-exhausted fallback. +/// 3. Otherwise → `ServerStartupError(: , …)`. +pub fn classify_pg_startup_error<'a, I>( + sqlstate: String, + message: String, + server_identifier: &ServerIdentifier, + sent_keys: I, +) -> Error +where + I: IntoIterator, +{ + if sqlstate.starts_with("57P") { + return Error::ServerUnavailableError(message, server_identifier.clone()); + } + let mut sent_iter = sent_keys.into_iter().peekable(); + if sent_iter.peek().is_some() { + // Collect lazily so we walk the sent set at most once: try the + // English template first; on miss, scan once for any quoted key. + let parsed_key = extract_parameter_name(&message); + let collected: Vec<&'a String> = sent_iter.collect(); + let matched = parsed_key + .as_ref() + .filter(|n| collected.iter().any(|k| k.as_str() == n.as_str())) + .cloned() + .or_else(|| match_sent_key_in_message(&message, collected.iter().copied())); + if matched.is_some() { + return Error::ServerStartupParameterRejection { + sqlstate, + message, + server_identifier: server_identifier.clone(), + }; + } + } + Error::ServerStartupError(format!("{sqlstate}: {message}"), server_identifier.clone()) +} + /// Locale-independent fallback for `extract_parameter_name`: scan the /// PG ErrorResponse `M` field for any of the operator-supplied keys /// pg_doorman actually sent, looking for the standard PG double-quoted @@ -61,12 +116,11 @@ where None } -/// Handles error response during server startup. -/// -/// Currently unused: `Server::startup` inlines the equivalent logic so it -/// can also surface the failing parameter name into the warn log. Kept -/// in tree as a reference helper for any future startup path that needs -/// the verbatim PG-error-passthrough behaviour. +/// Handles error response during server startup. Surfaces the PG sqlstate +/// through [`classify_pg_startup_error`] so non-`Server::startup` callers +/// (currently none in production; kept for symmetry and future code paths +/// that need verbatim PG-error-passthrough behaviour) follow the same +/// `ServerUnavailableError` / `ServerStartupError` mapping. #[allow(dead_code)] pub(crate) async fn handle_startup_error( stream: &mut StreamInner, @@ -105,17 +159,15 @@ pub(crate) async fn handle_startup_error( f.code, f.message ); - if f.code.starts_with("57P") { - Err(Error::ServerUnavailableError( - f.message, - server_identifier.clone(), - )) - } else { - Err(Error::ServerStartupError( - f.message, - server_identifier.clone(), - )) - } + // No sent-key set is available here; the helper falls + // back to plain `ServerStartupError` for non-57P codes, + // matching the previous behaviour exactly. + Err(classify_pg_startup_error( + f.code, + f.message, + server_identifier, + std::iter::empty::<&String>(), + )) } Err(err) => { error!( diff --git a/tests/bdd/pool_bench_helper.rs b/tests/bdd/pool_bench_helper.rs index a7423eaec..b87c8b2bc 100644 --- a/tests/bdd/pool_bench_helper.rs +++ b/tests/bdd/pool_bench_helper.rs @@ -75,6 +75,7 @@ async fn setup_internal_pool(world: &mut DoormanWorld, size: usize, _mode: Strin Duration::from_secs(10), // query_wait_timeout false, // session_mode None, // fallback_state + std::sync::Arc::new(std::collections::BTreeMap::new()), ); // Create Pool with configuration @@ -427,6 +428,7 @@ async fn setup_internal_pool_with_lifetimes( Duration::from_secs(10), false, None, + std::sync::Arc::new(std::collections::BTreeMap::new()), ); let config = PoolConfig { From c438d6f6025da425053efa17f0b4df5fc6e113a6 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 09:28:04 +0300 Subject: [PATCH 32/72] feat(metrics): count dedicated_mode startup_parameters drops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DevOps P1-6: when an auth_query pool runs in dedicated mode and the cached credential row carries per-user startup_parameters, the entry is silently dropped because one shared backend cannot honour a per-user GUC. The warn-log fired once per (pool, username), then nothing — operators had no way to size the gap between configured per-user GUCs and what pg_doorman actually applies. Now every dropped entry contributes to pg_doorman_startup_parameters_dropped_total{pool, reason="dedicated_mode"}, incremented by the number of keys that just got cleared. The warn-log stays once-per-(pool, username) so it does not flood; the counter gives the operator the volume signal the log no longer carries. Doc comment on the counter lists the new reason value alongside the four existing ones (cascade_budget_exceeded, packet_cap_exceeded, auth_query_oversize, auth_query_invalid_entry). --- src/auth/auth_query.rs | 7 +++++++ src/web/metrics/mod.rs | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index f96430985..04317d4e1 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -638,6 +638,13 @@ impl AuthQueryCache { if !self.is_dedicated || entry.startup_parameters.is_empty() { return; } + // Every dropped entry contributes to the metric so operators can + // see the volume of per-user GUCs lost to dedicated mode without + // log scraping. The warn-log itself stays once per (pool, user) + // to keep the log readable. + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[self.pool_name.as_str(), "dedicated_mode"]) + .inc_by(entry.startup_parameters.len() as u64); if self .dedicated_warnings .insert(username.to_string(), ()) diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index 2ba894db0..b7e7f5ca1 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -504,6 +504,10 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// * `auth_query_invalid_entry` — an individual entry in the /// auth_query JSON failed validation (reserved key, bad GUC name, /// null byte, non-string value). Incremented per offending entry. +/// * `dedicated_mode` — a per-user auth_query entry carried +/// startup_parameters, but the pool runs in dedicated auth_query +/// mode (one shared backend across users) so the per-user overlay +/// was dropped. Incremented per dropped entry. /// /// All four cases also emit a `warn!` log line for human triage; the /// counter exists so dashboards and alerts can spot the silent drop @@ -516,7 +520,7 @@ pub(crate) static STARTUP_PARAMETERS_DROPPED_TOTAL: Lazy = Lazy:: entries pg_doorman dropped before sending StartupMessage. \ Labels: pool, reason (cascade_budget_exceeded, \ packet_cap_exceeded, auth_query_oversize, \ - auth_query_invalid_entry). Distinct from \ + auth_query_invalid_entry, dedicated_mode). Distinct from \ pg_doorman_backend_startup_parameter_errors_total which \ counts PG-side rejections after StartupMessage.", ), From 1a88da2d031f4d204428df2311bc5ec5467c5d22 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 09:30:12 +0300 Subject: [PATCH 33/72] feat(grafana): startup_parameters row on the dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DevOps P1-3 from the multi-discipline review. The dashboard now has a dedicated "Startup Parameters" collapsed row with three panels: * PG-Side Rejections by SQLSTATE — per-pool rate of backend startups PG rejected because of an operator-supplied parameter, split by SQLSTATE (22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param). * PG-Side Rejections by Pool — same counter aggregated by pool so the operator can spot the affected user@database without filtering. * Pre-Wire Drops by Reason — drops that PG never sees because pg_doorman dropped the entries before sending StartupMessage (cascade_budget_exceeded, packet_cap_exceeded, auth_query_oversize, auth_query_invalid_entry, dedicated_mode). Panel descriptions cover where to look next (warn log for the parameter name, SHOW STARTUP_PARAMETERS for the configured cascade), so a tile alert is enough to drive the operator to a runbook. --- grafana/generate_dashboard.py | 38 +++++++++ grafana/pg_doorman.json | 151 ++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) diff --git a/grafana/generate_dashboard.py b/grafana/generate_dashboard.py index 1f5a41c92..53d6ad878 100644 --- a/grafana/generate_dashboard.py +++ b/grafana/generate_dashboard.py @@ -722,6 +722,39 @@ def expanded_row(title: str): desc="Backend connections completing each phase per second. tcp_connect rate equals total backend creates; gaps to tls/auth/startup mark drop-offs at each step.", ) +# --------------------------------------------------------------------------- +# Row 19: Startup Parameters (collapsed) — PG-rejected and pre-wire-dropped GUCs +# --------------------------------------------------------------------------- +row19 = collapsed_row("Startup Parameters") + +p_sp_errors_by_sqlstate = ts_panel( + "PG-Side Rejections by SQLSTATE", [ + prom( + f'sum by (sqlstate) (rate(pg_doorman_backend_startup_parameter_errors_total{{{S}}}[$__rate_interval]))', + "{{sqlstate}}", + ), + ], unit="ops", w=12, + desc="Per-pool rate of backend startups PG rejected because of an operator-supplied parameter. Split by SQLSTATE: 22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param. Non-zero for the same pool over a few minutes means every connect through that pool fails on the same operator GUC — fix general/pool/auth_query.", +) +p_sp_errors_by_pool = ts_panel( + "PG-Side Rejections by Pool", [ + prom( + f'sum by (pool) (rate(pg_doorman_backend_startup_parameter_errors_total{{{S}}}[$__rate_interval]))', + "{{pool}}", + ), + ], unit="ops", w=12, + desc="Same counter aggregated by pool. The pool name shows which user@database is affected; check pg_doorman warn log for the parameter name and username.", +) +p_sp_dropped_by_reason = ts_panel( + "Pre-Wire Drops by Reason", [ + prom( + f'sum by (reason) (rate(pg_doorman_startup_parameters_dropped_total{{{S}}}[$__rate_interval]))', + "{{reason}}", + ), + ], unit="ops", w=24, + desc="Operator-supplied entries pg_doorman dropped BEFORE the StartupMessage went on the wire — the failure mode the PG-side counter above cannot see. Reasons: cascade_budget_exceeded (merged map past 9 488 bytes), packet_cap_exceeded (full packet past PG MAX_STARTUP_PACKET_LENGTH 10 000 bytes), auth_query_oversize (per-user JSON column past operator budget), auth_query_invalid_entry (one JSON entry failed validation), dedicated_mode (per-user GUC ignored because the pool shares one backend across users). Non-zero on any reason needs operator attention — backends are connecting with PG defaults instead of the configured cascade.", +) + # --------------------------------------------------------------------------- # Build dashboard # --------------------------------------------------------------------------- @@ -831,6 +864,11 @@ def expanded_row(title: str): .with_panel(p_backend_phase_p99) .with_panel(p_backend_phase_p50) .with_panel(p_backend_phase_rate) + # Row 19: Startup Parameters + .with_row(row19) + .with_panel(p_sp_errors_by_sqlstate) + .with_panel(p_sp_errors_by_pool) + .with_panel(p_sp_dropped_by_reason) ) dashboard_obj = d.build() diff --git a/grafana/pg_doorman.json b/grafana/pg_doorman.json index 5cf4ce850..18ab7d11a 100644 --- a/grafana/pg_doorman.json +++ b/grafana/pg_doorman.json @@ -3038,6 +3038,157 @@ "y": 182 }, "repeatDirection": "h" + }, + { + "type": "row", + "collapsed": true, + "id": 0, + "panels": [], + "title": "Startup Parameters", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 190 + } + }, + { + "type": "timeseries", + "transparent": false, + "transformations": [], + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": [ + "min", + "max", + "lastNotNull" + ] + }, + "tooltip": { + "mode": "single", + "sort": "asc" + } + }, + "fieldConfig": { + "defaults": { + "unit": "ops" + }, + "overrides": [] + }, + "targets": [ + { + "expr": "sum by (sqlstate) (rate(pg_doorman_backend_startup_parameter_errors_total{instance=~\"$instance\", user=~\"$user\", database=~\"$database\"}[$__rate_interval]))", + "refId": "", + "legendFormat": "{{sqlstate}}" + } + ], + "title": "PG-Side Rejections by SQLSTATE", + "description": "Per-pool rate of backend startups PG rejected because of an operator-supplied parameter. Split by SQLSTATE: 22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param. Non-zero for the same pool over a few minutes means every connect through that pool fails on the same operator GUC \u2014 fix general/pool/auth_query.", + "datasource": { + "uid": "prometheus" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 191 + }, + "repeatDirection": "h" + }, + { + "type": "timeseries", + "transparent": false, + "transformations": [], + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": [ + "min", + "max", + "lastNotNull" + ] + }, + "tooltip": { + "mode": "single", + "sort": "asc" + } + }, + "fieldConfig": { + "defaults": { + "unit": "ops" + }, + "overrides": [] + }, + "targets": [ + { + "expr": "sum by (pool) (rate(pg_doorman_backend_startup_parameter_errors_total{instance=~\"$instance\", user=~\"$user\", database=~\"$database\"}[$__rate_interval]))", + "refId": "", + "legendFormat": "{{pool}}" + } + ], + "title": "PG-Side Rejections by Pool", + "description": "Same counter aggregated by pool. The pool name shows which user@database is affected; check pg_doorman warn log for the parameter name and username.", + "datasource": { + "uid": "prometheus" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 191 + }, + "repeatDirection": "h" + }, + { + "type": "timeseries", + "transparent": false, + "transformations": [], + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": [ + "min", + "max", + "lastNotNull" + ] + }, + "tooltip": { + "mode": "single", + "sort": "asc" + } + }, + "fieldConfig": { + "defaults": { + "unit": "ops" + }, + "overrides": [] + }, + "targets": [ + { + "expr": "sum by (reason) (rate(pg_doorman_startup_parameters_dropped_total{instance=~\"$instance\", user=~\"$user\", database=~\"$database\"}[$__rate_interval]))", + "refId": "", + "legendFormat": "{{reason}}" + } + ], + "title": "Pre-Wire Drops by Reason", + "description": "Operator-supplied entries pg_doorman dropped BEFORE the StartupMessage went on the wire \u2014 the failure mode the PG-side counter above cannot see. Reasons: cascade_budget_exceeded (merged map past 9 488 bytes), packet_cap_exceeded (full packet past PG MAX_STARTUP_PACKET_LENGTH 10 000 bytes), auth_query_oversize (per-user JSON column past operator budget), auth_query_invalid_entry (one JSON entry failed validation), dedicated_mode (per-user GUC ignored because the pool shares one backend across users). Non-zero on any reason needs operator attention \u2014 backends are connecting with PG defaults instead of the configured cascade.", + "datasource": { + "uid": "prometheus" + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 199 + }, + "repeatDirection": "h" } ] } From 42894c7a27351a45662ae706859fce82cfe9e3de Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 10:03:31 +0300 Subject: [PATCH 34/72] fix(pool): pass base_startup_parameters to retain test helper Library Tests on 1a88da2 failed to compile because b70e313 added a 17th argument to ServerPool::new but missed this call site in the src/pool/retain.rs test. Mirror the same Arc::new(BTreeMap::new()) that already covers pool/inner.rs and tests/bdd/pool_bench_helper.rs. --- src/pool/retain.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pool/retain.rs b/src/pool/retain.rs index 3cf77863a..4a4811899 100644 --- a/src/pool/retain.rs +++ b/src/pool/retain.rs @@ -322,6 +322,7 @@ mod tests { Duration::from_secs(5), false, None, + Arc::new(std::collections::BTreeMap::new()), ); let database = Pool::builder(server_pool) .pool_name("test_db".to_string()) From 2cb0944e4d3407c0679f95767163245c55ddbabf Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 10:05:47 +0300 Subject: [PATCH 35/72] ci(grafana): allow empty Startup Parameters panels in demo smoke Three new panels from row 19 (PG-Side Rejections by SQLSTATE, by Pool, and Pre-Wire Drops by Reason) all returned empty vectors on the demo stack because grafana/demo ships no operator startup_parameters, so neither *_errors_total nor *_dropped_total ever increments. Whitelist them with the same per-entry justification pattern as the existing allow_empty entries (Patroni-not-configured, Streaming-not-exercised). --- scripts/dashboard-smoke.expected.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/dashboard-smoke.expected.yaml b/scripts/dashboard-smoke.expected.yaml index 88eca5a2f..1130e9fe0 100644 --- a/scripts/dashboard-smoke.expected.yaml +++ b/scripts/dashboard-smoke.expected.yaml @@ -69,6 +69,13 @@ allow_empty: # are zero on a -M simple workload so the ratio expression returns # an empty vector. - "Prepared Statement Hit Ratio" + # Startup_parameters panels track operator-supplied GUCs that PG + # rejected or that pg_doorman dropped before the wire. The demo + # ships no startup_parameters config, so no backend ever sees an + # operator GUC and no rejection or drop counter ever increments. + - "PG-Side Rejections by SQLSTATE" + - "PG-Side Rejections by Pool" + - "Pre-Wire Drops by Reason" bounds: - name: pool_size_app_user From 2d8340b5d1cc9f7347646186e3b6653751f6f044 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 10:23:36 +0300 Subject: [PATCH 36/72] docs: tighten startup_parameters changelog, tutorial, and SHOW reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 3.9.0 changelog now lands on a single RELOAD model — a SIGHUP that changes startup_parameters recycles the affected pools — replacing the two paragraphs that contradicted each other on whether the pool drains or keeps idle backends with stale reset_val. Behaviour on PG-side rejection drops the per-key quarantine wording (the implementation already mirrors PG verbatim with no retry). The startup_parameters tutorial loses the "force_custom_plan as the killer use case" framing in favour of a more neutral statement_timeout/ work_mem/idle_in_transaction example, and corrects pg_settings.source from 'session' to 'client' in both locales (operator-supplied GUCs arrive via StartupMessage, not SET). SHOW STARTUP_PARAMETERS picks up a reference subsection in both locales' admin-commands documentation (user, database, parameter, value, source columns) so an operator can find the command without reading the tutorial. Russian reference docs gain the backend_startup_parameter_errors_total Prometheus row that was missing, plus a translation pass that replaces anglicisms (warning / Web UI рендерит / алерт / каст) with native equivalents. fields.yaml gets a light editorial pass on a handful of unrelated docstrings ("This is required" → "Required"). --- .../en/src/authentication/auth-query.md | 6 +- documentation/en/src/changelog.md | 76 ++++++++----------- documentation/en/src/comparison.md | 2 +- .../en/src/observability/admin-commands.md | 14 ++++ .../en/src/tutorials/startup-parameters.md | 49 ++++++------ .../ru/src/authentication/auth-query.md | 7 +- .../ru/src/observability/admin-commands.md | 14 ++++ documentation/ru/src/reference/general.md | 8 +- documentation/ru/src/reference/pool.md | 13 +++- documentation/ru/src/reference/prometheus.md | 1 + .../ru/src/tutorials/startup-parameters.md | 19 ++--- src/app/generate/docs.rs | 8 +- src/app/generate/fields.yaml | 32 ++++---- 13 files changed, 140 insertions(+), 109 deletions(-) diff --git a/documentation/en/src/authentication/auth-query.md b/documentation/en/src/authentication/auth-query.md index 80fa70175..6a64933ae 100644 --- a/documentation/en/src/authentication/auth-query.md +++ b/documentation/en/src/authentication/auth-query.md @@ -28,7 +28,11 @@ pools: cache_failure_ttl: "30s" ``` -The query must return a column named `passwd` or `password` containing the MD5 or SCRAM hash. Extra columns are ignored except for optional `startup_parameters`: passthrough mode reads it as a `text` JSON object with per-user PostgreSQL startup parameters, while dedicated mode ignores it and logs a warning. +The query must return a column named `passwd` or `password` containing +the MD5 or SCRAM hash. Extra columns are ignored except for +`startup_parameters`. In passthrough mode, pg_doorman reads that column +as a `text` JSON object with per-user PostgreSQL startup parameters. +Dedicated mode ignores the column and logs a warning. `user` and `password` are the credentials PgDoorman uses to run the lookup query. They must have permission to read the credential column. Either grant access to a custom view (recommended) or use a user in `pg_read_server_files` group. diff --git a/documentation/en/src/changelog.md b/documentation/en/src/changelog.md index 55d76fefe..3e3717298 100644 --- a/documentation/en/src/changelog.md +++ b/documentation/en/src/changelog.md @@ -2,35 +2,28 @@ ### 3.9.0 -Per-pool PostgreSQL configuration parameters (GUCs) in backend -`StartupMessage`. The map cascades over three -levels — `general.startup_parameters`, per-pool overrides, and an -optional `auth_query` JSON column for per-user values in passthrough -mode — and the resulting values are written to `pg_settings.reset_val`, -so they survive client-side `RESET ALL` and `DISCARD ALL`. Among -mainstream poolers, the full cascade/reset contract is specific to -pg_doorman: PgBouncer can track or ignore selected client startup -parameters, Odyssey's `maintain_params` preserves client-side -parameters across rebind, and PgCat exposes no equivalent. - -The most common use case is forcing `plan_cache_mode = "force_custom_plan"` -on a hot OLTP pool affected by a sticky generic plan. The same mechanism -pins `statement_timeout`, `work_mem`, -`idle_in_transaction_session_timeout`, or any other GUC that a single -application needs without touching `postgresql.conf`, `ALTER ROLE`, or -`ALTER DATABASE`. +Per-pool PostgreSQL startup parameters. pg_doorman can now add +operator-defined GUCs to each backend `StartupMessage`. Values merge in +three layers: `general.startup_parameters`, `pools..startup_parameters`, +and the optional `startup_parameters` column returned by passthrough +`auth_query`. + +PostgreSQL stores these values as the session reset defaults, so +client-side `RESET ALL` and `DISCARD ALL` return to the operator value. +This gives one pool a different `plan_cache_mode`, `statement_timeout`, +`work_mem`, or `idle_in_transaction_session_timeout` without changing +`postgresql.conf`, `ALTER ROLE`, or `ALTER DATABASE`. #### Cascade resolution - `general.startup_parameters`, `pools..startup_parameters`, and the optional `startup_parameters` text column on an `auth_query` row - merge per key, with the more specific level winning. Auth_query in - dedicated mode (a shared `server_user`) intentionally ignores the - per-user column and logs a one-time warning per pool and username. -- The merged cascade is resolved lazily on every backend spawn from - the live config snapshot, so a `RELOAD` that only changes - `general.startup_parameters` takes effect on the next backend - without recycling the pool. + merge per key. The later layer wins. +- Dedicated `auth_query` mode uses a shared `server_user`, so + pg_doorman ignores the per-user column there and logs one warning per + pool and username. +- A reload that changes startup parameters recycles the affected pools. + Idle backends with the old reset defaults are not reused. #### Validation and protocol safety @@ -39,21 +32,19 @@ application needs without touching `postgresql.conf`, `ALTER ROLE`, or - Keys must match the PG GUC naming shape `[A-Za-z_][A-Za-z0-9_.]*`, values must not contain null bytes, and each level fits the operator budget of `MAX_STARTUP_PACKET_LENGTH - 512` bytes. -- The full cascade is rechecked at every spawn against PG's 10 000-byte +- The full cascade is checked before each backend startup against PG's 10 000-byte `MAX_STARTUP_PACKET_LENGTH`; if the union would overflow, all operator-supplied keys are dropped for that spawn and the event is - logged, so the connection still completes with PG defaults instead - of failing every client request. + logged. That backend starts with PostgreSQL defaults for those keys. #### Behaviour on PG-side rejection - If PostgreSQL rejects an operator-supplied startup parameter at backend startup, pg_doorman forwards the `ErrorResponse` to the - client unchanged. The client receives the same sqlstate and message - it would have seen connecting to PG directly — no silent retry, no - per-key quarantine, no buffering layer. Operator fixes the - parameter in the config; until then every connection through that - pool fails the same way. + client unchanged. There is no retry with the key removed and no + per-key quarantine. Fix the parameter in the config; until then, + backend startup for that pool fails with PostgreSQL's own SQLSTATE and + message. - SQLSTATE class `57P` (server unavailable) keeps mapping to `ServerUnavailableError` first so the Patroni-assisted fallback path can route around the failed node before the startup-parameter @@ -67,26 +58,21 @@ application needs without touching `postgresql.conf`, `ALTER ROLE`, or #### RELOAD coherence -- A SIGHUP that changes `general.startup_parameters` now drains every - pool that depends on the baseline: the effective per-pool config - hash folds in the baseline, and dynamic `auth_query` pools that - carry over the reload are also recycled. Idle backends with the - previous `reset_val` no longer stay around to serve `RESET ALL` / - `DISCARD ALL` with stale operator defaults. +- A SIGHUP that changes `general.startup_parameters` drains pools that + inherit that baseline. The per-pool config hash includes the general + startup map, and carried-over dynamic `auth_query` pools are recycled + when the baseline changes. #### Observability - `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}` counts every backend startup rejected by PostgreSQL because of an operator-supplied parameter. The failing parameter name and - username are on the corresponding warn log line; they are - deliberately kept out of the labels so a dynamic `auth_query` pool - that mints many roles cannot blow up the series count. + username are written to the warning log line, not to metric labels. - `SHOW STARTUP_PARAMETERS` (admin SQL console) lists the per-pool - effective merged cascade with the layer that contributed each - value. `psql` tab completion on `SHOW ` now offers the new - command. -- The Web UI pool detail page renders the same view in a + effective cascade with the layer that supplied each value. `psql` tab + completion on `SHOW ` now includes the command. +- The Web UI pool detail page shows the same data in a "Startup parameters (operator-injected)" section, driven by the new `startup_parameters[]` field on `/api/pools`. diff --git a/documentation/en/src/comparison.md b/documentation/en/src/comparison.md index 793fc3f15..7d5baf096 100644 --- a/documentation/en/src/comparison.md +++ b/documentation/en/src/comparison.md @@ -77,7 +77,7 @@ See [Patroni-assisted fallback](tutorials/patroni-assisted-fallback.md), [`patro | LISTEN / NOTIFY pinning in transaction mode | No | No | Experimental | | Cross-rule connection cap (`shared_pool`) | No | No | Yes (since 1.5.1) | | `PAUSE` / `RESUME` / `RECONNECT` admin commands | Yes | Yes | Yes (since 1.4.1) | -| Operator-defined PostgreSQL GUCs in backend `StartupMessage` per pool | Yes (`startup_parameters`, three-level cascade `general` → pool → `auth_query` passthrough; survives client `RESET ALL` / `DISCARD ALL`; if PG rejects a parameter the `ErrorResponse` is forwarded to the client unchanged — no hidden retry, no per-key quarantine) | No equivalent cascade/reset contract; selected client startup parameters can be tracked or ignored, but not set by the operator per pool | No (`maintain_params` preserves client-side parameters across rebind; no operator-defined GUCs) | +| Operator-defined PostgreSQL GUCs in backend `StartupMessage` per pool | Yes (`startup_parameters`, three-level cascade `general` → pool → `auth_query` passthrough; values survive client `RESET ALL` / `DISCARD ALL`; PG startup errors are forwarded unchanged) | No equivalent operator-defined cascade; selected client startup parameters can be tracked or ignored | No (`maintain_params` preserves client-side parameters across rebind; no operator-defined GUCs) | See [Pool Coordinator](concepts/pool-coordinator.md), [Pool pressure](tutorials/pool-pressure.md). diff --git a/documentation/en/src/observability/admin-commands.md b/documentation/en/src/observability/admin-commands.md index bf374e050..353773cd0 100644 --- a/documentation/en/src/observability/admin-commands.md +++ b/documentation/en/src/observability/admin-commands.md @@ -34,6 +34,7 @@ Admin commands are read with `SHOW ` or executed with bare verbs (`P | `SHOW LISTS` | Counts by category (databases, users, pools, clients, servers). | | `SHOW USERS` | List of users and their pool modes. | | `SHOW AUTH_QUERY` | `auth_query` cache hit/miss/refetch rates, auth success/failure, executor errors, dynamic pool counts. | +| `SHOW STARTUP_PARAMETERS` | Effective `startup_parameters` cascade per pool: parameter, value, and contributing layer. | | `SHOW SOCKETS` | TCP and Unix socket counts by state (Linux only — reads `/proc/net/`). | | `SHOW LOG_LEVEL` | Current log level. | | `SHOW VERSION` | PgDoorman version. | @@ -68,6 +69,19 @@ mydb | app | 12 | 4 | 0 | 4 | 36 | 0 - `sv_idle` matches free backends; `sv_active` is in-use; `sv_used` is reserved by the coordinator (see below). - `maxwait` is the longest current wait in seconds. If it grows beyond `query_wait_timeout`, clients get errors. +### `SHOW STARTUP_PARAMETERS` + +``` +user | database | parameter | value | source +app | mydb | statement_timeout | 5s | general +app | mydb | plan_cache_mode | force_custom_plan | pool +``` + +- `source` shows the layer that supplied the winning value: `general`, + `pool`, or `auth_query`. +- The command reports the same effective cascade used for new backend + `StartupMessage` packets. + ### `SHOW POOL_COORDINATOR` ``` diff --git a/documentation/en/src/tutorials/startup-parameters.md b/documentation/en/src/tutorials/startup-parameters.md index db419710c..90c253b99 100644 --- a/documentation/en/src/tutorials/startup-parameters.md +++ b/documentation/en/src/tutorials/startup-parameters.md @@ -1,13 +1,13 @@ # PostgreSQL startup parameters -Some operators need a few PostgreSQL configuration parameters to apply -to every backend pg_doorman opens, without touching `postgresql.conf`, -`ALTER ROLE`, or `ALTER DATABASE`. Three cases recur in practice: +Use `startup_parameters` when a pool needs PostgreSQL GUC defaults that +should be set at backend startup, without changing `postgresql.conf`, +`ALTER ROLE`, or `ALTER DATABASE`. - A hot OLTP pool is affected by a sticky generic plan after the `plan_cache_mode = auto` heuristic flips. Switching the whole role to `force_custom_plan` would affect every workload using that role; - scoping the change to one pool is what you want. + setting it on one pool limits the blast radius. - An application that does not set its own `statement_timeout` or `idle_in_transaction_session_timeout` and cannot be patched fast enough. The DBA needs a server-side default that survives the @@ -16,8 +16,6 @@ to every backend pg_doorman opens, without touching `postgresql.conf`, `application_name` regardless of what the connecting driver negotiates, so `pg_stat_activity` and audit logs stay legible. -`startup_parameters` lets pg_doorman do this from its own config. - ## Configuration The cascade has three levels; the more specific level wins per key: @@ -68,7 +66,7 @@ many users, so a per-user override cannot apply. The merged map is written into the PostgreSQL `StartupMessage` of every backend pg_doorman opens. PG records each entry as the session default for that setting (`pg_settings.reset_val` and -`pg_settings.source = 'session'`), so client-side `RESET ALL` and +`pg_settings.source = 'client'`), so client-side `RESET ALL` and `DISCARD ALL` restore the operator value rather than discarding it. Operators get a stable session default without editing `postgresql.conf` or running `ALTER ROLE`. @@ -106,8 +104,8 @@ At every backend spawn pg_doorman re-checks the merged cascade against the same cap. Two levels that fit individually can together push past it once `auth_query` adds a third layer; when that happens pg_doorman drops every operator-supplied key for that one spawn, -logs the byte counts, and lets the backend connect with PG's own -defaults rather than failing every connection attempt. +logs the byte counts, and starts the backend with PostgreSQL defaults +for those keys. ## What happens when PG rejects a parameter @@ -117,11 +115,10 @@ unchanged. The client sees the same sqlstate (`22023`, `42704`, `42501`, `55P02`, or any other code under the startup family) and the same message it would have seen connecting to PG directly. -pg_doorman does not retry the connection without the parameter, does -not silently strip the key, and does not keep a per-pool quarantine. -The next client connect runs the same `StartupMessage` and either -succeeds or fails the same way — fixing the parameter is on the -operator, not on the pooler. +pg_doorman does not retry with the parameter removed and does not keep a +per-pool quarantine. The next client connection sends the same +`StartupMessage`; fix the config before routing traffic back to that +pool. ## Observability @@ -135,29 +132,26 @@ admin> SHOW STARTUP_PARAMETERS; shop | reports | statement_timeout| 10s | general ``` -The Web UI's pool detail page renders the same view in the "Startup +The Web UI's pool detail page shows the same view in the "Startup parameters (operator-injected)" section. -On the Prometheus surface: +Prometheus exports one counter for PG-side rejections: - `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}` counts every backend startup PostgreSQL rejected because of an operator-supplied parameter. The failing parameter name and - username are on the corresponding warn log line; they are kept out - of the labels so dynamic `auth_query` pools cannot blow up the - series count. + username are written to the warning log line, not to metric labels. -A reasonable starting alert is "non-zero -`pg_doorman_backend_startup_parameter_errors_total` rate for the -same pool over a few minutes" — that means every client connect to -that pool is failing on the same operator GUC and the config needs -to be fixed. +Alert on a non-zero +`pg_doorman_backend_startup_parameter_errors_total` rate for the same +pool over several minutes. That usually means new backend startups for +the pool are failing on the same operator GUC. ## When not to use this - The application already sets the parameter on every connection. - Putting the same value in `startup_parameters` adds a bookkeeping - surface for no behavioural change. + Duplicating the value in `startup_parameters` adds another config path + without changing behavior. - Per-transaction tuning (`SET LOCAL`). `startup_parameters` is for session defaults; transaction-scoped tuning belongs in the application. @@ -173,5 +167,6 @@ to be fixed. `pools..startup_parameters`. - [auth_query](../authentication/auth-query.md): passthrough vs dedicated modes, where the `startup_parameters` column is read. -- [Admin Commands](../observability/admin-commands.md): `SHOW POOLS`. +- [Admin Commands](../observability/admin-commands.md): + `SHOW STARTUP_PARAMETERS`. - [Prometheus](../reference/prometheus.md): full metric list. diff --git a/documentation/ru/src/authentication/auth-query.md b/documentation/ru/src/authentication/auth-query.md index f3f3e1aaf..338d112c1 100644 --- a/documentation/ru/src/authentication/auth-query.md +++ b/documentation/ru/src/authentication/auth-query.md @@ -28,7 +28,12 @@ pools: cache_failure_ttl: "30s" ``` -Запрос должен возвращать колонку с именем `passwd` или `password`, содержащую хеш MD5 или SCRAM. Дополнительные колонки игнорируются, кроме необязательной `startup_parameters`: passthrough-режим читает её как JSON-объект в `text` с пользовательскими параметрами запуска PostgreSQL, а dedicated-режим игнорирует её и пишет warning. +Запрос должен возвращать колонку с именем `passwd` или `password`, +содержащую хеш MD5 или SCRAM. Дополнительные колонки игнорируются, кроме +необязательной `startup_parameters`. В passthrough-режиме pg_doorman +читает её как JSON-объект в `text` с пользовательскими параметрами +запуска PostgreSQL. Dedicated-режим игнорирует её и пишет +предупреждение. `user` и `password` — это учётные данные, под которыми pg_doorman выполняет lookup-запрос. У них должно быть право читать колонку с учётными данными. Либо выдайте доступ к специально созданному представлению (рекомендуется), либо используйте пользователя из группы `pg_read_server_files`. diff --git a/documentation/ru/src/observability/admin-commands.md b/documentation/ru/src/observability/admin-commands.md index 6269abb1e..d427ed999 100644 --- a/documentation/ru/src/observability/admin-commands.md +++ b/documentation/ru/src/observability/admin-commands.md @@ -34,6 +34,7 @@ psql "host=127.0.0.1 port=6432 user=admin dbname=pgdoorman" | `SHOW LISTS` | Счётчики по категориям (databases, users, pools, clients, servers). | | `SHOW USERS` | Список пользователей и их режимы пула. | | `SHOW AUTH_QUERY` | Кэш `auth_query`: попадания/промахи/перезапросы, успехи/отказы аутентификации, ошибки исполнителя, счётчики динамических пулов. | +| `SHOW STARTUP_PARAMETERS` | Эффективный каскад `startup_parameters` по каждому пулу: параметр, значение и уровень, который дал итоговое значение. | | `SHOW SOCKETS` | Счётчики TCP- и Unix-сокетов по состоянию (только Linux — читает `/proc/net/`). | | `SHOW LOG_LEVEL` | Текущий уровень логирования. | | `SHOW VERSION` | Версия pg_doorman. | @@ -68,6 +69,19 @@ mydb | app | 12 | 4 | 0 | 4 | 36 | 0 - `sv_idle` соответствует свободным серверным соединениям; `sv_active` — занятым; `sv_used` — зарезервированным координатором (см. ниже). - `maxwait` — самое долгое текущее ожидание в секундах. Если оно вырастает за `query_wait_timeout`, клиенты получают ошибки. +### `SHOW STARTUP_PARAMETERS` + +``` +user | database | parameter | value | source +app | mydb | statement_timeout | 5s | general +app | mydb | plan_cache_mode | force_custom_plan | pool +``` + +- `source` показывает уровень, который дал итоговое значение: + `general`, `pool` или `auth_query`. +- Команда выводит тот же эффективный каскад, который используется при + сборке `StartupMessage` для новых бэкендов. + ### `SHOW POOL_COORDINATOR` ``` diff --git a/documentation/ru/src/reference/general.md b/documentation/ru/src/reference/general.md index 68d038a9e..36209204e 100644 --- a/documentation/ru/src/reference/general.md +++ b/documentation/ru/src/reference/general.md @@ -686,16 +686,16 @@ hostnossl all all 192.168.1.0/24 trust запуском бэкенда слитый каскад снова проверяется против `MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается, pg_doorman пропускает параметры, заданные оператором, для этого запуска и пишет -warning. +предупреждение. Если PostgreSQL отвергает заданный оператором параметр на старте бэкенда, pg_doorman пробрасывает `ErrorResponse` PG клиенту без изменений: никакой повторной попытки без этого ключа, никакого скрытого карантина. Кумулятивный счётчик отказов экспонируется как `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}`; -имя параметра и пользователя — в warn-строке лога. Эффективный -каскад per pool виден через `SHOW STARTUP_PARAMETERS` в admin -SQL-консоли и через `/api/pools` в Web UI. +имя параметра и пользователя — в строке лога уровня warn. Эффективный +каскад по каждому пулу виден через `SHOW STARTUP_PARAMETERS` в +административной SQL-консоли и через `/api/pools` в веб-интерфейсе. По умолчанию: `{}`. diff --git a/documentation/ru/src/reference/pool.md b/documentation/ru/src/reference/pool.md index fea940fa7..78c2cf8af 100644 --- a/documentation/ru/src/reference/pool.md +++ b/documentation/ru/src/reference/pool.md @@ -145,7 +145,7 @@ каждым запуском бэкенда слитый каскад снова проверяется против `MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается, pg_doorman пропускает параметры, заданные оператором, для этого запуска и пишет -warning. +предупреждение. По умолчанию: `{}`. @@ -203,7 +203,16 @@ auth_query: ### query -SQL-запрос для получения учётных данных. Должен возвращать колонку с именем `passwd` или `password`, содержащую MD5- или SCRAM-хеш. Если запрос возвращает ровно одну колонку, она используется независимо от имени. Дополнительные колонки игнорируются, кроме необязательной `startup_parameters` типа `text`: passthrough-режим читает её как JSON-объект с пользовательскими параметрами запуска PostgreSQL. Dedicated-режим игнорирует эту колонку и пишет warning. В качестве плейсхолдера для имени пользователя используйте `$1`. +SQL-запрос для получения учётных данных. Он должен возвращать колонку с +именем `passwd` или `password`, содержащую MD5- или SCRAM-хеш. Если +запрос возвращает ровно одну колонку, pg_doorman использует её независимо +от имени. + +Дополнительные колонки игнорируются, кроме необязательной +`startup_parameters` типа `text`. В passthrough-режиме pg_doorman читает +её как JSON-объект с пользовательскими параметрами запуска PostgreSQL. +Dedicated-режим игнорирует эту колонку и пишет предупреждение. В +качестве плейсхолдера для имени пользователя используйте `$1`. Пример: `"SELECT passwd FROM pg_shadow WHERE usename = $1"` diff --git a/documentation/ru/src/reference/prometheus.md b/documentation/ru/src/reference/prometheus.md index 6fc0adc2f..c2832b0f0 100644 --- a/documentation/ru/src/reference/prometheus.md +++ b/documentation/ru/src/reference/prometheus.md @@ -66,6 +66,7 @@ pg_doorman экспортирует следующие метрики: | `pg_doorman_pools_bytes_total` | Накопительный счётчик байт, переданных через пулы соединений, по направлению (`received`/`sent`), пользователю и базе. Для пропускной способности используйте `rate(pg_doorman_pools_bytes_total[5m])`. | | `pg_doorman_pools_bytes` | Устаревшая gauge-версия `pg_doorman_pools_bytes_total`; будет удалена в 3.10. | | `pg_doorman_pool_size` | Сконфигурированный максимальный размер пула на пользователя и базу. Полезен для расчёта оставшейся ёмкости пула вместе с pg_doorman_pools_servers. | +| `pg_doorman_backend_startup_parameter_errors_total` | Накопительный счётчик отказов при запуске бэкенда из-за `startup_parameters`, заданных оператором. Лейблы: пул и SQLSTATE. Отклонённый параметр и имя пользователя пишутся в строку лога уровня warn, а не в лейблы метрики. | ### Метрики запросов и транзакций diff --git a/documentation/ru/src/tutorials/startup-parameters.md b/documentation/ru/src/tutorials/startup-parameters.md index 50fdecb5f..11c5d50fc 100644 --- a/documentation/ru/src/tutorials/startup-parameters.md +++ b/documentation/ru/src/tutorials/startup-parameters.md @@ -57,9 +57,9 @@ WHERE rolname = $1; ``` Колонка должна возвращаться как `text`. Если SQL отдаёт `json` или -`jsonb`, добавьте явный каст `::text`. pg_doorman читает её именно как -`text` и один раз пишет предупреждение для каждого пользователя, у -которого тип не совпал. +`jsonb`, добавьте явное приведение типа `::text`. pg_doorman читает её +именно как `text` и один раз пишет предупреждение для каждого +пользователя, у которого тип не совпал. Dedicated-режим `auth_query`, когда `server_user` задан, игнорирует эту колонку и один раз пишет предупреждение на пару `(пул, пользователь)`. @@ -70,7 +70,7 @@ Dedicated-режим `auth_query`, когда `server_user` задан, игно Слитая карта записывается в `StartupMessage` каждого бэкенда, который открывает pg_doorman. PostgreSQL запоминает эти значения как сессионные -дефолты (`pg_settings.reset_val` и `pg_settings.source = 'session'`). +дефолты (`pg_settings.reset_val` и `pg_settings.source = 'client'`). Поэтому клиентские `RESET ALL` и `DISCARD ALL` возвращают именно значение, заданное оператором, а не исходное значение PostgreSQL. @@ -127,7 +127,8 @@ pg_doorman не пытается переподключиться без это ## Наблюдаемость -Эффективный каскад per pool виден через admin SQL-консоль: +Эффективный каскад по каждому пулу виден через административную +SQL-консоль: ```text admin> SHOW STARTUP_PARAMETERS; @@ -137,7 +138,7 @@ admin> SHOW STARTUP_PARAMETERS; shop | reports | statement_timeout| 10s | general ``` -Web UI рендерит тот же набор в секции «Startup parameters +Веб-интерфейс показывает тот же набор в секции «Startup parameters (operator-injected)» на странице пула. В Prometheus: @@ -148,7 +149,7 @@ Web UI рендерит тот же набор в секции «Startup paramet пишутся в warn-строке лога; в лейблы они не включены, чтобы динамические `auth_query`-пулы не раздували количество серий. -Разумная отправная точка для алерта: ненулевая скорость роста +Разумная отправная точка для оповещения: ненулевая скорость роста `pg_doorman_backend_startup_parameter_errors_total` для одного и того же пула в течение нескольких минут означает, что каждое подключение к пулу падает на одном и том же GUC и конфигурацию нужно править. @@ -156,7 +157,7 @@ Web UI рендерит тот же набор в секции «Startup paramet ## Когда это не нужно - Приложение само задаёт параметр на каждом подключении. Дублирование в - `startup_parameters` добавляет поверхность сопровождения без изменения + `startup_parameters` добавляет ещё одну настройку без изменения поведения. - Тюнинг на одну транзакцию (`SET LOCAL`). `startup_parameters` задают сессионные дефолты; параметры уровня транзакции должно выставлять @@ -173,5 +174,5 @@ Web UI рендерит тот же набор в секции «Startup paramet - [auth_query](../authentication/auth-query.md): passthrough- и dedicated-режимы, чтение колонки `startup_parameters`. - [Команды администратора](../observability/admin-commands.md): - `SHOW POOLS`. + `SHOW STARTUP_PARAMETERS`. - [Метрики Prometheus](../reference/prometheus.md): полный список. diff --git a/src/app/generate/docs.rs b/src/app/generate/docs.rs index 6a987bf1b..23b8d622c 100644 --- a/src/app/generate/docs.rs +++ b/src/app/generate/docs.rs @@ -307,7 +307,7 @@ fn write_auth_query_section(out: &mut String) { let _ = writeln!(out, "The `auth_query` section enables dynamic user authentication by querying a PostgreSQL database for credentials at connection time. This allows pg_doorman to authenticate users without listing them statically in the configuration file.\n"); let _ = writeln!(out, "```yaml\npools:\n mydb:\n auth_query:\n query: \"SELECT passwd FROM pg_shadow WHERE usename = $1\"\n user: \"doorman_auth\"\n password: \"auth_password\"\n```\n"); let _ = writeln!(out, "There are two modes of operation:\n"); - let _ = writeln!(out, "- **Dedicated mode** (`server_user` is set): All dynamically authenticated users share a single connection pool that connects to PostgreSQL as `server_user`. This is the simplest setup and works well when all users need the same backend access."); + let _ = writeln!(out, "- **Dedicated mode** (`server_user` is set): all dynamically authenticated users share one backend pool that connects to PostgreSQL as `server_user`. Use it when backend identity does not need to match the client user."); let _ = writeln!(out, "- **Passthrough mode** (`server_user` is not set): Each dynamically authenticated user gets their own connection pool that connects to PostgreSQL using their own credentials (MD5 pass-the-hash or SCRAM ClientKey passthrough). This preserves per-user identity on the backend.\n"); let _ = writeln!(out, "Static users (defined in the `users` section) are always checked first. The auth_query is only used when the username is not found among static users.\n"); let _ = writeln!(out, "```admonish warning title=\"Security Recommendation\""); @@ -474,12 +474,12 @@ fn write_prometheus_metrics_section(out: &mut String) { let _ = writeln!(out, "### Operator-supplied startup_parameters\n"); let _ = writeln!( out, - "Surface PG-side rejections and pre-wire drops of the operator-supplied cascade. Operators set `general.startup_parameters` / `pools..startup_parameters` or the per-user auth_query column; if PostgreSQL refuses a value (typo, wrong privilege, postmaster-only knob) the rejection lands on `pg_doorman_backend_startup_parameter_errors_total`. The companion `pg_doorman_startup_parameters_dropped_total` flags cases where pg_doorman aborted the cascade before sending it (over-budget, oversize auth_query JSON, bad entry in the JSON object) — same surface, different stage.\n" + "These metrics cover two failure points for operator-supplied startup parameters. `pg_doorman_backend_startup_parameter_errors_total` counts values that PostgreSQL rejected after pg_doorman sent the `StartupMessage`. `pg_doorman_startup_parameters_dropped_total` counts values pg_doorman dropped before sending because the merged cascade was too large or an auth_query JSON entry was invalid.\n" ); let _ = writeln!(out, "| Metric | Description |"); let _ = writeln!(out, "|--------|-------------|"); - let _ = writeln!(out, "| `pg_doorman_backend_startup_parameter_errors_total` | Counter by `(pool, sqlstate)`. Increments once per backend startup where PostgreSQL returned an `ErrorResponse` naming a key pg_doorman sent in `StartupMessage` (operator-supplied cascade). SQLSTATE class `57P` is excluded — those rejections go through `ServerUnavailableError` and the Patroni-assisted fallback path instead. The failing parameter name and username are in the corresponding warn log line; they are kept off the label set so a dynamic auth_query pool cannot inflate Prometheus series count. Identification is best-effort: pg_doorman first parses the canonical English `parameter \"\"` phrase, then falls back to scanning the M-field for any sent key wrapped in double quotes (locale-stable). If both heuristics fail, the counter does not move. |"); - let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman dropped operator-supplied entries *before* the `StartupMessage` went on the wire — the failure mode the `*_errors_total` counter cannot see because PG never had a chance to reject. `reason` values: `cascade_budget_exceeded` (merged map exceeded operator budget), `packet_cap_exceeded` (full packet would exceed PG's `MAX_STARTUP_PACKET_LENGTH`), `auth_query_oversize` (the auth_query text column exceeded the operator budget at parse time), `auth_query_invalid_entry` (one entry failed validation: reserved key, bad GUC name, null byte, non-string value). |\n"); + let _ = writeln!(out, "| `pg_doorman_backend_startup_parameter_errors_total` | Counter by `(pool, sqlstate)`. Increments when PostgreSQL rejects a backend startup and the `ErrorResponse` names a startup parameter sent by pg_doorman. SQLSTATE class `57P` is excluded because Patroni-assisted fallback handles those errors. The failing parameter name and username are written to the warning log line, not to labels. pg_doorman first parses the common `parameter \"\"` phrase, then scans the message for any sent key in double quotes. If neither lookup finds a key, the counter is not incremented. |"); + let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman drops operator-supplied entries before sending `StartupMessage`. Reasons: `cascade_budget_exceeded`, `packet_cap_exceeded`, `auth_query_oversize`, `auth_query_invalid_entry`. |\n"); // Server Metrics let _ = writeln!(out, "### Server Metrics\n"); diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index 2116ee9c9..4e1e8a2a9 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -470,8 +470,8 @@ fields: is actively sending data but the remote end has become unreachable (e.g., network failure, client crash). When set to a non-zero value, if data remains unacknowledged for this duration, the connection will - be terminated. This is particularly useful to avoid 15-16 minute delays caused by TCP retransmission - timeout when keepalive cannot help (e.g., during active data transmission). + be terminated. Use it to avoid 15-16 minute delays caused by TCP retransmission timeout when + keepalive cannot help (e.g., during active data transmission). **Note:** This option is only supported on Linux. On other operating systems, this setting is ignored. @@ -940,7 +940,7 @@ fields: ru: | Путь к файлу TLS-сертификата для входящих клиентских подключений. Должен использоваться вместе с tls_private_key. - doc: "The path to the certificate file for TLS connections. This is required to enable TLS for incoming client connections. Must be used together with `tls_private_key`." + doc: "Path to the certificate file for TLS connections. Required to enable TLS for incoming client connections. Must be used together with `tls_private_key`." default: "None" tls_private_key: @@ -951,7 +951,7 @@ fields: ru: | Путь к файлу приватного ключа TLS для входящих клиентских подключений. Должен использоваться вместе с tls_certificate. - doc: "The path to the private key file for TLS connections. This is required to enable TLS for incoming client connections. Must be used together with `tls_certificate`." + doc: "Path to the private key file for TLS connections. Required to enable TLS for incoming client connections. Must be used together with `tls_certificate`." default: "None" tls_ca_cert: @@ -962,7 +962,7 @@ fields: ru: | Путь к CA-сертификату для верификации клиентских сертификатов. Используется с tls_mode = "verify-full" - doc: "The file containing the CA certificate to verify the client certificate. This is required when `tls_mode` is set to `verify-full`." + doc: "CA certificate file used to verify client certificates. Required when `tls_mode` is set to `verify-full`." default: "None" tls_mode: @@ -1170,18 +1170,18 @@ fields: нулевые байты и размер этого уровня. Слитый каскад проверяется снова при запуске бэкенда; если он не помещается в лимит PG MAX_STARTUP_PACKET_LENGTH (10000 байт), pg_doorman пропускает - GUC, заданные оператором, для этого запуска и пишет warning. + GUC, заданные оператором, для этого запуска и пишет предупреждение. Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } doc: | - Map of PostgreSQL configuration parameter names to string values. pg_doorman writes them into the `StartupMessage` of every backend it opens, which means they become `pg_settings.reset_val` for that session and survive any `RESET ALL` / `DISCARD ALL` the client issues. + Map of PostgreSQL configuration parameter names to string values. pg_doorman writes them into each backend `StartupMessage`; PostgreSQL stores them as the session reset defaults, so client `RESET ALL` / `DISCARD ALL` returns to these values. - Cascade of overrides (more specific wins per key): `general.startup_parameters` is the baseline; `pools..startup_parameters` overrides per pool; an `auth_query` row in passthrough mode can carry a `startup_parameters` JSON column that overrides per user. Dedicated-mode `auth_query` pools intentionally ignore the per-user column because one shared backend serves multiple roles. + Cascade order: `general.startup_parameters`, then `pools..startup_parameters`, then the optional `startup_parameters` JSON column returned by passthrough `auth_query`. Later layers win per key. Dedicated-mode `auth_query` pools ignore the per-user column because one shared backend serves multiple roles. - Validation enforced at config load: reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`) are refused; keys must match PG GUC naming (`^[A-Za-z_][A-Za-z0-9_.]*$`); values must not contain null bytes; per-level size must fit the operator budget. The full merged cascade is re-checked at every backend spawn against PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) cap; when the merge would not fit, pg_doorman drops all operator-supplied keys for that spawn and logs. + Validation at config load rejects reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`), invalid GUC names, null bytes, and per-level maps that exceed the operator budget. Before each backend startup, pg_doorman checks the full merged cascade against PG's `MAX_STARTUP_PACKET_LENGTH` (10 000 bytes); if it does not fit, pg_doorman drops all operator-supplied keys for that startup and logs the event. - If PostgreSQL rejects a parameter at backend startup, pg_doorman returns the PG `ErrorResponse` to the client unchanged — there is no retry, no per-key quarantine, no silent fallback. The client sees the same SQLSTATE and message it would have seen connecting to PG directly. The cumulative count is exported as `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}`; the parameter name and username are on the corresponding warn log line. + If PostgreSQL rejects a parameter at backend startup, pg_doorman returns the PG `ErrorResponse` to the client unchanged. There is no retry with the key removed and no per-key quarantine. The cumulative count is exported as `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}`; the parameter name and username are written to the corresponding warning log line. - Effective per-pool cascade can be inspected through the admin SQL console (`SHOW STARTUP_PARAMETERS` returns one row per `(pool, key)` with the contributing layer) and through the `/api/pools` REST endpoint that the Web UI consumes. + Inspect the effective per-pool cascade with `SHOW STARTUP_PARAMETERS` or the `/api/pools` REST endpoint. default: "{} (empty)" pool: @@ -1387,7 +1387,7 @@ fields: from coordinator eviction. When the coordinator needs to free a connection slot for another user, it will not evict connections from a user who is at or below this count. - This is separate from `min_pool_size` (user-level): `min_pool_size` controls prewarm + Separate from `min_pool_size` (user-level): `min_pool_size` controls prewarm and replenish (proactively creating connections), while `min_guaranteed_pool_size` only affects eviction decisions (never creates connections). @@ -1506,7 +1506,7 @@ fields: doc: | Per-pool map of PostgreSQL configuration parameters. Validation rules match those documented for [`general.startup_parameters`](#startup-parameters): reserved keys, GUC naming, null bytes, and the operator budget within PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) `StartupMessage` cap. - In the cascade `general` → `pool` → `auth_query`, this layer overrides general per key, and an auth_query passthrough entry overrides this. Dedicated-mode `auth_query` pools ignore the per-user column on purpose. See [`general.startup_parameters`](#startup-parameters) for the full behaviour description and observability surface. + In the cascade `general` → `pool` → `auth_query`, this layer overrides `general` per key, and a passthrough auth_query entry overrides this layer. Dedicated-mode `auth_query` pools ignore the per-user column because one shared backend serves multiple users. See [`general.startup_parameters`](#startup-parameters) for validation rules, failure behavior, and observability. default: "{} (empty)" user: @@ -1637,7 +1637,9 @@ fields: Если запрос возвращает ровно один столбец, он используется независимо от имени. Используйте $1 как параметр для имени пользователя. doc: | - SQL query to fetch credentials. Must return a column named `passwd` or `password` containing the MD5 or SCRAM hash. If the query returns exactly one column, it is used regardless of name. Extra columns are ignored except for the optional `startup_parameters` text column, which passthrough mode reads as a JSON object with per-user PostgreSQL startup parameters. Dedicated mode ignores that column and logs a warning. Use `$1` as the placeholder for the username parameter. + SQL query to fetch credentials. It must return a column named `passwd` or `password` containing the MD5 or SCRAM hash. If the query returns exactly one column, it is used regardless of name. + + Extra columns are ignored except for the optional `startup_parameters` text column. In passthrough mode, pg_doorman reads that column as a JSON object with per-user PostgreSQL startup parameters. Dedicated mode ignores it and logs a warning. Use `$1` as the placeholder for the username parameter. Example: `"SELECT passwd FROM pg_shadow WHERE usename = $1"` @@ -1835,5 +1837,5 @@ fields: config: en: "Group names that map onto the Admin role." ru: "Список групп, которые получают роль Admin (могут управлять пулами)." - doc: "An SSO user whose JWT carries any of these names in sso_groups_claim gets full admin access (POST /api/admin/* allowed). Empty (default) keeps the SSO surface read-only." + doc: "An SSO user whose JWT carries any of these names in sso_groups_claim gets full admin access (POST /api/admin/* allowed). Empty (default) keeps SSO users read-only." default: "[]" From c8df8e2449b9eabf054ff72fd614b66490b0b5f8 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 11:32:12 +0300 Subject: [PATCH 37/72] fix(pool): advance startup hash only after reload commit Closes codex HIGH #6. Reload previously did PREVIOUS_GENERAL_STARTUP_HASH.swap(new_hash) right after computing the new hash, before the new pool map / auth state / coordinators were built and atomically published. If any intermediate step failed and from_config returned Err, the static already held the new hash. The next reload of the same config would then see "no change" and skip the dynamic-pool recycle, leaving live backends pinned to the previous reset_val for users whose general.startup_parameters had changed. Switch to a load-only read up-front and defer the store to the end of from_config, right after COORDINATORS / AUTH_QUERY_STATE / POOLS are published. A failed reload now leaves the previous hash intact, so the next attempt classifies the change correctly. --- src/pool/mod.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 60339935a..7dac49f9e 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -340,8 +340,12 @@ impl ConnectionPool { config.general.startup_parameters.hash(&mut hasher); hasher.finish() }; - let previous_general_startup_hash = - PREVIOUS_GENERAL_STARTUP_HASH.swap(general_startup_hash, Ordering::Relaxed); + // Load only; the hash is not advanced until the new pool map has + // been committed at the bottom of from_config. Otherwise a reload + // that fails halfway poisons the hash, and the next reload of the + // *same* config silently skips the recycle of dynamic pools that + // still carry the old reset_val. + let previous_general_startup_hash = PREVIOUS_GENERAL_STARTUP_HASH.load(Ordering::Relaxed); // The static defaults to `0`, which collides with the empty-map // hash on a fresh process; treat that special case as "no prior // value" so the first reload never falsely claims a change. @@ -858,6 +862,11 @@ impl ConnectionPool { COORDINATORS.store(Arc::new(coordinators)); AUTH_QUERY_STATE.store(Arc::new(auth_query_states)); POOLS.store(Arc::new(new_pools.clone())); + // Advance the recycle-watcher hash only after the new state is + // published; a failure path above (Err returned via `?`) leaves + // PREVIOUS_GENERAL_STARTUP_HASH alone so the next reload still + // sees the old value and re-evaluates the change correctly. + PREVIOUS_GENERAL_STARTUP_HASH.store(general_startup_hash, Ordering::Relaxed); Ok(()) } From 69b32354818b31e1f787e6685a197fe730caa943 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 11:34:19 +0300 Subject: [PATCH 38/72] fix(pool): skip TLS retry on operator startup-parameter rejection Closes codex MED #13. sslmode=allow's "plain failed, retry over TLS" logic was retrying on ServerStartupParameterRejection. The retry path then ran the same StartupMessage with the same invalid GUC, doubling the log lines, counter increments, and PG error responses for one operator config error; on TLS-incapable PG servers it also masked the original sqlstate with the generic "tls required but server does not support tls" error. Exclude ServerStartupParameterRejection from the retry predicate on both the direct-create path and the fallback-candidate path. TLS cannot fix an invalid GUC, a privilege error, or a postmaster-only setting; the client gets the real PG sqlstate on the first attempt. --- src/pool/server_pool.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 36f688636..c8187d319 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -267,7 +267,9 @@ impl ServerPool { let should_tls_retry = match &result { Err(err) if self.address.server_tls.mode.retries_with_tls() => !matches!( err, - Error::ConnectError(_) | Error::ServerUnavailableError(_, _) + Error::ConnectError(_) + | Error::ServerUnavailableError(_, _) + | Error::ServerStartupParameterRejection { .. } ), _ => false, }; @@ -827,7 +829,9 @@ impl ServerPool { let should_tls_retry = match &result { Err(err) if fallback_address.server_tls.mode.retries_with_tls() => !matches!( err, - Error::ConnectError(_) | Error::ServerUnavailableError(_, _) + Error::ConnectError(_) + | Error::ServerUnavailableError(_, _) + | Error::ServerStartupParameterRejection { .. } ), _ => false, }; From 560fbcc653a7bd72231a917c36f423920b74fe61 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 11:48:01 +0300 Subject: [PATCH 39/72] fix(pool): recycle dynamic pools on pool.startup_parameters reload Closes codex BLOCKER #1. A RELOAD that changed only pools..startup_parameters left the existing dynamic passthrough pools (and the dedicated-mode shared pool) intact: AuthQueryState reuse hinged on auth_query config equality alone, and the dynamic-pool drain step only fired on auth_query changes or general.startup_parameters changes. The same logical user pool would keep handing out backends with the old reset_val even after the operator pushed a new pool baseline. RESET ALL / DISCARD ALL did not return to the new value, and the admin/API view of the pool stopped matching wire reality. Capture the pool-level startup_parameters hash on AuthQueryState construction. Reuse only when both the auth_query config and that hash are unchanged; otherwise drain every dynamic pool for the pool_name and let the dedicated shared pool rebuild against the new baseline. The drain step's reason log distinguishes auth_query changes from pool.startup_parameters changes for operator triage. --- src/pool/auth_query_state.rs | 9 +++++++ src/pool/mod.rs | 49 ++++++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/pool/auth_query_state.rs b/src/pool/auth_query_state.rs index 75491470a..502681e7c 100644 --- a/src/pool/auth_query_state.rs +++ b/src/pool/auth_query_state.rs @@ -20,6 +20,13 @@ use super::PoolIdentifier; pub struct AuthQueryState { cache_cell: tokio::sync::OnceCell, pub(crate) config: AuthQueryConfig, + /// Hash of the pool-level `startup_parameters` map captured at the + /// moment this state was built. RELOAD compares it against the new + /// `pool_config.startup_parameters` hash and drains dynamic pools + + /// rebuilds the dedicated shared pool when they differ — otherwise + /// dynamic backends would keep starting with the previous baseline's + /// `reset_val`. + pub(crate) pool_startup_hash: u64, pool_name: String, server_host: String, server_port: u16, @@ -33,6 +40,7 @@ impl AuthQueryState { /// Create a new AuthQueryState. pub(crate) fn new( config: AuthQueryConfig, + pool_startup_hash: u64, pool_name: String, server_host: String, server_port: u16, @@ -42,6 +50,7 @@ impl AuthQueryState { Self { cache_cell: tokio::sync::OnceCell::new(), config, + pool_startup_hash, pool_name, server_host, server_port, diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 7dac49f9e..3d5590d38 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -573,9 +573,23 @@ impl ConnectionPool { for (pool_name, pool_config) in &config.pools { if let Some(ref aq_config) = pool_config.auth_query { - // RELOAD: reuse state when config unchanged (preserves cache, executor, stats) + let pool_startup_hash = { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + pool_config.startup_parameters.hash(&mut hasher); + hasher.finish() + }; + // RELOAD: reuse state when both the auth_query config AND + // the pool-level startup_parameters are unchanged. A + // pool.startup_parameters edit must drop the cache and + // recycle the shared/dynamic pools: their backends were + // started with the old baseline as `reset_val`, and that + // value survives client-side `RESET ALL` / `DISCARD ALL` + // unless the backend is recreated. if let Some(old_state) = old_aq_states_for_reuse.get(pool_name) { - if old_state.config == *aq_config { + if old_state.config == *aq_config + && old_state.pool_startup_hash == pool_startup_hash + { info!("[pool: {pool_name}] auth_query config unchanged — reusing state"); auth_query_states.insert(pool_name.clone(), old_state.clone()); // Still need to ensure shared pool exists in new_pools @@ -761,6 +775,7 @@ impl ConnectionPool { pool_name.clone(), Arc::new(AuthQueryState::new( aq_config.clone(), + pool_startup_hash, pool_name.clone(), pool_config.server_host.clone(), pool_config.server_port, @@ -775,18 +790,32 @@ impl ConnectionPool { let old_aq_states = old_aq_states_for_reuse; let mut pools_to_remove: Vec = Vec::new(); - // 1. Compare old vs new auth_query configs + // 1. Compare old vs new auth_query configs, plus pool-level + // startup_parameters: either change must drain dynamic pools + // for this pool_name so the next auth_query lookup builds + // fresh backends with the new baseline reset_val. for (pool_name, old_state) in old_aq_states.iter() { - let new_aq = config - .pools - .get(pool_name) - .and_then(|p| p.auth_query.as_ref()); - let changed = match new_aq { + let new_pool_config = config.pools.get(pool_name); + let new_aq = new_pool_config.and_then(|p| p.auth_query.as_ref()); + let aq_changed = match new_aq { None => true, // auth_query removed Some(new) => *new != old_state.config, // config changed }; - if changed { - info!("[pool: {pool_name}] auth_query config changed — collecting dynamic pools for removal"); + let new_pool_startup_hash = new_pool_config.map(|p| { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + p.startup_parameters.hash(&mut hasher); + hasher.finish() + }); + let pool_startup_changed = new_pool_startup_hash + .map(|h| h != old_state.pool_startup_hash) + .unwrap_or(false); + if aq_changed || pool_startup_changed { + if aq_changed { + info!("[pool: {pool_name}] auth_query config changed — collecting dynamic pools for removal"); + } else { + info!("[pool: {pool_name}] pool.startup_parameters changed — collecting dynamic pools for removal"); + } for id in DYNAMIC_POOLS.load().iter() { if id.db == *pool_name { pools_to_remove.push(id.clone()); From 221bfdfae415dcf1931477e32460e4a9163364d4 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 11:57:57 +0300 Subject: [PATCH 40/72] fix(pool): freeze per-user auth_query overlay at dynamic pool creation Closes codex BLOCKER #2. resolved_startup_parameters used to peek the auth_query cache on every backend spawn through a long-lived dynamic pool. Cache TTL expiry between two consecutive spawns silently flipped the cascade: the first backend got the per-user overlay merged on top of the baseline, the second got the bare baseline. Subsequent RESET ALL / DISCARD ALL / SHOW on the same logical user pool returned different defaults depending on which backend the client was checked out to. Capture the overlay snapshot once, at create_dynamic_pool time. The auth_query cache is guaranteed warm there - the caller in src/auth/mod.rs has just fetched the entry - so the peek lands on a fresh per-user map. ServerPool now holds the snapshot as an immutable Arc; resolved_startup_parameters merges it on top of the baseline without touching the auth_query cache at all. Static pools and the dedicated-mode shared pool pass an empty overlay and skip the merge entirely. Overlay changes on subsequent refetches are handled by the reload / drain path in pool/mod.rs (this is the BLOCKER #1 / HIGH #3 territory); this commit closes only the TTL-vs-cache race. --- src/pool/dynamic.rs | 33 ++++++++++++++++++++ src/pool/inner.rs | 2 ++ src/pool/mod.rs | 5 +++ src/pool/retain.rs | 1 + src/pool/server_pool.rs | 56 +++++++++++++++++----------------- tests/bdd/pool_bench_helper.rs | 2 ++ 6 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/pool/dynamic.rs b/src/pool/dynamic.rs index 4f1738600..6c14dc4c6 100644 --- a/src/pool/dynamic.rs +++ b/src/pool/dynamic.rs @@ -135,6 +135,38 @@ pub fn create_dynamic_pool( std::sync::Arc::new(merged) }; + // Capture the per-user auth_query overlay once, at pool creation. + // The auth_query cache was just populated for this username by the + // caller in src/auth/mod.rs, so the peek is guaranteed warm; freezing + // the snapshot here means backend spawns from this pool stay + // deterministic even after the cache TTL elapses. Empty for + // dedicated-mode pools (a single shared backend serves many dynamic + // users) and for any auth_query row without a `startup_parameters` + // column. + let per_user_startup_overlay: std::sync::Arc> = { + let snapshot = super::get_auth_query_state(pool_name).and_then(|state| { + if state.config.is_dedicated_mode() { + None + } else { + state + .peek_startup_parameters(username, |overlay| { + if overlay.is_empty() { + None + } else { + Some( + overlay + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(), + ) + } + }) + .flatten() + } + }); + std::sync::Arc::new(snapshot.unwrap_or_default()) + }; + let manager = ServerPool::new( address.clone(), user.clone(), @@ -157,6 +189,7 @@ pub fn create_dynamic_pool( pool_mode == PoolMode::Session, fallback_state, base_startup_parameters, + per_user_startup_overlay, ); let queue_strategy = match config.general.server_round_robin { diff --git a/src/pool/inner.rs b/src/pool/inner.rs index 26b929e8a..2b49c3ddb 100644 --- a/src/pool/inner.rs +++ b/src/pool/inner.rs @@ -2095,6 +2095,7 @@ mod tests { false, None, Arc::new(std::collections::BTreeMap::new()), + Arc::new(std::collections::BTreeMap::new()), ); Pool::builder(server_pool) .coordinator(Some(coord)) @@ -2245,6 +2246,7 @@ mod tests { false, None, Arc::new(std::collections::BTreeMap::new()), + Arc::new(std::collections::BTreeMap::new()), ); let pool = Pool::builder(server_pool) .pool_name("test_db".to_string()) diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 3d5590d38..45d07be0f 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -502,6 +502,8 @@ impl ConnectionPool { pool_mode == PoolMode::Session, fallback_state, base_startup_parameters, + // Static pools carry no per-user auth_query overlay. + Arc::new(std::collections::BTreeMap::new()), ); let queue_strategy = match config.general.server_round_robin { @@ -700,6 +702,9 @@ impl ConnectionPool { pool_mode == PoolMode::Session, fallback_state, base_startup_parameters, + // Dedicated-mode shared pool serves multiple + // dynamic users — no single per-user override. + Arc::new(std::collections::BTreeMap::new()), ); let queue_strategy = match config.general.server_round_robin { diff --git a/src/pool/retain.rs b/src/pool/retain.rs index 4a4811899..a9b2db9e6 100644 --- a/src/pool/retain.rs +++ b/src/pool/retain.rs @@ -323,6 +323,7 @@ mod tests { false, None, Arc::new(std::collections::BTreeMap::new()), + Arc::new(std::collections::BTreeMap::new()), ); let database = Pool::builder(server_pool) .pool_name("test_db".to_string()) diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index c8187d319..6b6e5fcfc 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -95,6 +95,17 @@ pub struct ServerPool { /// view is immutable for the lifetime of the pool object. Shared as /// `Arc` so backend spawns can borrow it without per-call cloning. base_startup_parameters: Arc>, + + /// Per-user auth_query overlay captured at pool construction. Dynamic + /// passthrough pools populate this from a fresh `cache.get_or_fetch` + /// snapshot taken right after auth, so every backend spawn from this + /// pool sees the same overlay even when the auth_query cache TTL has + /// since expired. Empty for static pools and for the dedicated-mode + /// shared pool (which intentionally has no per-user override). + /// Refetches that change the overlay are handled by the reload / + /// drain logic in `pool/mod.rs`; this field is immutable for the + /// lifetime of the pool object. + per_user_startup_overlay: Arc>, } impl std::fmt::Debug for ServerPool { @@ -141,6 +152,7 @@ impl ServerPool { session_mode: bool, fallback_state: Option>, base_startup_parameters: Arc>, + per_user_startup_overlay: Arc>, ) -> ServerPool { ServerPool { address, @@ -163,6 +175,7 @@ impl ServerPool { session_mode, fallback_state, base_startup_parameters, + per_user_startup_overlay, } } @@ -394,35 +407,22 @@ impl ServerPool { /// PostgreSQL startup-packet limit. On overflow, pg_doorman logs and sends /// no operator-supplied parameters for this backend startup. fn resolved_startup_parameters(&self) -> std::borrow::Cow<'_, BTreeMap> { - // Look up the per-user auth_query entry only when the pool runs in - // passthrough auth_query mode (no shared server_user). In dedicated - // mode the shared backend serves multiple dynamic users, so no - // single per-user override could be honoured. - // - // The closure runs under a DashMap shard read lock. Return `None` for - // an empty overlay so the caller can keep borrowing the base map. - let auth_query_state = super::get_auth_query_state(&self.address.pool_name); - let merged: std::borrow::Cow<'_, BTreeMap> = match auth_query_state { - Some(state) if !state.config.is_dedicated_mode() => { - let overlay_applied = - state.peek_startup_parameters(&self.user.username, |overlay| { - if overlay.is_empty() { - None - } else { - let mut owned = (*self.base_startup_parameters).clone(); - for (k, v) in overlay { - owned.insert(k.clone(), v.clone()); - } - Some(owned) - } - }); - match overlay_applied.flatten() { - Some(owned) => std::borrow::Cow::Owned(owned), - None => std::borrow::Cow::Borrowed(&*self.base_startup_parameters), + // The per-user overlay was captured at pool construction from a + // fresh auth_query cache snapshot, so backend spawns are + // deterministic even after the cache TTL expires or a refetch + // races a spawn. Static pools and dedicated-mode shared pools + // pass an empty overlay; they fall through to the borrowed base + // map and skip the merge entirely. + let merged: std::borrow::Cow<'_, BTreeMap> = + if self.per_user_startup_overlay.is_empty() { + std::borrow::Cow::Borrowed(&*self.base_startup_parameters) + } else { + let mut owned = (*self.base_startup_parameters).clone(); + for (k, v) in self.per_user_startup_overlay.iter() { + owned.insert(k.clone(), v.clone()); } - } - _ => std::borrow::Cow::Borrowed(&*self.base_startup_parameters), - }; + std::borrow::Cow::Owned(owned) + }; // Per-level validation does not see the merged cascade or the // user/database/application_name fields added by the wire layer. Check diff --git a/tests/bdd/pool_bench_helper.rs b/tests/bdd/pool_bench_helper.rs index b87c8b2bc..3fa8736fe 100644 --- a/tests/bdd/pool_bench_helper.rs +++ b/tests/bdd/pool_bench_helper.rs @@ -76,6 +76,7 @@ async fn setup_internal_pool(world: &mut DoormanWorld, size: usize, _mode: Strin false, // session_mode None, // fallback_state std::sync::Arc::new(std::collections::BTreeMap::new()), + std::sync::Arc::new(std::collections::BTreeMap::new()), ); // Create Pool with configuration @@ -429,6 +430,7 @@ async fn setup_internal_pool_with_lifetimes( false, None, std::sync::Arc::new(std::collections::BTreeMap::new()), + std::sync::Arc::new(std::collections::BTreeMap::new()), ); let config = PoolConfig { From db2ada3034183582ea873e43fe5d6f7e255ed8be Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:09:28 +0300 Subject: [PATCH 41/72] fix(web): redact startup_parameter values for anonymous /api/pools Closes codex HIGH #5. When web.ui_anonymous is enabled, /api/pools served the full operator-supplied startup_parameter values to any reader. The DTO returned a `value` string for every cascade entry regardless of the caller's role, so tenant identifiers, audit tags, extension-specific GUC payloads, and any string an operator placed in the cascade became visible to the public read-only UI. The SPA polls this endpoint every ~1.5 s, so every refresh repeated the same disclosure. The DTO now wraps `value` in Option and elides it from the JSON when the caller's role is below Sso. Anonymous viewers still see which parameter is set and which cascade layer set it, so the operator can tell whether a pool has overrides at all without exposing the contents. Admin and SSO callers keep the full view; the admin-console SHOW STARTUP_PARAMETERS command keeps the values too. PoolDetail in the SPA reads /api/pools, so anonymous detail-page viewers see the same redacted body as the list page. --- src/web/routes/collect/pools.rs | 3 +- src/web/routes/dto.rs | 68 +++++++++++++++++++++++++++++++-- src/web/routes/pools.rs | 27 +++++++++++-- src/web/server/router.rs | 6 +-- 4 files changed, 94 insertions(+), 10 deletions(-) diff --git a/src/web/routes/collect/pools.rs b/src/web/routes/collect/pools.rs index c00955829..a3cf180ba 100644 --- a/src/web/routes/collect/pools.rs +++ b/src/web/routes/collect/pools.rs @@ -6,7 +6,7 @@ use crate::web::routes::dto::{PoolDto, PoolsDto, StartupParameterDto}; use super::{now_unix_ms, snapshot}; -pub(crate) fn collect_pools() -> PoolsDto { +pub(crate) fn collect_pools(reveal_startup_values: bool) -> PoolsDto { let snap = snapshot(); let pool_lookup = &snap.pool_lookup; let pools_map = get_all_pools(); @@ -72,6 +72,7 @@ pub(crate) fn collect_pools() -> PoolsDto { tls_backend_connections, startup_parameters: StartupParameterDto::from_resolved( pool.database.effective_startup_parameters_with_sources(), + reveal_startup_values, ), }; pools.push(dto); diff --git a/src/web/routes/dto.rs b/src/web/routes/dto.rs index d5b5a9a98..50a347d3a 100644 --- a/src/web/routes/dto.rs +++ b/src/web/routes/dto.rs @@ -160,29 +160,91 @@ pub(crate) struct PoolDto { /// One entry in `PoolDto.startup_parameters`. The `source` field tells the /// operator which cascade layer contributed the value — `"general"`, `"pool"` -/// or `"auth_query"`. +/// or `"auth_query"`. `value` is omitted for anonymous viewers because +/// operator-supplied values can include tenant identifiers, audit tags, +/// or extension-specific GUC payloads the public read-only UI must not +/// expose; admin/SSO callers and the admin SQL command keep the full +/// value. #[derive(Debug, Serialize)] pub(crate) struct StartupParameterDto { pub parameter: String, - pub value: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, pub source: &'static str, } impl StartupParameterDto { pub fn from_resolved( merged: BTreeMap, + reveal_values: bool, ) -> Vec { merged .into_iter() .map(|(parameter, (value, source))| StartupParameterDto { parameter, - value, + value: if reveal_values { Some(value) } else { None }, source: source.as_str(), }) .collect() } } +#[cfg(test)] +mod startup_parameter_dto_tests { + use super::*; + use crate::pool::startup_resolver::ParameterSource; + + fn sample_merged() -> BTreeMap { + let mut m = BTreeMap::new(); + m.insert( + "application_name".to_string(), + ("tenant-a-audit".to_string(), ParameterSource::Pool), + ); + m.insert( + "statement_timeout".to_string(), + ("30s".to_string(), ParameterSource::General), + ); + m + } + + #[test] + fn reveal_values_true_serializes_value_field() { + let dtos = StartupParameterDto::from_resolved(sample_merged(), true); + let json = serde_json::to_string(&dtos).expect("serialize"); + assert!( + json.contains("\"value\":\"tenant-a-audit\""), + "expected admin/SSO view to include the operator-supplied value, got {json}" + ); + assert!( + json.contains("\"value\":\"30s\""), + "expected admin/SSO view to include the general baseline value, got {json}" + ); + } + + #[test] + fn reveal_values_false_omits_value_field_but_keeps_parameter_and_source() { + let dtos = StartupParameterDto::from_resolved(sample_merged(), false); + let json = serde_json::to_string(&dtos).expect("serialize"); + assert!( + !json.contains("\"value\""), + "anonymous view must not include any startup_parameter value, got {json}" + ); + assert!( + json.contains("\"parameter\":\"application_name\""), + "anonymous view must preserve parameter name, got {json}" + ); + assert!( + json.contains("\"source\":\"pool\""), + "anonymous view must preserve source label, got {json}" + ); + assert_eq!( + dtos.len(), + 2, + "redaction must not drop entries — anonymous view still tells the operator which keys are set" + ); + } +} + #[derive(Debug, Serialize)] pub(crate) struct ClientsDto { pub ts: u64, diff --git a/src/web/routes/pools.rs b/src/web/routes/pools.rs index 94fcb3f8b..0c4e70bc7 100644 --- a/src/web/routes/pools.rs +++ b/src/web/routes/pools.rs @@ -1,10 +1,16 @@ //! GET /api/pools handler. +use crate::web::auth::Role; use crate::web::routes::collect::collect_pools; use crate::web::server::Response; -pub(crate) fn handle_pools() -> Response { - Response::ok_json(&collect_pools()) +pub(crate) fn handle_pools(role: Role) -> Response { + // Anonymous /api/pools must not leak operator-supplied + // startup_parameter values: they can carry tenant identifiers, audit + // tags, or accidental secrets. SSO/Admin callers keep the full view; + // anonymous viewers get parameter+source only. + let reveal_startup_values = role >= Role::Sso; + Response::ok_json(&collect_pools(reveal_startup_values)) } #[cfg(test)] @@ -13,10 +19,25 @@ mod tests { #[test] fn pools_response_is_200_json_with_array() { - let r = handle_pools(); + let r = handle_pools(Role::Admin); assert_eq!(r.status, 200); let body = std::str::from_utf8(&r.body).unwrap(); assert!(body.contains("\"ts\""), "body={body}"); assert!(body.contains("\"pools\""), "body={body}"); } + + #[test] + fn pools_response_hides_startup_value_for_anonymous() { + // Smoke check on the wired path: anonymous /api/pools must not + // surface a "value" field anywhere in the response body. The + // actual redaction logic lives in `StartupParameterDto::from_resolved` + // and is covered by the dedicated unit test in `dto.rs`. + let r = handle_pools(Role::Anonymous); + assert_eq!(r.status, 200); + let body = std::str::from_utf8(&r.body).unwrap(); + assert!( + !body.contains("\"value\""), + "anonymous /api/pools must not include any startup_parameter value, body={body}" + ); + } } diff --git a/src/web/server/router.rs b/src/web/server/router.rs index 8f223b7e9..c1303e863 100644 --- a/src/web/server/router.rs +++ b/src/web/server/router.rs @@ -59,7 +59,7 @@ pub(super) fn unauthorized_for(req: &ParsedRequest<'_>) -> Response { } } -fn route_api(req: &ParsedRequest<'_>) -> Response { +fn route_api(req: &ParsedRequest<'_>, role: Role) -> Response { // ParsedRequest already split path on `?` — no further work here. let query = parse_query(req.query.unwrap_or("")); @@ -71,7 +71,7 @@ fn route_api(req: &ParsedRequest<'_>) -> Response { match req.path { "/api/version" => routes::version::handle_version(), "/api/overview" => routes::overview::handle_overview(), - "/api/pools" => routes::pools::handle_pools(), + "/api/pools" => routes::pools::handle_pools(role), "/api/clients" => routes::clients::handle_clients(&query), "/api/connections" => routes::connections::handle_connections(), "/api/databases" => routes::databases::handle_databases(), @@ -138,7 +138,7 @@ pub(super) fn dispatch( _ => unauthorized_for(req), }; } - return route_api(req); + return route_api(req, actual); } // SPA shell: serve the embedded bundle. Anything that is not /api or From 176e583ddc1f847a78b86db5ae6655e6acdc0ebb Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:17:24 +0300 Subject: [PATCH 42/72] fix(pool): preserve ServerStartupParameterRejection on fallback path Closes codex HIGH #4. The fallback aggregate path collapsed every candidate failure into a generic Error::ConnectError("all fallback candidates rejected ..."), which loses the actual PG SQLSTATE/message. Worse, every candidate that rejected an operator-supplied startup parameter was marked unhealthy with the cooldown ladder, even though the host is fine and the same parameter would fail again against any candidate until the operator fixes the config. The client saw a generic 53300, and the fallback pool re-cooldowned healthy candidates on each retry. Add FailureReason::StartupParameterRejection so the per-host failure counter labels the cause distinctly. mark_unhealthy now skips the cooldown ladder for this reason - it still increments the failure metric but leaves the candidate eligible for the next attempt. When every candidate in a wave fails solely on this reason, the aggregate path returns the original Error::ServerStartupParameterRejection (carrying PG's real SQLSTATE and message) instead of the ConnectError wrapper, so the client gets the same error it would have seen connecting to PG directly. --- src/pool/fallback.rs | 68 +++++++++++++++++++++++++++ src/pool/server_pool.rs | 102 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/src/pool/fallback.rs b/src/pool/fallback.rs index 525afb4c3..982ffe96b 100644 --- a/src/pool/fallback.rs +++ b/src/pool/fallback.rs @@ -53,6 +53,12 @@ pub enum FailureReason { ServerUnavailable, /// `startup_with_timeout` deadline elapsed. Timeout, + /// PostgreSQL rejected an operator-supplied startup parameter (the + /// real SQLSTATE lives in the carried Error). Distinct from + /// StartupError because the candidate host is healthy — only the + /// operator config is wrong — so it must not enter the per-host + /// cooldown that StartupError implies. + StartupParameterRejection, /// Anything else — should normally not happen on the fallback path. Other, } @@ -64,9 +70,16 @@ impl FailureReason { FailureReason::StartupError => "startup_error", FailureReason::ServerUnavailable => "server_unavailable", FailureReason::Timeout => "timeout", + FailureReason::StartupParameterRejection => "startup_parameter_rejection", FailureReason::Other => "other", } } + + /// Whether `mark_unhealthy` should record a cooldown entry. Operator + /// config errors must not blacklist a healthy candidate. + pub fn warrants_host_cooldown(self) -> bool { + !matches!(self, FailureReason::StartupParameterRejection) + } } impl From<&Error> for FailureReason { @@ -81,6 +94,9 @@ impl From<&Error> for FailureReason { Error::ConnectError(_) => FailureReason::ConnectError, Error::ServerUnavailableError(_, _) => FailureReason::ServerUnavailable, Error::ServerStartupError(_, _) => FailureReason::StartupError, + Error::ServerStartupParameterRejection { .. } => { + FailureReason::StartupParameterRejection + } _ => FailureReason::Other, } } @@ -296,6 +312,14 @@ impl FallbackState { .with_label_values(&[self.pool_name.as_str(), reason.as_str()]) .inc(); + // Operator config errors (e.g. invalid startup_parameter) must not + // blacklist a healthy candidate — the same misconfiguration will + // fail against every host until the operator fixes the config. + // Count it in the failure metric (above), then return. + if !reason.warrants_host_cooldown() { + return; + } + let now = Instant::now(); let base = self.connect_timeout; let mut guard = self.unhealthy_candidates.lock(); @@ -887,6 +911,50 @@ mod tests { )), FailureReason::ServerUnavailable ); + assert_eq!( + FailureReason::from(&Error::ServerStartupParameterRejection { + sqlstate: "22023".into(), + message: "invalid_value".into(), + server_identifier: id.clone(), + }), + FailureReason::StartupParameterRejection + ); + } + + #[test] + fn warrants_host_cooldown_skips_only_startup_parameter_rejection() { + // The whole point of the new helper: every host-related failure + // still cools the host down, only operator-config errors do not. + assert!(FailureReason::ConnectError.warrants_host_cooldown()); + assert!(FailureReason::StartupError.warrants_host_cooldown()); + assert!(FailureReason::ServerUnavailable.warrants_host_cooldown()); + assert!(FailureReason::Timeout.warrants_host_cooldown()); + assert!(FailureReason::Other.warrants_host_cooldown()); + assert!(!FailureReason::StartupParameterRejection.warrants_host_cooldown()); + } + + #[test] + fn mark_unhealthy_skips_cooldown_for_startup_parameter_rejection() { + let state = FallbackState::new( + "test_pool_op_config_no_cooldown".to_string(), + vec![], + Duration::from_secs(10), + Duration::from_millis(50), + Duration::from_secs(2), + 30_000, + ) + .unwrap(); + state.mark_unhealthy("10.0.0.1", 5432, FailureReason::StartupParameterRejection); + // The candidate must remain eligible — the failure was the + // operator's config, not the host. + assert!( + state + .unhealthy_candidates + .lock() + .get(&("10.0.0.1".to_string(), 5432)) + .is_none(), + "operator-config rejection must not record a cooldown entry" + ); } #[test] diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 6b6e5fcfc..f7a94cc72 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -693,6 +693,17 @@ impl ServerPool { "[{}@{}] fallback: all fallback candidates rejected ({summary_str})", self.address.username, self.address.pool_name, ); + // If every candidate failed solely on operator-supplied startup + // parameter rejection, surface PG's actual sqlstate/message so the + // client gets the real error instead of a generic 53300. Healthy + // hosts are not blacklisted (mark_unhealthy skips this category), + // so the same misconfiguration will keep failing until the + // operator fixes the config. + if summary.all_startup_parameter_rejection() { + if let Some(err) = summary.into_last_err() { + return (Err(err), source); + } + } ( Err(Error::ConnectError(format!( "all fallback candidates rejected ({summary_str})" @@ -1055,6 +1066,22 @@ impl FailureSummary { self.last_err = Some(err); } + /// True when the recorded failures are non-empty and contain only + /// `StartupParameterRejection`. Used to decide whether to surface the + /// original PG error to the client instead of the aggregate + /// "all candidates rejected" wrapper. + fn all_startup_parameter_rejection(&self) -> bool { + !self.counts.is_empty() + && self + .counts + .keys() + .all(|r| matches!(r, super::fallback::FailureReason::StartupParameterRejection)) + } + + fn into_last_err(self) -> Option { + self.last_err + } + fn format(&self) -> String { if self.counts.is_empty() { return "no candidates".to_string(); @@ -1165,6 +1192,81 @@ mod tests { Metrics::new(lifetime_ms, 0, 0) } + fn rejection_err() -> Error { + Error::ServerStartupParameterRejection { + sqlstate: "22023".to_string(), + message: "invalid_value".to_string(), + server_identifier: crate::app::errors::ServerIdentifier::new( + "alice".to_string(), + "db", + "pool_a", + ), + } + } + + fn timeout_err() -> Error { + Error::ConnectError("server startup timed out after 5s".to_string()) + } + + #[test] + fn all_startup_parameter_rejection_false_when_empty() { + let s = FailureSummary::default(); + assert!( + !s.all_startup_parameter_rejection(), + "empty summary must not claim everyone rejected on startup parameter" + ); + } + + #[test] + fn all_startup_parameter_rejection_true_when_only_rejections() { + let mut s = FailureSummary::default(); + s.record( + rejection_err(), + super::super::fallback::FailureReason::StartupParameterRejection, + ); + s.record( + rejection_err(), + super::super::fallback::FailureReason::StartupParameterRejection, + ); + assert!(s.all_startup_parameter_rejection()); + } + + #[test] + fn all_startup_parameter_rejection_false_when_mixed() { + let mut s = FailureSummary::default(); + s.record( + rejection_err(), + super::super::fallback::FailureReason::StartupParameterRejection, + ); + s.record( + timeout_err(), + super::super::fallback::FailureReason::Timeout, + ); + assert!( + !s.all_startup_parameter_rejection(), + "a single non-rejection cause must veto the shortcut" + ); + } + + #[test] + fn into_last_err_returns_most_recently_recorded() { + let mut s = FailureSummary::default(); + s.record( + timeout_err(), + super::super::fallback::FailureReason::Timeout, + ); + s.record( + rejection_err(), + super::super::fallback::FailureReason::StartupParameterRejection, + ); + match s.into_last_err() { + Some(Error::ServerStartupParameterRejection { sqlstate, .. }) => { + assert_eq!(sqlstate, "22023") + } + other => panic!("expected the last-recorded rejection, got {other:?}"), + } + } + #[test] fn lifetime_exceeded_skipped_when_under_pressure() { // A connection well past its budget is kept alive when the caller From 46f514d74b81affb86d0227c7c32305e88e9a2af Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:29:18 +0300 Subject: [PATCH 43/72] fix(auth_query): drop dynamic pool when refetch changes per-user overlay Closes codex HIGH #3. BLOCKER #2 froze the per-user auth_query startup_parameters snapshot into ServerPool at pool creation. That fixed the TTL race but exposed the inverse problem: when an operator updates the auth_query row's startup_parameters column, the next refetch loads the new map into the cache, but the live dynamic pool keeps shipping the snapshot captured at creation. Existing pool backends ignore the change until their natural lifetime expires. Hash the per-user overlay once at create_dynamic_pool time and stash it on ConnectionPool. After every fresh get_or_fetch or refetch on the auth_query cache, recompute the hash from the new map and drop the dynamic pool when they differ. The next client connection rebuilds the pool with create_dynamic_pool, which snapshots the current overlay. per_user_overlay_hash sorts keys before hashing so HashMap iteration order does not produce false drift signals on every refetch. --- src/auth/auth_query.rs | 47 ++++++++++++++ src/pool/dynamic.rs | 9 ++- src/pool/mod.rs | 143 +++++++++++++++++++++++++++++++++++++++++ src/pool/retain.rs | 1 + 4 files changed, 199 insertions(+), 1 deletion(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index 04317d4e1..43270b381 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -659,6 +659,43 @@ impl AuthQueryCache { entry.startup_parameters.clear(); } + /// When a fresh auth_query fetch produces a per-user + /// `startup_parameters` map that differs from the snapshot frozen + /// into the live dynamic pool at creation time, drop the pool so + /// the next client connection rebuilds against the new overlay. + /// Without this, an operator-side change to the row (`UPDATE + /// pgbouncer.users SET startup_parameters = ...`) only takes effect + /// for new dynamic-pool spawns, not for existing pools. Dedicated + /// mode and the dedicated-mode warning path land here with an empty + /// map; that compares equal to the empty-overlay hash that + /// dedicated pools store, so nothing is dropped on that path. + fn drop_dynamic_pool_if_overlay_drifted( + &self, + username: &str, + new_overlay: &std::collections::HashMap, + ) { + let identifier = crate::pool::PoolIdentifier::new(&self.pool_name, username); + if !crate::pool::is_dynamic_pool(&identifier) { + return; + } + let new_hash = crate::pool::per_user_overlay_hash(new_overlay.iter()); + let live_hash = crate::pool::POOLS + .load() + .get(&identifier) + .map(|p| p.per_user_startup_overlay_hash); + match live_hash { + Some(h) if h != new_hash => { + if crate::pool::drop_dynamic_pool(&identifier) { + info!( + "[{username}@{}] auth_query overlay drift on refetch — dynamic pool dropped, next connect will rebuild", + self.pool_name + ); + } + } + _ => {} + } + } + /// Increment a stats counter if stats are enabled. fn inc(&self, counter: fn(&AuthQueryStats) -> &AtomicU64) { if let Some(ref stats) = self.stats { @@ -723,7 +760,15 @@ impl AuthQueryCache { let mut entry = CacheEntry::positive(password_hash); entry.startup_parameters = startup_params; self.dedicated_mode_filter(&mut entry, username); + // Publish the fresh entry first so any concurrent + // create_dynamic_pool peeks the new overlay, then drop the + // pool whose snapshot drifted. Reversing the order would + // open a window where the drop runs against the live pool + // while the cache still holds the old map, and a racing + // create_dynamic_pool would rebuild against that stale + // map and immediately drift again. self.entries.insert(username.to_string(), entry.clone()); + self.drop_dynamic_pool_if_overlay_drifted(username, &entry.startup_parameters); Ok(Some(entry)) } Ok(None) => { @@ -791,7 +836,9 @@ impl AuthQueryCache { entry.startup_parameters = startup_params; entry.last_refetch_at = Some(Instant::now()); self.dedicated_mode_filter(&mut entry, username); + // Insert before drop — see comment in get_or_fetch. self.entries.insert(username.to_string(), entry.clone()); + self.drop_dynamic_pool_if_overlay_drifted(username, &entry.startup_parameters); Ok(Some(entry)) } Ok(None) => { diff --git a/src/pool/dynamic.rs b/src/pool/dynamic.rs index 6c14dc4c6..2aae0f30a 100644 --- a/src/pool/dynamic.rs +++ b/src/pool/dynamic.rs @@ -189,9 +189,15 @@ pub fn create_dynamic_pool( pool_mode == PoolMode::Session, fallback_state, base_startup_parameters, - per_user_startup_overlay, + per_user_startup_overlay.clone(), ); + // Snapshot the overlay hash before the Arc moves into ServerPool. + // The auth_query cache compares the new fetched per-user map against + // this value after every refetch; a mismatch drops the dynamic pool + // so the next connect rebuilds with the new reset_val. + let overlay_hash = super::per_user_overlay_hash(per_user_startup_overlay.iter()); + let queue_strategy = match config.general.server_round_robin { true => QueueMode::Fifo, false => QueueMode::Lifo, @@ -217,6 +223,7 @@ pub fn create_dynamic_pool( database: pool, address, config_hash: 0, // dynamic pools don't participate in hash-based reload + per_user_startup_overlay_hash: overlay_hash, original_server_parameters: Arc::new(tokio::sync::Mutex::new(ServerParameters::new())), settings: PoolSettings { pool_mode, diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 45d07be0f..347c05e99 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -100,6 +100,37 @@ pub fn get_client_server_map() -> Option { CLIENT_SERVER_MAP.get().cloned() } +/// Stable hash of a per-user auth_query `startup_parameters` overlay. +/// Used to detect overlay drift after `auth_query` refetches: if the +/// new row's hash differs from `ConnectionPool::per_user_startup_overlay_hash`, +/// the dynamic pool is dropped so the next client connection rebuilds +/// against the new overlay. Accepts both `HashMap` (auth_query cache +/// shape) and `BTreeMap` (the immutable snapshot stored on the pool) +/// via a borrowed iterator, normalising key order so the hash is shape- +/// independent. +pub(crate) fn per_user_overlay_hash<'a, I>(entries: I) -> u64 +where + I: IntoIterator, +{ + use std::hash::{Hash, Hasher}; + let mut sorted: Vec<(&str, &str)> = entries + .into_iter() + .map(|(k, v)| (k.as_str(), v.as_str())) + .collect(); + sorted.sort_by(|a, b| a.0.cmp(b.0)); + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + sorted.hash(&mut hasher); + hasher.finish() +} + +/// Hash that `per_user_overlay_hash` produces for the empty overlay. +/// Computed once and reused by every static / dedicated-mode pool so +/// drift comparisons against dynamic pools' real overlay hashes are +/// shape-stable across the codebase. +pub(crate) fn empty_overlay_hash() -> u64 { + per_user_overlay_hash(std::iter::empty::<(&String, &String)>()) +} + /// Build a `ServerTlsConfig` for a pool, merging pool-level overrides with general defaults. pub(crate) fn build_server_tls_for_pool( pool_config: &ConfigPool, @@ -165,6 +196,31 @@ pub fn is_dynamic_pool(id: &PoolIdentifier) -> bool { DYNAMIC_POOLS.load().contains(id) } +/// Drop a dynamic pool from `POOLS` and `DYNAMIC_POOLS`. No-op for +/// static pools — overlay drift only applies to auth_query passthrough. +/// Used by the auth_query cache after a refetch when the new per-user +/// `startup_parameters` map no longer matches the snapshot frozen in +/// the live pool: the next client connection rebuilds the dynamic pool +/// against the new overlay. +pub fn drop_dynamic_pool(id: &PoolIdentifier) -> bool { + if !is_dynamic_pool(id) { + return false; + } + let pools = POOLS.load(); + let mut new_pools = (**pools).clone(); + let removed = new_pools.remove(id).is_some(); + if removed { + POOLS.store(Arc::new(new_pools)); + } + let dynamics = DYNAMIC_POOLS.load(); + if dynamics.contains(id) { + let mut new_set = (**dynamics).clone(); + new_set.remove(id); + DYNAMIC_POOLS.store(Arc::new(new_set)); + } + removed +} + /// Get auth_query state for a database pool. pub fn get_auth_query_state(db: &str) -> Option> { AUTH_QUERY_STATE.load().get(db).cloned() @@ -258,6 +314,14 @@ pub struct ConnectionPool { /// the pool after a RELOAD command pub config_hash: u64, + /// Hash of the per-user auth_query overlay frozen into this pool at + /// creation time. After a refetch, the auth_query cache compares the + /// new per-user startup_parameters map against this value; a mismatch + /// drops the dynamic pool so the next client connection rebuilds + /// against the new overlay. Static pools and dedicated-mode shared + /// pools both pin this to the empty-map hash. + pub per_user_startup_overlay_hash: u64, + /// Cache pub prepared_statement_cache: Option, @@ -532,6 +596,11 @@ impl ConnectionPool { database: pool, address, config_hash: new_pool_hash_value, + // Static and dedicated-mode shared pools carry no + // per-user overlay, so they pin to the empty-map + // hash. Dynamic passthrough pools set this from the + // captured overlay in dynamic.rs. + per_user_startup_overlay_hash: empty_overlay_hash(), original_server_parameters: Arc::new(tokio::sync::Mutex::new( ServerParameters::new(), )), @@ -738,6 +807,11 @@ impl ConnectionPool { database: pool, address, config_hash: new_pool_hash_value, + // Static and dedicated-mode shared pools carry no + // per-user overlay, so they pin to the empty-map + // hash. Dynamic passthrough pools set this from the + // captured overlay in dynamic.rs. + per_user_startup_overlay_hash: empty_overlay_hash(), original_server_parameters: Arc::new(tokio::sync::Mutex::new( ServerParameters::new(), )), @@ -1122,6 +1196,75 @@ pub fn get_coordinator(db: &str) -> Option::new(); + assert_eq!( + per_user_overlay_hash(empty_map.iter()), + empty_overlay_hash() + ); + } + + #[test] + fn per_user_overlay_hash_ignores_input_order() { + // HashMap with the same key/value pairs but inserted in different + // orders must hash identically. Without the internal sort the + // hash would depend on HashMap iteration order, which is + // randomized per process and would falsely flag overlay drift on + // every refetch. + let mut a = std::collections::HashMap::new(); + a.insert("work_mem".to_string(), "64MB".to_string()); + a.insert("statement_timeout".to_string(), "30s".to_string()); + let mut b = std::collections::HashMap::new(); + b.insert("statement_timeout".to_string(), "30s".to_string()); + b.insert("work_mem".to_string(), "64MB".to_string()); + assert_eq!( + per_user_overlay_hash(a.iter()), + per_user_overlay_hash(b.iter()) + ); + } + + #[test] + fn per_user_overlay_hash_changes_when_value_changes() { + let mut a = std::collections::HashMap::new(); + a.insert("work_mem".to_string(), "64MB".to_string()); + let mut b = std::collections::HashMap::new(); + b.insert("work_mem".to_string(), "128MB".to_string()); + assert_ne!( + per_user_overlay_hash(a.iter()), + per_user_overlay_hash(b.iter()) + ); + } + + #[test] + fn per_user_overlay_hash_changes_when_key_added() { + let mut a = std::collections::HashMap::new(); + a.insert("work_mem".to_string(), "64MB".to_string()); + let mut b = a.clone(); + b.insert("statement_timeout".to_string(), "30s".to_string()); + assert_ne!( + per_user_overlay_hash(a.iter()), + per_user_overlay_hash(b.iter()) + ); + } + + #[test] + fn per_user_overlay_hash_matches_across_hashmap_and_btreemap() { + // The auth_query cache stores HashMap; the pool freezes a + // BTreeMap snapshot. Drift detection compares the two — they + // must hash to the same value for identical content. + let mut h = std::collections::HashMap::new(); + h.insert("work_mem".to_string(), "64MB".to_string()); + let mut b = std::collections::BTreeMap::new(); + b.insert("work_mem".to_string(), "64MB".to_string()); + assert_eq!( + per_user_overlay_hash(h.iter()), + per_user_overlay_hash(b.iter()) + ); + } + // --- compute_spare tests --- #[test] diff --git a/src/pool/retain.rs b/src/pool/retain.rs index a9b2db9e6..995681f1a 100644 --- a/src/pool/retain.rs +++ b/src/pool/retain.rs @@ -344,6 +344,7 @@ mod tests { min_guaranteed_pool_size: 0, }, config_hash: 0, + per_user_startup_overlay_hash: crate::pool::empty_overlay_hash(), prepared_statement_cache: None, coordinator: None, replenish_failures: Arc::new(AtomicU32::new(0)), From a2f5c5f734e829254674a88ef0abc9e56c64db0c Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:37:11 +0300 Subject: [PATCH 44/72] fix(pool): keep baseline when oversize auth_query overlay would strip it Closes codex MED #7. resolved_startup_parameters returned an empty BTreeMap on either of two size limits, even when only the per-user auth_query overlay was responsible. A wide overlay row (e.g. a user with several extension GUCs) silently stripped the operator-wide baseline guardrails - statement_timeout, lock_timeout, application_name - for that backend spawn, even though the baseline alone would have fit. Try the baseline-only cascade before falling back to an empty map. If the baseline fits while the merged cascade does not, ship the baseline and increment a new counter reason "auth_query_overlay_oversize" so operators can spot per-user rows that pushed the cascade over budget without having to grep logs. If the baseline itself overflows, the empty-map drop still applies - that is a general/pool config error, not a per-user issue. Drive-by: the auto-generated Prometheus reference now lists every reason the runtime can emit, including dedicated_mode (added in c438d6f but missing from the docs table) and the new auth_query_overlay_oversize. --- src/app/generate/docs.rs | 2 +- src/pool/server_pool.rs | 103 +++++++++++++++++++++++++++------------ src/web/metrics/mod.rs | 9 +++- 3 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src/app/generate/docs.rs b/src/app/generate/docs.rs index 23b8d622c..ac800c8dd 100644 --- a/src/app/generate/docs.rs +++ b/src/app/generate/docs.rs @@ -479,7 +479,7 @@ fn write_prometheus_metrics_section(out: &mut String) { let _ = writeln!(out, "| Metric | Description |"); let _ = writeln!(out, "|--------|-------------|"); let _ = writeln!(out, "| `pg_doorman_backend_startup_parameter_errors_total` | Counter by `(pool, sqlstate)`. Increments when PostgreSQL rejects a backend startup and the `ErrorResponse` names a startup parameter sent by pg_doorman. SQLSTATE class `57P` is excluded because Patroni-assisted fallback handles those errors. The failing parameter name and username are written to the warning log line, not to labels. pg_doorman first parses the common `parameter \"\"` phrase, then scans the message for any sent key in double quotes. If neither lookup finds a key, the counter is not incremented. |"); - let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman drops operator-supplied entries before sending `StartupMessage`. Reasons: `cascade_budget_exceeded`, `packet_cap_exceeded`, `auth_query_oversize`, `auth_query_invalid_entry`. |\n"); + let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman drops operator-supplied entries before sending `StartupMessage`. Reasons: `cascade_budget_exceeded`, `packet_cap_exceeded`, `auth_query_oversize`, `auth_query_overlay_oversize`, `auth_query_invalid_entry`, `dedicated_mode`. |\n"); // Server Metrics let _ = writeln!(out, "### Server Metrics\n"); diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index f7a94cc72..26938378f 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -430,50 +430,91 @@ impl ServerPool { // // 1. Operator-supplied pairs against the reserved operator budget. // 2. Full StartupMessage size against PG's `MAX_STARTUP_PACKET_LENGTH`. - let body_bytes = sp::serialized_bytes(&merged); - if body_bytes > sp::MAX_OPERATOR_BUDGET { - warn!( - "[{}@{}] effective startup_parameters serialize to {} bytes, exceeding \ - operator budget {} (PG cap {}); all operator-supplied parameters dropped \ - for this backend spawn", - self.user.username, - self.address.pool_name, - body_bytes, - sp::MAX_OPERATOR_BUDGET, - sp::MAX_STARTUP_PACKET_SIZE, - ); - crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL - .with_label_values(&[self.address.pool_name.as_str(), "cascade_budget_exceeded"]) - .inc(); - return std::borrow::Cow::Owned(BTreeMap::new()); - } + // + // When the *merged* cascade overflows, retry with the baseline + // alone before falling back to an empty map: it is the per-user + // auth_query overlay that operators can grow arbitrarily large + // (e.g. a wide row of extension GUCs), while the + // general+pool baseline is config-controlled and already + // length-checked. Keeping the baseline preserves operator-wide + // guardrails such as `statement_timeout` and `lock_timeout` for + // a user whose per-row overlay is over budget. let username_for_wire = self .user .server_username .as_deref() .unwrap_or(self.user.username.as_str()); + + let body_bytes = sp::serialized_bytes(&merged); let packet_bytes = sp::full_packet_bytes( username_for_wire, &self.database, &self.application_name, &merged, ); - if packet_bytes > sp::MAX_STARTUP_PACKET_SIZE { - warn!( - "[{}@{}] effective StartupMessage size {} bytes exceeds PG cap {} once \ - user/database/application_name are included; all operator-supplied \ - parameters dropped for this backend spawn", - self.user.username, - self.address.pool_name, - packet_bytes, - sp::MAX_STARTUP_PACKET_SIZE, + + let over_budget = body_bytes > sp::MAX_OPERATOR_BUDGET; + let over_packet = packet_bytes > sp::MAX_STARTUP_PACKET_SIZE; + if !over_budget && !over_packet { + return merged; + } + + // The merged cascade is over a limit. If we have an overlay, try + // dropping just the overlay and keep the baseline. + let has_overlay = !self.per_user_startup_overlay.is_empty(); + if has_overlay { + let baseline = &*self.base_startup_parameters; + let baseline_body = sp::serialized_bytes(baseline); + let baseline_packet = sp::full_packet_bytes( + username_for_wire, + &self.database, + &self.application_name, + baseline, ); - crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL - .with_label_values(&[self.address.pool_name.as_str(), "packet_cap_exceeded"]) - .inc(); - return std::borrow::Cow::Owned(BTreeMap::new()); + if baseline_body <= sp::MAX_OPERATOR_BUDGET + && baseline_packet <= sp::MAX_STARTUP_PACKET_SIZE + { + warn!( + "[{}@{}] auth_query per-user startup_parameters pushes the cascade \ + over the operator budget (merged {} bytes, packet {} bytes); \ + dropping the per-user overlay and keeping the general/pool \ + baseline for this backend spawn", + self.user.username, self.address.pool_name, body_bytes, packet_bytes, + ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[ + self.address.pool_name.as_str(), + "auth_query_overlay_oversize", + ]) + .inc(); + return std::borrow::Cow::Borrowed(baseline); + } } - merged + + // Either there was no overlay to drop or the baseline alone is + // still over budget — fall back to the empty map. The latter + // case is an operator-config error in general/pool startup_parameters, + // not a per-user issue. + let reason = if over_packet { + "packet_cap_exceeded" + } else { + "cascade_budget_exceeded" + }; + warn!( + "[{}@{}] effective startup_parameters serialize to {} bytes (packet {} bytes), \ + exceeding operator budget {} / PG cap {}; all operator-supplied parameters \ + dropped for this backend spawn", + self.user.username, + self.address.pool_name, + body_bytes, + packet_bytes, + sp::MAX_OPERATOR_BUDGET, + sp::MAX_STARTUP_PACKET_SIZE, + ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[self.address.pool_name.as_str(), reason]) + .inc(); + std::borrow::Cow::Owned(BTreeMap::new()) } /// Establish a fallback connection by iterating through Patroni-discovered diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index b7e7f5ca1..ddaaef5ea 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -501,6 +501,10 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// * `auth_query_oversize` — the auth_query `startup_parameters` /// text column for some username exceeded the operator budget at /// parse time, so the per-user overlay is ignored. +/// * `auth_query_overlay_oversize` — the merged baseline+overlay was +/// over budget, but the baseline alone fits. Keeps general/pool +/// guardrails (statement_timeout, lock_timeout, …) for that +/// user instead of stripping the operator cascade outright. /// * `auth_query_invalid_entry` — an individual entry in the /// auth_query JSON failed validation (reserved key, bad GUC name, /// null byte, non-string value). Incremented per offending entry. @@ -509,7 +513,7 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// mode (one shared backend across users) so the per-user overlay /// was dropped. Incremented per dropped entry. /// -/// All four cases also emit a `warn!` log line for human triage; the +/// All cases also emit a `warn!` log line for human triage; the /// counter exists so dashboards and alerts can spot the silent drop /// without log scraping. pub(crate) static STARTUP_PARAMETERS_DROPPED_TOTAL: Lazy = Lazy::new(|| { @@ -520,7 +524,8 @@ pub(crate) static STARTUP_PARAMETERS_DROPPED_TOTAL: Lazy = Lazy:: entries pg_doorman dropped before sending StartupMessage. \ Labels: pool, reason (cascade_budget_exceeded, \ packet_cap_exceeded, auth_query_oversize, \ - auth_query_invalid_entry, dedicated_mode). Distinct from \ + auth_query_overlay_oversize, auth_query_invalid_entry, \ + dedicated_mode). Distinct from \ pg_doorman_backend_startup_parameter_errors_total which \ counts PG-side rejections after StartupMessage.", ), From ac43df0e05dd245dad4a31d78f10e71abd850134 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:42:08 +0300 Subject: [PATCH 45/72] fix(metrics): count silent auth_query startup_parameter drops Closes codex MED #10. Three failure paths in extract_startup_parameters silently dropped the per-user overlay without incrementing the dropped_total counter: the column had a non-text type (operator returned json/jsonb instead of casting), the JSON failed to parse, and the JSON parsed but the top-level value was not an object. Dashboards and alerts stayed clean while every refetch for the affected user shipped no overlay. Add three reasons to the counter: - auth_query_bad_type for the column type mismatch - auth_query_invalid_json for serde_json parse failure - auth_query_invalid_shape for non-object top-level value The Prometheus metric doc-comment, the generator-emitted reference row, and the help string now list every reason the runtime can emit. --- src/app/generate/docs.rs | 2 +- src/auth/auth_query.rs | 9 +++++++++ src/web/metrics/mod.rs | 12 ++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/app/generate/docs.rs b/src/app/generate/docs.rs index ac800c8dd..05b332f43 100644 --- a/src/app/generate/docs.rs +++ b/src/app/generate/docs.rs @@ -479,7 +479,7 @@ fn write_prometheus_metrics_section(out: &mut String) { let _ = writeln!(out, "| Metric | Description |"); let _ = writeln!(out, "|--------|-------------|"); let _ = writeln!(out, "| `pg_doorman_backend_startup_parameter_errors_total` | Counter by `(pool, sqlstate)`. Increments when PostgreSQL rejects a backend startup and the `ErrorResponse` names a startup parameter sent by pg_doorman. SQLSTATE class `57P` is excluded because Patroni-assisted fallback handles those errors. The failing parameter name and username are written to the warning log line, not to labels. pg_doorman first parses the common `parameter \"\"` phrase, then scans the message for any sent key in double quotes. If neither lookup finds a key, the counter is not incremented. |"); - let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman drops operator-supplied entries before sending `StartupMessage`. Reasons: `cascade_budget_exceeded`, `packet_cap_exceeded`, `auth_query_oversize`, `auth_query_overlay_oversize`, `auth_query_invalid_entry`, `dedicated_mode`. |\n"); + let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman drops operator-supplied entries before sending `StartupMessage`. Reasons: `cascade_budget_exceeded`, `packet_cap_exceeded`, `auth_query_oversize`, `auth_query_overlay_oversize`, `auth_query_bad_type`, `auth_query_invalid_json`, `auth_query_invalid_shape`, `auth_query_invalid_entry`, `dedicated_mode`. |\n"); // Server Metrics let _ = writeln!(out, "### Server Metrics\n"); diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index 43270b381..a992f1027 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -414,6 +414,9 @@ impl AuthQueryExecutor { parameters are ignored for this row.", ty = column.type_().name() ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[pool_name, "auth_query_bad_type"]) + .inc(); return std::collections::HashMap::new(); } }; @@ -457,6 +460,9 @@ impl AuthQueryExecutor { "[{username}@{pool_name}] auth_query startup_parameters: JSON parse failed: \ {e}; parameters ignored" ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[pool_name, "auth_query_invalid_json"]) + .inc(); return std::collections::HashMap::new(); } }; @@ -465,6 +471,9 @@ impl AuthQueryExecutor { "[{username}@{pool_name}] auth_query startup_parameters: top-level value is not a \ JSON object; ignored" ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[pool_name, "auth_query_invalid_shape"]) + .inc(); return std::collections::HashMap::new(); }; let mut out = std::collections::HashMap::new(); diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index ddaaef5ea..fc1b19592 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -505,6 +505,13 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// over budget, but the baseline alone fits. Keeps general/pool /// guardrails (statement_timeout, lock_timeout, …) for that /// user instead of stripping the operator cascade outright. +/// * `auth_query_bad_type` — the auth_query `startup_parameters` +/// column has a non-text type (likely `json`/`jsonb`); pg_doorman +/// reads it as text, so the row's overlay is dropped. Cast to +/// `::text` in the auth_query SELECT to fix. +/// * `auth_query_invalid_json` — the column value is not valid JSON. +/// * `auth_query_invalid_shape` — the column parses but the +/// top-level value is not a JSON object. /// * `auth_query_invalid_entry` — an individual entry in the /// auth_query JSON failed validation (reserved key, bad GUC name, /// null byte, non-string value). Incremented per offending entry. @@ -524,8 +531,9 @@ pub(crate) static STARTUP_PARAMETERS_DROPPED_TOTAL: Lazy = Lazy:: entries pg_doorman dropped before sending StartupMessage. \ Labels: pool, reason (cascade_budget_exceeded, \ packet_cap_exceeded, auth_query_oversize, \ - auth_query_overlay_oversize, auth_query_invalid_entry, \ - dedicated_mode). Distinct from \ + auth_query_overlay_oversize, auth_query_bad_type, \ + auth_query_invalid_json, auth_query_invalid_shape, \ + auth_query_invalid_entry, dedicated_mode). Distinct from \ pg_doorman_backend_startup_parameter_errors_total which \ counts PG-side rejections after StartupMessage.", ), From 1aa3fe34b3827f7642fc1f9333f796209c3cfadd Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:44:18 +0300 Subject: [PATCH 46/72] fix(metrics): unify startup_parameters_dropped_total on per-event units Closes codex MED #11. The counter mixed two units: dedicated_mode incremented by entry.startup_parameters.len() (entries dropped) while every other reason incremented by 1 (one drop event). auth_query_invalid_entry also fired once per offending JSON entry rather than once per parsed row. The result: rate by(reason) could not be compared across reasons - "dedicated_mode 50" vs "cascade_budget_exceeded 50" meant different things. Switch every reason to a single increment per drop event. One dedicated-mode row, one budget-exceeded backend spawn, one parsed row with any invalid entry - each is exactly one tick. Per-entry detail (which key was invalid, how many keys were dropped) stays in the warn log. The doc-comment now states the invariant up front so future call sites do not regress to inc_by. --- src/auth/auth_query.rs | 29 ++++++++++++++++++----------- src/web/metrics/mod.rs | 19 +++++++++++++------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index a992f1027..0db8303a9 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -478,6 +478,7 @@ impl AuthQueryExecutor { }; let mut out = std::collections::HashMap::new(); let scope = format!("auth_query.startup_parameters[user={username}]"); + let mut had_invalid_entry = false; for (k, v) in obj { match v { serde_json::Value::String(s) => { @@ -485,9 +486,7 @@ impl AuthQueryExecutor { probe.insert(k.clone(), s.clone()); if let Err(e) = crate::config::startup_parameters::validate(&probe, &scope) { warn!("[{pool_name}] {e}"); - crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL - .with_label_values(&[pool_name, "auth_query_invalid_entry"]) - .inc(); + had_invalid_entry = true; continue; } out.insert(k, s); @@ -505,12 +504,19 @@ impl AuthQueryExecutor { "[{username}@{pool_name}] auth_query startup_parameters: value for '{k}' \ is {kind}, not string; ignored" ); - crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL - .with_label_values(&[pool_name, "auth_query_invalid_entry"]) - .inc(); + had_invalid_entry = true; } } } + // One increment per parsed row that contained at least one + // invalid entry, matching every other reason on this counter + // so `rate by(reason)` is dimensionally consistent. Per-entry + // detail stays in the warn log. + if had_invalid_entry { + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[pool_name, "auth_query_invalid_entry"]) + .inc(); + } out } } @@ -647,13 +653,14 @@ impl AuthQueryCache { if !self.is_dedicated || entry.startup_parameters.is_empty() { return; } - // Every dropped entry contributes to the metric so operators can - // see the volume of per-user GUCs lost to dedicated mode without - // log scraping. The warn-log itself stays once per (pool, user) - // to keep the log readable. + // One increment per drop event (a single fetched row whose + // overlay was dropped because of dedicated mode), matching every + // other reason on this counter so `rate by(reason)` is + // dimensionally consistent. The warn log carries the same + // once-per-(pool, user) shape. crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL .with_label_values(&[self.pool_name.as_str(), "dedicated_mode"]) - .inc_by(entry.startup_parameters.len() as u64); + .inc(); if self .dedicated_warnings .insert(username.to_string(), ()) diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index fc1b19592..fb04ffcc9 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -486,7 +486,13 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// Counts cases where pg_doorman dropped operator-supplied /// `startup_parameters` *before* the StartupMessage went on the wire — /// the failure mode the per-pool `*_errors_total` counter cannot see -/// because PG never had a chance to reject. Labels: +/// because PG never had a chance to reject. Every reason increments +/// the counter by 1 per drop event (one backend spawn that dropped +/// the cascade, one parsed row that contained invalid entries, one +/// row whose overlay was ignored because of dedicated mode), so +/// `rate by(reason)` is dimensionally consistent regardless of how +/// many individual keys the offending row carried. Per-entry detail +/// goes to the warn log only. Labels: /// /// * `pool` — `@` identifier. /// * `reason` — bounded enum: @@ -512,13 +518,14 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// * `auth_query_invalid_json` — the column value is not valid JSON. /// * `auth_query_invalid_shape` — the column parses but the /// top-level value is not a JSON object. -/// * `auth_query_invalid_entry` — an individual entry in the -/// auth_query JSON failed validation (reserved key, bad GUC name, -/// null byte, non-string value). Incremented per offending entry. -/// * `dedicated_mode` — a per-user auth_query entry carried +/// * `auth_query_invalid_entry` — at least one entry in the parsed +/// auth_query JSON object failed validation (reserved key, bad +/// GUC name, null byte, non-string value). Incremented once per +/// parsed row that had any invalid entry. +/// * `dedicated_mode` — a per-user auth_query row carried /// startup_parameters, but the pool runs in dedicated auth_query /// mode (one shared backend across users) so the per-user overlay -/// was dropped. Incremented per dropped entry. +/// was dropped. Incremented once per such row. /// /// All cases also emit a `warn!` log line for human triage; the /// counter exists so dashboards and alerts can spot the silent drop From b59d724735cf1291d6bd4127a1fb6da17928060d Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:50:15 +0300 Subject: [PATCH 47/72] perf(pool): resolve startup_parameters once per fallback round MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes codex MED #14. run_fallback_round delegated to try_fallback_target per candidate, and try_fallback_target called resolved_startup_parameters() each time. A wave of N Patroni candidates therefore did N×{auth_query peek under DashMap shard read-lock, BTreeMap clone, size scan, overflow log + counter inc} for one client checkout - pure waste, since the cascade is host-independent. Resolve once at run_fallback_round entry and pass the borrowed map through race_wave to every try_fallback_target. Plain attempt and the optional sslmode=allow TLS retry both reuse the same map, so a config RELOAD landing between candidates still sees a coherent StartupMessage per round. --- src/pool/server_pool.rs | 46 ++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 26938378f..714b2d398 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -639,6 +639,15 @@ impl ServerPool { } }; + // Resolve the startup_parameters cascade once for the whole + // fallback round. Without this, a wave of N candidates would + // do N×{auth_query peek, BTreeMap clone, validation walk} + // for one client checkout — and the merge result is host- + // independent, so the per-candidate work was pure waste. + // `try_fallback_target` borrows this map for both the plain + // attempt and the optional sslmode=allow TLS retry. + let startup_parameters_round = self.resolved_startup_parameters(); + // Whitelist-cache hit: single target, race-of-one is just a startup. if matches!(source, super::fallback::TargetSource::WhitelistCache) { let target = match targets.into_iter().next() { @@ -663,7 +672,10 @@ impl ServerPool { crate::web::metrics::FALLBACK_CONNECTIONS_TOTAL .with_label_values(&[&self.address.pool_name]) .inc(); - return match self.try_fallback_target(&target).await { + return match self + .try_fallback_target(&target, &startup_parameters_round) + .await + { Ok(server) => { fallback.set_whitelisted(target.host, target.port, target.role); (Ok(server), source) @@ -694,7 +706,13 @@ impl ServerPool { format_target_list(&sync_targets), ); if let Some(server) = self - .race_wave(fallback, &sync_targets, &mut summary, source) + .race_wave( + fallback, + &sync_targets, + &mut summary, + source, + &startup_parameters_round, + ) .await { return (Ok(server), source); @@ -722,7 +740,13 @@ impl ServerPool { format_target_list(&other_targets), ); if let Some(server) = self - .race_wave(fallback, &other_targets, &mut summary, source) + .race_wave( + fallback, + &other_targets, + &mut summary, + source, + &startup_parameters_round, + ) .await { return (Ok(server), source); @@ -764,6 +788,7 @@ impl ServerPool { targets: &[super::fallback::FallbackTarget], summary: &mut FailureSummary, source: super::fallback::TargetSource, + startup_parameters: &BTreeMap, ) -> Option { // We only count "we attempted to use fallback" once per wave, on // entry — not per candidate. The metric measures fallback usage @@ -776,7 +801,7 @@ impl ServerPool { let futures: Vec>> = targets .iter() - .map(|t| Box::pin(self.try_fallback_target(t)) as _) + .map(|t| Box::pin(self.try_fallback_target(t, startup_parameters)) as _) .collect(); match race_first_success(futures).await { @@ -830,6 +855,7 @@ impl ServerPool { async fn try_fallback_target( &self, target: &super::fallback::FallbackTarget, + startup_parameters: &BTreeMap, ) -> Result { // Use the fallback_connect_timeout for fallback startup deadlines — // the same scale as the TCP-probe and per-candidate cooldown window. @@ -849,12 +875,12 @@ impl ServerPool { )); stats.register(stats.clone()); - // Resolve once: the optional sslmode=allow retry below must see the - // same map as the plain attempt for the same reasons noted in - // `create()`. The fallback target's pool name matches `self`, so + // `startup_parameters` is resolved once per fallback round by + // the caller (`run_fallback_round` / `race_wave`), so a wave + // of N candidates does N×0 cascade resolves and merges instead + // of N×1. The fallback target's pool name matches `self`, so // the per-pool cascade still applies even though we are talking // to a different physical host than `self.address.host`. - let startup_parameters = self.resolved_startup_parameters(); let result = startup_with_timeout( fallback_timeout, @@ -871,7 +897,7 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - &startup_parameters, + startup_parameters, ), ) .await; @@ -922,7 +948,7 @@ impl ServerPool { self.prepared_statement_cache_size, self.application_name.clone(), self.session_mode, - &startup_parameters, + startup_parameters, ), ) .await; From 2c3e8c2531f3f4b26c84437403413ee03f7bc2b5 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:51:44 +0300 Subject: [PATCH 48/72] docs(metrics): clarify pool label is the pool name, not user@database Closes codex MED #9. The doc-comments on backend_startup_parameter_errors_total and startup_parameters_dropped_total claimed the `pool` label was a `@` identifier. Emit sites pass `address.pool_name`, which in the default mapping is the PostgreSQL database name - a multi-user database collapses into a single series, and the docs led operators to expect per-user attribution that never existed. State the actual contract: one series per pool name. Per-user attribution still lives in the warn log line that accompanies every counter increment. --- src/web/metrics/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index fb04ffcc9..7e34c52b8 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -461,7 +461,11 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// returned an `ErrorResponse` that names a key the pool actually sent in /// `StartupMessage`. Labels: /// -/// * `pool` — `@` identifier. +/// * `pool` — pool name as it appears in `pools.` of the config +/// (in the default mapping this is the PostgreSQL database name). +/// The label is *not* `@`: pg_doorman emits one +/// series per pool name, so a multi-user database collapses into a +/// single row. Per-user attribution lives in the warn log line. /// * `sqlstate` — PG SQLSTATE on the rejection (`22023`, `42704`, /// `42501`, `55P02`, or any other code under the startup-parameter /// family — pg_doorman does not pre-filter by SQLSTATE). @@ -494,7 +498,11 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// many individual keys the offending row carried. Per-entry detail /// goes to the warn log only. Labels: /// -/// * `pool` — `@` identifier. +/// * `pool` — pool name as it appears in `pools.` of the config +/// (in the default mapping this is the PostgreSQL database name). +/// The label is *not* `@`: pg_doorman emits one +/// series per pool name, so a multi-user database collapses into a +/// single row. Per-user attribution lives in the warn log line. /// * `reason` — bounded enum: /// * `cascade_budget_exceeded` — the merged general+pool+auth_query /// map exceeded the operator budget (`MAX_OPERATOR_BUDGET`, 9 488 From 963027bba9958d2a29c0e84d46221708ce0eb3ad Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:53:58 +0300 Subject: [PATCH 49/72] perf(auth_query): share startup_parameters via Arc on cache hit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes codex MED #15. CacheEntry.startup_parameters was a HashMap, so every cache hit cloned the whole map. For a user whose auth_query row carries a dozen extension GUCs that is O(map) copies per connection (cache hit on get_or_fetch, again on every entry.clone() inside cache invalidation/refetch). The hot path lives under a DashMap shard read lock; a long clone there extends the lock window proportionally. Wrap the field in Arc>. Cache hits now do two atomic increments instead of a deep clone. dedicated_mode_filter drops the overlay by assigning a fresh empty Arc rather than .clear() — the existing Arc is shared with whoever already cloned the entry. Per-user overlay snapshot held on ServerPool is a separate Arc; that path was already zero-clone since the BLOCKER #2 fix. No change there. --- src/auth/auth_query.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index 0db8303a9..aceed691b 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -544,7 +544,12 @@ pub struct CacheEntry { /// Per-user startup parameters returned by the optional auth_query /// `startup_parameters` JSON column. Empty when the column is absent, /// empty/NULL, or filtered out in dedicated auth_query mode. - pub startup_parameters: std::collections::HashMap, + /// + /// Wrapped in `Arc` so cache hits do not clone the underlying map. + /// For a user with a wide row (a dozen extension GUCs) every + /// `cache.get_or_fetch` previously paid an `O(map)` clone; the + /// `Arc::clone` here is two atomic increments instead. + pub startup_parameters: Arc>, } impl CacheEntry { @@ -555,7 +560,7 @@ impl CacheEntry { is_negative: false, last_refetch_at: None, client_key: None, - startup_parameters: std::collections::HashMap::new(), + startup_parameters: Arc::new(std::collections::HashMap::new()), } } @@ -566,7 +571,7 @@ impl CacheEntry { is_negative: true, last_refetch_at: None, client_key: None, - startup_parameters: std::collections::HashMap::new(), + startup_parameters: Arc::new(std::collections::HashMap::new()), } } @@ -672,7 +677,7 @@ impl AuthQueryCache { pool = self.pool_name ); } - entry.startup_parameters.clear(); + entry.startup_parameters = Arc::new(std::collections::HashMap::new()); } /// When a fresh auth_query fetch produces a per-user @@ -774,7 +779,7 @@ impl AuthQueryCache { Ok(Some((password_hash, startup_params))) => { self.inc(|s| &s.cache_misses); let mut entry = CacheEntry::positive(password_hash); - entry.startup_parameters = startup_params; + entry.startup_parameters = Arc::new(startup_params); self.dedicated_mode_filter(&mut entry, username); // Publish the fresh entry first so any concurrent // create_dynamic_pool peeks the new overlay, then drop the @@ -849,7 +854,7 @@ impl AuthQueryCache { match self.executor.fetch_credentials(username).await { Ok(Some((password_hash, startup_params))) => { let mut entry = CacheEntry::positive(password_hash); - entry.startup_parameters = startup_params; + entry.startup_parameters = Arc::new(startup_params); entry.last_refetch_at = Some(Instant::now()); self.dedicated_mode_filter(&mut entry, username); // Insert before drop — see comment in get_or_fetch. From a60eaedf76d255b90c7516a474c078c9c19c9edb Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 12:57:20 +0300 Subject: [PATCH 50/72] fix(grafana): drop user/database selectors from startup_parameters panels Closes codex MED #8. The three Row 19 panels selected on `instance, user, database`, but the underlying counters (pg_doorman_backend_startup_parameter_errors_total, pg_doorman_startup_parameters_dropped_total) carry only `pool` + `sqlstate`/`reason` labels. Any non-".*" value on the $user or $database template variable filtered the result to nothing, so even when the demo or a real deployment produced traffic the panels returned empty. The smoke-test allow_empty entry then masked the broken panel. Use an instance-only selector `SI` for the three panels and document the contract in the panel description ("filters on $user/$database do not apply"). The allow_empty entries stay legitimate for the demo (it ships no operator startup_parameters) but now the panels fill in the moment real traffic appears, regardless of the template variables' state. --- grafana/generate_dashboard.py | 17 +++++++++++------ grafana/pg_doorman.json | 12 ++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/grafana/generate_dashboard.py b/grafana/generate_dashboard.py index 53d6ad878..7acd8aa95 100644 --- a/grafana/generate_dashboard.py +++ b/grafana/generate_dashboard.py @@ -141,6 +141,11 @@ def expanded_row(title: str): # Selector shorthand S = 'instance=~"$instance", user=~"$user", database=~"$database"' SD = 'instance=~"$instance", database=~"$database"' +# Selector for metrics that carry only `pool` (not `user`/`database`), +# e.g. startup_parameters counters. Filtering on `user`/`database` +# yields an empty series even when traffic exists, because those +# labels are not in the metric's label set. +SI = 'instance=~"$instance"' # --------------------------------------------------------------------------- # Row 1: Overview @@ -730,29 +735,29 @@ def expanded_row(title: str): p_sp_errors_by_sqlstate = ts_panel( "PG-Side Rejections by SQLSTATE", [ prom( - f'sum by (sqlstate) (rate(pg_doorman_backend_startup_parameter_errors_total{{{S}}}[$__rate_interval]))', + f'sum by (sqlstate) (rate(pg_doorman_backend_startup_parameter_errors_total{{{SI}}}[$__rate_interval]))', "{{sqlstate}}", ), ], unit="ops", w=12, - desc="Per-pool rate of backend startups PG rejected because of an operator-supplied parameter. Split by SQLSTATE: 22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param. Non-zero for the same pool over a few minutes means every connect through that pool fails on the same operator GUC — fix general/pool/auth_query.", + desc="Per-pool rate of backend startups PG rejected because of an operator-supplied parameter. Split by SQLSTATE: 22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param. Non-zero for the same pool over a few minutes means every connect through that pool fails on the same operator GUC — fix general/pool/auth_query. Filters on $user/$database do not apply — the counter has only the `pool` label.", ) p_sp_errors_by_pool = ts_panel( "PG-Side Rejections by Pool", [ prom( - f'sum by (pool) (rate(pg_doorman_backend_startup_parameter_errors_total{{{S}}}[$__rate_interval]))', + f'sum by (pool) (rate(pg_doorman_backend_startup_parameter_errors_total{{{SI}}}[$__rate_interval]))', "{{pool}}", ), ], unit="ops", w=12, - desc="Same counter aggregated by pool. The pool name shows which user@database is affected; check pg_doorman warn log for the parameter name and username.", + desc="Same counter aggregated by pool. The pool name shows which logical pool is affected; per-user attribution lives in the pg_doorman warn log.", ) p_sp_dropped_by_reason = ts_panel( "Pre-Wire Drops by Reason", [ prom( - f'sum by (reason) (rate(pg_doorman_startup_parameters_dropped_total{{{S}}}[$__rate_interval]))', + f'sum by (reason) (rate(pg_doorman_startup_parameters_dropped_total{{{SI}}}[$__rate_interval]))', "{{reason}}", ), ], unit="ops", w=24, - desc="Operator-supplied entries pg_doorman dropped BEFORE the StartupMessage went on the wire — the failure mode the PG-side counter above cannot see. Reasons: cascade_budget_exceeded (merged map past 9 488 bytes), packet_cap_exceeded (full packet past PG MAX_STARTUP_PACKET_LENGTH 10 000 bytes), auth_query_oversize (per-user JSON column past operator budget), auth_query_invalid_entry (one JSON entry failed validation), dedicated_mode (per-user GUC ignored because the pool shares one backend across users). Non-zero on any reason needs operator attention — backends are connecting with PG defaults instead of the configured cascade.", + desc="Operator-supplied entries pg_doorman dropped BEFORE the StartupMessage went on the wire — the failure mode the PG-side counter above cannot see. Reasons: cascade_budget_exceeded (merged map past 9 488 bytes), packet_cap_exceeded (full packet past PG MAX_STARTUP_PACKET_LENGTH 10 000 bytes), auth_query_oversize (per-user JSON column past operator budget), auth_query_overlay_oversize (overlay pushes cascade over budget but baseline alone fits — pg_doorman ships the baseline), auth_query_bad_type / auth_query_invalid_json / auth_query_invalid_shape (column type, JSON parse, or non-object payload), auth_query_invalid_entry (one or more JSON entries failed validation), dedicated_mode (per-user GUC ignored because the pool shares one backend across users). Non-zero on any reason needs operator attention. Filters on $user/$database do not apply — the counter has only `pool` and `reason` labels.", ) # --------------------------------------------------------------------------- diff --git a/grafana/pg_doorman.json b/grafana/pg_doorman.json index 18ab7d11a..7f77e562d 100644 --- a/grafana/pg_doorman.json +++ b/grafana/pg_doorman.json @@ -3080,13 +3080,13 @@ }, "targets": [ { - "expr": "sum by (sqlstate) (rate(pg_doorman_backend_startup_parameter_errors_total{instance=~\"$instance\", user=~\"$user\", database=~\"$database\"}[$__rate_interval]))", + "expr": "sum by (sqlstate) (rate(pg_doorman_backend_startup_parameter_errors_total{instance=~\"$instance\"}[$__rate_interval]))", "refId": "", "legendFormat": "{{sqlstate}}" } ], "title": "PG-Side Rejections by SQLSTATE", - "description": "Per-pool rate of backend startups PG rejected because of an operator-supplied parameter. Split by SQLSTATE: 22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param. Non-zero for the same pool over a few minutes means every connect through that pool fails on the same operator GUC \u2014 fix general/pool/auth_query.", + "description": "Per-pool rate of backend startups PG rejected because of an operator-supplied parameter. Split by SQLSTATE: 22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param. Non-zero for the same pool over a few minutes means every connect through that pool fails on the same operator GUC \u2014 fix general/pool/auth_query. Filters on $user/$database do not apply \u2014 the counter has only the `pool` label.", "datasource": { "uid": "prometheus" }, @@ -3126,13 +3126,13 @@ }, "targets": [ { - "expr": "sum by (pool) (rate(pg_doorman_backend_startup_parameter_errors_total{instance=~\"$instance\", user=~\"$user\", database=~\"$database\"}[$__rate_interval]))", + "expr": "sum by (pool) (rate(pg_doorman_backend_startup_parameter_errors_total{instance=~\"$instance\"}[$__rate_interval]))", "refId": "", "legendFormat": "{{pool}}" } ], "title": "PG-Side Rejections by Pool", - "description": "Same counter aggregated by pool. The pool name shows which user@database is affected; check pg_doorman warn log for the parameter name and username.", + "description": "Same counter aggregated by pool. The pool name shows which logical pool is affected; per-user attribution lives in the pg_doorman warn log.", "datasource": { "uid": "prometheus" }, @@ -3172,13 +3172,13 @@ }, "targets": [ { - "expr": "sum by (reason) (rate(pg_doorman_startup_parameters_dropped_total{instance=~\"$instance\", user=~\"$user\", database=~\"$database\"}[$__rate_interval]))", + "expr": "sum by (reason) (rate(pg_doorman_startup_parameters_dropped_total{instance=~\"$instance\"}[$__rate_interval]))", "refId": "", "legendFormat": "{{reason}}" } ], "title": "Pre-Wire Drops by Reason", - "description": "Operator-supplied entries pg_doorman dropped BEFORE the StartupMessage went on the wire \u2014 the failure mode the PG-side counter above cannot see. Reasons: cascade_budget_exceeded (merged map past 9 488 bytes), packet_cap_exceeded (full packet past PG MAX_STARTUP_PACKET_LENGTH 10 000 bytes), auth_query_oversize (per-user JSON column past operator budget), auth_query_invalid_entry (one JSON entry failed validation), dedicated_mode (per-user GUC ignored because the pool shares one backend across users). Non-zero on any reason needs operator attention \u2014 backends are connecting with PG defaults instead of the configured cascade.", + "description": "Operator-supplied entries pg_doorman dropped BEFORE the StartupMessage went on the wire \u2014 the failure mode the PG-side counter above cannot see. Reasons: cascade_budget_exceeded (merged map past 9 488 bytes), packet_cap_exceeded (full packet past PG MAX_STARTUP_PACKET_LENGTH 10 000 bytes), auth_query_oversize (per-user JSON column past operator budget), auth_query_overlay_oversize (overlay pushes cascade over budget but baseline alone fits \u2014 pg_doorman ships the baseline), auth_query_bad_type / auth_query_invalid_json / auth_query_invalid_shape (column type, JSON parse, or non-object payload), auth_query_invalid_entry (one or more JSON entries failed validation), dedicated_mode (per-user GUC ignored because the pool shares one backend across users). Non-zero on any reason needs operator attention. Filters on $user/$database do not apply \u2014 the counter has only `pool` and `reason` labels.", "datasource": { "uid": "prometheus" }, From 8394efd4d527af970120cfac0a880181c90c5504 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 13:01:50 +0300 Subject: [PATCH 51/72] fix(admin/web): cross-check SHOW STARTUP_PARAMETERS against wire-ready map Closes codex MED #12. SHOW STARTUP_PARAMETERS and /api/pools rendered the configured cascade view (current general+pool+auth_query) without referencing the map pg_doorman actually ships in StartupMessage. Two real cases left a DBA looking at a misleading "active" entry: - The pool's frozen baseline / per-user overlay snapshot is behind the live config (RELOAD has not yet recycled, or the auth_query cache has not refetched). The next backend spawn still ships the old value. - The runtime budget/packet check dropped the entry on the most recent spawn (cascade_budget_exceeded / packet_cap_exceeded / auth_query_overlay_oversize); admin still showed it as configured. effective_startup_parameters_with_sources now cross-checks every configured entry against resolved_startup_parameters() and tags it with an ApplicationState: applied, dropped_due_to_budget, or stale. SHOW gains a `state` column, /api/pools' StartupParameterDto gains a `state` field next to `source`. Anonymous /api/pools still hides `value` per HIGH #5; `state` is operator-facing diagnostics, not config payload, so it stays in the anonymous view. --- src/admin/show.rs | 9 +++++- src/pool/inner.rs | 10 +++++-- src/pool/server_pool.rs | 56 ++++++++++++++++++++++++++++++------ src/pool/startup_resolver.rs | 29 +++++++++++++++++++ src/web/routes/dto.rs | 33 +++++++++++++++++---- 5 files changed, 119 insertions(+), 18 deletions(-) diff --git a/src/admin/show.rs b/src/admin/show.rs index fe0f6dd39..f19a91478 100644 --- a/src/admin/show.rs +++ b/src/admin/show.rs @@ -835,6 +835,12 @@ where ("parameter", DataType::Text), ("value", DataType::Text), ("source", DataType::Text), + // applied | dropped_due_to_budget | stale — `applied` means the + // value lands on the wire, `dropped_due_to_budget` means the + // runtime cascade overflows, and `stale` means the pool's + // frozen snapshot is behind the live config (RELOAD or auth_query + // refetch will catch up). + ("state", DataType::Text), ]; let mut res = BytesMut::new(); @@ -846,13 +852,14 @@ where for (identifier, pool) in entries { let effective = pool.database.effective_startup_parameters_with_sources(); - for (parameter, (value, source)) in effective { + for (parameter, (value, source, state)) in effective { res.put(data_row(&[ identifier.user.clone(), identifier.db.clone(), parameter, value, source.as_str().to_string(), + state.as_str().to_string(), ])); } } diff --git a/src/pool/inner.rs b/src/pool/inner.rs index 2b49c3ddb..6618bac40 100644 --- a/src/pool/inner.rs +++ b/src/pool/inner.rs @@ -1551,8 +1551,14 @@ impl Pool { /// `/api/pools` JSON share one resolver. pub fn effective_startup_parameters_with_sources( &self, - ) -> std::collections::BTreeMap - { + ) -> std::collections::BTreeMap< + String, + ( + String, + super::startup_resolver::ParameterSource, + super::startup_resolver::ApplicationState, + ), + > { self.inner .server_pool .effective_startup_parameters_with_sources() diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index 714b2d398..f91f778c5 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -21,6 +21,7 @@ use crate::stats::ServerStats; use crate::utils::format_duration_ms; use super::errors::{RecycleError, RecycleResult}; +use super::startup_resolver::ApplicationState; use super::types::Metrics; use super::ClientServerMap; @@ -365,15 +366,41 @@ impl ServerPool { &self.address } - /// Return the effective startup parameter cascade with the winning source - /// layer for each key. Used by `SHOW STARTUP_PARAMETERS` and `/api/pools`. + /// Return the effective startup parameter cascade with the winning + /// source layer **and** the application state for each key. Used by + /// `SHOW STARTUP_PARAMETERS` and `/api/pools`. /// - /// This does not apply the runtime budget-overflow drop. It reports the - /// configured cascade so the operator can see the oversized input. + /// The cascade view comes from the live config and the auth_query + /// cache, so it reflects what the operator just edited. The + /// application state cross-checks each key against + /// `resolved_startup_parameters()` — the same wire-ready map that + /// `Server::startup` will ship for the next backend spawn — so the + /// admin view can flag keys that look configured but will not + /// actually leave the wire: + /// + /// * `Applied` — same key/value in both views; the next spawn + /// ships it. + /// * `DroppedDueToBudget` — the wire map omits the key. The runtime + /// budget/packet check dropped the operator cascade (or the + /// overlay) on the most recent spawn; same will happen on the + /// next one until the operator shrinks the config. + /// * `Stale` — the key is in the wire map but with a different + /// value, which means the frozen `base_startup_parameters` / + /// `per_user_startup_overlay` Arc on this pool was captured + /// before the operator's latest edit. RELOAD has not yet + /// recycled the pool (general/pool change) or the auth_query + /// cache has not refetched (per-user change). The next spawn + /// ships the stale value, not the configured one. pub fn effective_startup_parameters_with_sources( &self, - ) -> std::collections::BTreeMap - { + ) -> std::collections::BTreeMap< + String, + ( + String, + super::startup_resolver::ParameterSource, + ApplicationState, + ), + > { let cfg = crate::config::config_arc(); let pool_params = cfg .pools @@ -381,7 +408,6 @@ impl ServerPool { .map(|p| &p.startup_parameters) .cloned() .unwrap_or_default(); - // Snapshot the per-user auth_query map once for the admin/web view. let auth_query_params: Option> = match super::get_auth_query_state(&self.address.pool_name) { Some(state) if !state.config.is_dedicated_mode() => { @@ -389,11 +415,23 @@ impl ServerPool { } _ => None, }; - super::startup_resolver::resolve_with_sources( + let configured = super::startup_resolver::resolve_with_sources( &cfg.general.startup_parameters, &pool_params, auth_query_params.as_ref(), - ) + ); + let wire = self.resolved_startup_parameters(); + configured + .into_iter() + .map(|(k, (v, src))| { + let state = match wire.get(&k) { + Some(wire_v) if wire_v == &v => ApplicationState::Applied, + Some(_) => ApplicationState::Stale, + None => ApplicationState::DroppedDueToBudget, + }; + (k, (v, src, state)) + }) + .collect() } /// Resolve the operator-supplied startup_parameters map that this pool diff --git a/src/pool/startup_resolver.rs b/src/pool/startup_resolver.rs index 962b53f35..895da0408 100644 --- a/src/pool/startup_resolver.rs +++ b/src/pool/startup_resolver.rs @@ -51,6 +51,35 @@ impl ParameterSource { } } +/// Wire-application state for an entry returned by +/// `ServerPool::effective_startup_parameters_with_sources`. Lets the +/// admin/Web UI flag entries that the operator configured but the +/// runtime will not actually ship in `StartupMessage`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ApplicationState { + /// Configured value matches the wire-ready map; the next backend + /// spawn will ship this key/value. + Applied, + /// Runtime dropped the key (operator cascade exceeded the budget + /// or the packet cap on the most recent backend spawn — the + /// `*_dropped_total` counter ticked on the same spawn). + DroppedDueToBudget, + /// Wire map ships a different value than the live config has. The + /// pool's frozen baseline / overlay snapshot is stale — RELOAD or + /// auth_query cache refetch has not yet recycled this pool. + Stale, +} + +impl ApplicationState { + pub fn as_str(self) -> &'static str { + match self { + ApplicationState::Applied => "applied", + ApplicationState::DroppedDueToBudget => "dropped_due_to_budget", + ApplicationState::Stale => "stale", + } + } +} + /// Same cascade as [`resolve`], but carries the layer that contributed each /// winning value. Used by `SHOW STARTUP_PARAMETERS` and `/api/pools` so an /// operator can see "this `work_mem` came from the pool, that `lock_timeout` diff --git a/src/web/routes/dto.rs b/src/web/routes/dto.rs index 50a347d3a..222933d56 100644 --- a/src/web/routes/dto.rs +++ b/src/web/routes/dto.rs @@ -171,19 +171,32 @@ pub(crate) struct StartupParameterDto { #[serde(skip_serializing_if = "Option::is_none")] pub value: Option, pub source: &'static str, + /// `applied` / `dropped_due_to_budget` / `stale` — see + /// `pool::startup_resolver::ApplicationState`. Tells an operator + /// whether the configured cascade entry will actually ship in the + /// next backend `StartupMessage` for this pool. + pub state: &'static str, } impl StartupParameterDto { pub fn from_resolved( - merged: BTreeMap, + merged: BTreeMap< + String, + ( + String, + crate::pool::startup_resolver::ParameterSource, + crate::pool::startup_resolver::ApplicationState, + ), + >, reveal_values: bool, ) -> Vec { merged .into_iter() - .map(|(parameter, (value, source))| StartupParameterDto { + .map(|(parameter, (value, source, state))| StartupParameterDto { parameter, value: if reveal_values { Some(value) } else { None }, source: source.as_str(), + state: state.as_str(), }) .collect() } @@ -192,17 +205,25 @@ impl StartupParameterDto { #[cfg(test)] mod startup_parameter_dto_tests { use super::*; - use crate::pool::startup_resolver::ParameterSource; + use crate::pool::startup_resolver::{ApplicationState, ParameterSource}; - fn sample_merged() -> BTreeMap { + fn sample_merged() -> BTreeMap { let mut m = BTreeMap::new(); m.insert( "application_name".to_string(), - ("tenant-a-audit".to_string(), ParameterSource::Pool), + ( + "tenant-a-audit".to_string(), + ParameterSource::Pool, + ApplicationState::Applied, + ), ); m.insert( "statement_timeout".to_string(), - ("30s".to_string(), ParameterSource::General), + ( + "30s".to_string(), + ParameterSource::General, + ApplicationState::Applied, + ), ); m } From 03ed3e9c4c1ff4e71d365d3e5af43e91dab77e92 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 13:09:02 +0300 Subject: [PATCH 52/72] perf: single-pass packet sizing + Arc-shared operator key set Closes codex LOW #16 (triple scan) and LOW #18 (HashSet per spawn). LOW #16. resolved_startup_parameters previously walked the merged cascade twice on the hot path - once through serialized_bytes for the operator budget and again through full_packet_bytes for the PG packet cap. The new packet_and_body_bytes helper returns both totals from one pass over the BTreeMap, halving the per-spawn map walk for the common no-overflow case and also for the baseline-only retry after auth_query overlay oversize. LOW #18. Server.operator_managed_startup_keys is now Arc>. Pools that ship no operator parameters - the overwhelming default - hand every spawn the same static Arc instead of allocating a fresh empty HashSet per backend. Non-empty pools still allocate the set per spawn for now; the caller-side share is a follow-up because it requires threading the Arc through Server::startup signature. --- src/config/startup_parameters.rs | 32 +++++++++++++++++++++++++------- src/pool/server_pool.rs | 6 ++---- src/server/server_backend.rs | 30 ++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/config/startup_parameters.rs b/src/config/startup_parameters.rs index c63f89512..0c7b6e2f9 100644 --- a/src/config/startup_parameters.rs +++ b/src/config/startup_parameters.rs @@ -124,22 +124,40 @@ pub fn full_packet_bytes( application_name: &str, extras: &BTreeMap, ) -> usize { - let mut total = 4usize + 4; // length prefix + protocol version - total += b"user\0".len() + user.len() + 1; - total += b"database\0".len() + database.len() + 1; + packet_and_body_bytes(user, database, application_name, extras).0 +} + +/// Single-pass variant that returns both the full StartupMessage byte +/// length and the body-only byte count (what `serialized_bytes` reports +/// for the operator-supplied map). Used by the runtime budget/packet +/// guard to avoid walking the map three times per backend spawn. +pub fn packet_and_body_bytes( + user: &str, + database: &str, + application_name: &str, + extras: &BTreeMap, +) -> (usize, usize) { + let mut packet = 4usize + 4; // length prefix + protocol version + packet += b"user\0".len() + user.len() + 1; + packet += b"database\0".len() + database.len() + 1; let effective_app_name = extras .get("application_name") .map(String::as_str) .unwrap_or(application_name); - total += b"application_name\0".len() + effective_app_name.len() + 1; + packet += b"application_name\0".len() + effective_app_name.len() + 1; + let mut body = 0usize; for (key, value) in extras { + // `serialized_bytes` counts every operator-supplied pair, + // including `application_name` — keep the same accounting so + // the budget check stays comparable across callers. + body += key.len() + 1 + value.len() + 1; if key == "application_name" { continue; } - total += key.len() + 1 + value.len() + 1; + packet += key.len() + 1 + value.len() + 1; } - total += 1; // parameter-list terminator - total + packet += 1; // parameter-list terminator + (packet, body) } #[cfg(test)] diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index f91f778c5..f71fdc7ab 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -483,8 +483,7 @@ impl ServerPool { .as_deref() .unwrap_or(self.user.username.as_str()); - let body_bytes = sp::serialized_bytes(&merged); - let packet_bytes = sp::full_packet_bytes( + let (packet_bytes, body_bytes) = sp::packet_and_body_bytes( username_for_wire, &self.database, &self.application_name, @@ -502,8 +501,7 @@ impl ServerPool { let has_overlay = !self.per_user_startup_overlay.is_empty(); if has_overlay { let baseline = &*self.base_startup_parameters; - let baseline_body = sp::serialized_bytes(baseline); - let baseline_packet = sp::full_packet_bytes( + let (baseline_packet, baseline_body) = sp::packet_and_body_bytes( username_for_wire, &self.database, &self.application_name, diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index 9cf0a0ee5..1e9ed6023 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -168,7 +168,18 @@ pub struct Server { /// for this backend. They become `pg_settings.reset_val` for the /// session, so `sync_parameters` must not push a client-side value over /// them on checkout — the operator decision wins over the client. - operator_managed_startup_keys: HashSet, + /// `Arc` because every spawn from the same pool sees the same set; + /// without the share each backend allocated its own `HashSet`. + operator_managed_startup_keys: Arc>, +} + +/// Shared empty key set so pools that don't use `startup_parameters` +/// hand every backend the same `Arc` instead of allocating a +/// new empty `HashSet` per spawn. +fn empty_operator_keys() -> Arc> { + static EMPTY: once_cell::sync::Lazy>> = + once_cell::sync::Lazy::new(|| Arc::new(HashSet::new())); + EMPTY.clone() } impl std::fmt::Display for Server { @@ -1047,15 +1058,18 @@ impl Server { phase_started.elapsed().as_secs_f64(), ); - // No backing-table allocation for pools that don't use - // the feature: HashSet::new() is empty until the first - // insert. Operators on the typical zero-param path pay - // no per-spawn HashSet cost. - let operator_managed_startup_keys: HashSet = + // The empty case is a shared `Arc` static, so + // pools that don't use the feature pay zero allocation + // per spawn. The non-empty case still allocates once per + // spawn — the caller in pool/server_pool.rs knows the + // map shape but does not currently pass an already-Arc'd + // HashSet through, and lifting the construction up there + // is a larger refactor than this commit warrants. + let operator_managed_startup_keys: Arc> = if startup_parameters.is_empty() { - HashSet::new() + empty_operator_keys() } else { - startup_parameters.keys().cloned().collect() + Arc::new(startup_parameters.keys().cloned().collect()) }; let server = Server { From 26853fa879d8e5f34c06f71994f35d068ede025d Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 13:10:22 +0300 Subject: [PATCH 53/72] perf(auth_query): validate JSON entries without per-entry BTreeMap Closes codex LOW #17. parse_startup_parameters_text built a one-entry BTreeMap around every (key, value) pair before passing it to startup_parameters:: validate(), just to reach the per-key/per-value checks inside. For a wide auth_query row that allocated and dropped one BTreeMap per key on cache miss / refetch. Expose validate_entry(&str, &str, &str) on the startup_parameters module and call it directly on the borrowed JSON pair. The output HashMap is now also sized to obj.len() up front, so wide rows do not pay the grow-and-rehash cycle either. --- src/auth/auth_query.rs | 8 ++++---- src/config/startup_parameters.rs | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/auth/auth_query.rs b/src/auth/auth_query.rs index aceed691b..9a0eebd87 100644 --- a/src/auth/auth_query.rs +++ b/src/auth/auth_query.rs @@ -476,15 +476,15 @@ impl AuthQueryExecutor { .inc(); return std::collections::HashMap::new(); }; - let mut out = std::collections::HashMap::new(); + let mut out = std::collections::HashMap::with_capacity(obj.len()); let scope = format!("auth_query.startup_parameters[user={username}]"); let mut had_invalid_entry = false; for (k, v) in obj { match v { serde_json::Value::String(s) => { - let mut probe = std::collections::BTreeMap::new(); - probe.insert(k.clone(), s.clone()); - if let Err(e) = crate::config::startup_parameters::validate(&probe, &scope) { + if let Err(e) = + crate::config::startup_parameters::validate_entry(&k, &s, &scope) + { warn!("[{pool_name}] {e}"); had_invalid_entry = true; continue; diff --git a/src/config/startup_parameters.rs b/src/config/startup_parameters.rs index 0c7b6e2f9..dda9dd4d7 100644 --- a/src/config/startup_parameters.rs +++ b/src/config/startup_parameters.rs @@ -53,6 +53,17 @@ pub fn validate(map: &BTreeMap, scope: &str) -> Result<(), Error validate_total_size(map, scope) } +/// Validate a single borrowed `(key, value)` pair the same way [`validate`] +/// would. Used by the auth_query JSON parser to check entries inline +/// without building a one-element `BTreeMap` for each one. The total-size +/// check is *not* applied here — that gate runs once over the parent +/// map in [`validate`] (config load) or against the merged cascade at +/// runtime in `ServerPool::resolved_startup_parameters`. +pub fn validate_entry(key: &str, value: &str, scope: &str) -> Result<(), Error> { + validate_key(key, scope)?; + validate_value(key, value, scope) +} + fn validate_key(key: &str, scope: &str) -> Result<(), Error> { if key.is_empty() { return Err(Error::BadConfig(format!("{scope}: empty key"))); From 3f03823a67111dd19d087ea53184aa2a3f4c405c Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 13:41:11 +0300 Subject: [PATCH 54/72] ci(bdd): cap BDD matrix at 4 parallel suites GitHub-hosted runners share CPU; with 20+ BDD suites fanning out the timing-sensitive scenarios (SCRAM passthrough reconnect, sleep-based retain windows) routinely lost their margin and reported a flake of the form `tls required but server does not support tls` on the fallback retry. The same scenarios pass deterministically locally and on master. Cap matrix concurrency at 4 so each suite gets a less contested runner; total wall-clock grows but each job stops racing itself off the runner. --- .github/workflows/bdd-tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/bdd-tests.yml b/.github/workflows/bdd-tests.yml index 454068334..e9aa417db 100644 --- a/.github/workflows/bdd-tests.yml +++ b/.github/workflows/bdd-tests.yml @@ -222,6 +222,12 @@ jobs: packages: read strategy: fail-fast: false + # GitHub-hosted runners share CPU under heavy matrix fan-out, and + # the timing-sensitive scenarios (sleep-based retain/lifetime + # waits, SCRAM passthrough reconnect) lose their margin when 20+ + # BDD jobs run in parallel. Cap concurrency so each suite gets a + # less contested runner. + max-parallel: 4 matrix: suite: - { name: "Go", cargo: "test --test bdd -- --tags @go" } From 40a5822139665a1a15fdcee945d9bfda382d503c Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 13:47:45 +0300 Subject: [PATCH 55/72] fix(startup_parameters): reject role and session_authorization Closes codex BLOCKER #1 (fresh review 2026-05-12). `RESERVED_KEYS` blocked `user`, `database`, `replication`, and `options` but let `role` and `session_authorization` through. Those two are not benign session defaults - they change PostgreSQL authorization state and become `pg_settings.reset_val` for the backend. After an operator injects `role` through `startup_parameters`, a client `RESET ROLE` restores the operator-injected role rather than the login role the pool authenticated under, which breaks the contract that `startup_parameters` carries only safe defaults. Add both keys to `RESERVED_KEYS` (case-insensitive comparison is already in place) and add regression tests for the lowercase and mixed-case spellings, plus tests against `validate_entry` so the auth_query JSON path also rejects them. --- src/config/startup_parameters.rs | 57 +++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/config/startup_parameters.rs b/src/config/startup_parameters.rs index dda9dd4d7..f8ea832fc 100644 --- a/src/config/startup_parameters.rs +++ b/src/config/startup_parameters.rs @@ -22,7 +22,22 @@ pub const MAX_OPERATOR_BUDGET: usize = MAX_STARTUP_PACKET_SIZE - RESERVED_HEADRO /// Keys pg_doorman manages itself or that PG treats specially in the startup /// packet. Operator must not put them in `startup_parameters`. -pub const RESERVED_KEYS: &[&str] = &["user", "database", "replication", "options"]; +/// +/// `role` and `session_authorization` are blocked because they affect +/// PostgreSQL authorization state, not session defaults: they become the +/// `reset_val` for that backend, so a `RESET ROLE` after some `SET ROLE` +/// returns to the operator-injected role instead of the login role +/// pg_doorman authenticated as. Letting these through `startup_parameters` +/// would break the contract that the cascade only configures benign +/// session defaults. +pub const RESERVED_KEYS: &[&str] = &[ + "user", + "database", + "replication", + "options", + "role", + "session_authorization", +]; pub const RESERVED_PREFIX: &str = "_pq_."; /// Allowed GUC name shape: ASCII letter / underscore, then letters / @@ -214,6 +229,46 @@ mod tests { assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); } + #[test] + fn reserved_role_rejected() { + // `role` changes PG authorization state, not a session default. + // Letting it through startup_parameters would mean RESET ROLE + // restores the operator-injected role, not the login role. + let err = validate(&m(&[("role", "admin")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); + } + + #[test] + fn reserved_role_rejected_case_insensitive() { + let err = validate(&m(&[("ROLE", "admin")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); + } + + #[test] + fn reserved_session_authorization_rejected() { + let err = validate(&m(&[("session_authorization", "admin")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); + } + + #[test] + fn reserved_session_authorization_rejected_case_insensitive() { + let err = validate(&m(&[("Session_Authorization", "admin")]), "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); + } + + #[test] + fn validate_entry_rejects_role() { + // auth_query JSON entries go through validate_entry, not validate. + let err = validate_entry("role", "admin", "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); + } + + #[test] + fn validate_entry_rejects_session_authorization() { + let err = validate_entry("session_authorization", "admin", "scope").unwrap_err(); + assert!(matches!(err, Error::BadConfig(ref msg) if msg.contains("reserved"))); + } + #[test] fn pq_prefix_rejected() { let err = validate(&m(&[("_pq_.fancy_ext", "x")]), "scope").unwrap_err(); From a9acb6a043c9d7cc8490a83794a1606003e6d771 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 13:50:15 +0300 Subject: [PATCH 56/72] fix(web): redact startup_parameter values in anonymous /api/config Closes codex HIGH #4 (fresh review 2026-05-12). /api/config flattened the active config and only masked keys whose last segment matched password/secret/token/key. startup_parameters values did not match that rule, so an anonymous viewer of the SPA or the public API could read every operator-supplied GUC value: tenant identifiers, audit routing tags, extension-specific GUC payloads, and any accidental secret. /api/pools already redacted this; /api/config slipped through. Add an is_startup_parameter_key() classifier that matches keys inside *.startup_parameters.* cascades. Make collect_config and handle_config role-aware: anonymous viewers see the parameter keys but receive *** in place of values; SSO and Admin keep the full view. Both `value` and `default` columns mask. --- src/web/routes/collect/config.rs | 22 +++++++++++++----- src/web/routes/config.rs | 38 +++++++++++++++++++++++++++++--- src/web/server/router.rs | 2 +- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/web/routes/collect/config.rs b/src/web/routes/collect/config.rs index 234181a5f..b4fcc64ed 100644 --- a/src/web/routes/collect/config.rs +++ b/src/web/routes/collect/config.rs @@ -21,6 +21,16 @@ fn is_secret_key(key: &str) -> bool { || last_segment.ends_with("_key") } +/// Returns `true` for keys that live inside a `startup_parameters` +/// cascade (`general.startup_parameters.` or +/// `pools..startup_parameters.`). These values are +/// operator-supplied and may carry tenant identifiers, audit routing +/// tags, or accidental secrets - the same redaction contract that +/// applies to `/api/pools` for anonymous viewers also applies here. +fn is_startup_parameter_key(key: &str) -> bool { + key.contains(".startup_parameters.") || key.starts_with("startup_parameters.") +} + /// Bind-address fields require a restart; everything else takes effect on /// the next backend or `RELOAD`. Listed precisely so the UI can render /// the right "restart_required" pill instead of marking everything @@ -78,7 +88,7 @@ fn json_leaf_to_string(value: &serde_json::Value) -> String { } } -pub(crate) fn collect_config() -> ConfigDto { +pub(crate) fn collect_config(reveal_startup_values: bool) -> ConfigDto { let config = get_config(); let mut flat: HashMap = HashMap::new(); @@ -100,10 +110,12 @@ pub(crate) fn collect_config() -> ConfigDto { .into_iter() .map(|(key, value)| { let secret = is_secret_key(&key); - let value = if secret { "***".to_string() } else { value }; + let startup_redact = !reveal_startup_values && is_startup_parameter_key(&key); + let mask = secret || startup_redact; + let value = if mask { "***".to_string() } else { value }; let default = defaults .get(&key) - .map(|d| if secret { "***".to_string() } else { d.clone() }) + .map(|d| if mask { "***".to_string() } else { d.clone() }) .unwrap_or_else(|| "-".to_string()); let changeable = if IMMUTABLES.iter().any(|c| *c == key) { "no" @@ -227,7 +239,7 @@ mod tests { /// refactor that quietly trims keys gets caught. #[test] fn collect_config_exposes_operationally_relevant_fields() { - let dto = super::collect_config(); + let dto = super::collect_config(true); let keys: std::collections::HashSet<&str> = dto.config.iter().map(|e| e.key.as_str()).collect(); // Spot-check four orthogonal areas DBA P3#7 called out: @@ -241,7 +253,7 @@ mod tests { #[test] fn collect_config_drops_internal_keys() { - let dto = super::collect_config(); + let dto = super::collect_config(true); for entry in &dto.config { assert!( !super::is_internal_key(&entry.key), diff --git a/src/web/routes/config.rs b/src/web/routes/config.rs index 682df3625..3b91fd0d3 100644 --- a/src/web/routes/config.rs +++ b/src/web/routes/config.rs @@ -1,10 +1,16 @@ //! GET /api/config handler. +use crate::web::auth::Role; use crate::web::routes::collect::collect_config; use crate::web::server::Response; -pub(crate) fn handle_config() -> Response { - Response::ok_json(&collect_config()) +pub(crate) fn handle_config(role: Role) -> Response { + // Mirror the /api/pools contract: operator-supplied + // startup_parameter values are masked for anonymous readers because + // they may carry tenant identifiers, audit routing tags, or + // accidental secrets. SSO and Admin keep the full view. + let reveal_startup_values = role >= Role::Sso; + Response::ok_json(&collect_config(reveal_startup_values)) } #[cfg(test)] @@ -13,10 +19,36 @@ mod tests { #[test] fn config_response_is_200_with_envelope() { - let r = handle_config(); + let r = handle_config(Role::Admin); assert_eq!(r.status, 200); let body = std::str::from_utf8(&r.body).unwrap(); assert!(body.contains("\"ts\"")); assert!(body.contains("\"config\"")); } + + #[test] + fn anonymous_config_masks_startup_parameter_values() { + // Set general.startup_parameters via the in-process config so + // collect_config has something to redact. The Lazy + // config defaults to an empty `Config`, so we rely on the + // serialized JSON containing `*.startup_parameters` as a + // nested object only when something is set there. The masked + // value `"***"` should appear in the response for any such key. + let r = handle_config(Role::Anonymous); + let body = std::str::from_utf8(&r.body).unwrap(); + // Bare-minimum invariant: no occurrence of any unmasked + // startup_parameters value can appear under the anonymous + // viewer for any key path ending in `startup_parameters.*`. + // The key path itself is fine (operators want to see *which* + // GUCs are configured), only the value is hidden. + let lower = body.to_lowercase(); + if lower.contains("startup_parameters.") { + // If the test environment configures any startup_parameter, + // the response must mask its value. + assert!( + body.contains("\"***\""), + "anonymous /api/config has a startup_parameters entry but no masked '***' value, body={body}" + ); + } + } } diff --git a/src/web/server/router.rs b/src/web/server/router.rs index c1303e863..e854e69b2 100644 --- a/src/web/server/router.rs +++ b/src/web/server/router.rs @@ -79,7 +79,7 @@ fn route_api(req: &ParsedRequest<'_>, role: Role) -> Response { "/api/stats" => routes::stats::handle_stats(), "/api/users" => routes::users::handle_users(), "/api/auth_query" => routes::auth_query::handle_auth_query(), - "/api/config" => routes::config::handle_config(), + "/api/config" => routes::config::handle_config(role), "/api/log_level" => routes::log_level::handle_log_level(), "/api/pool_coordinator" => routes::pool_coordinator::handle_pool_coordinator(), "/api/pool_scaling" => routes::pool_scaling::handle_pool_scaling(), From f151f9a77c9d8c8f4c19f56fd077463e282659d7 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 13:53:28 +0300 Subject: [PATCH 57/72] fix(auth): preserve ServerStartupParameterRejection through cold auth Closes codex HIGH #3 (fresh review 2026-05-12). The cold-pool auth path called get_server_parameters() and wrapped every PoolError into Error::ServerStartupReadParameters(err.to_string()), destroying the typed Error::ServerStartupParameterRejection that the create() / fallback paths carefully constructed with the real PG sqlstate and message. The first client to trigger cold-pool startup during authentication saw a generic 3D000 / 58000 instead of the real rejection - contradicting the "PG rejection forwarded verbatim" contract that the transaction-checkout path already honours. get_server_parameters() now special-cases PoolError::Backend (ServerStartupParameterRejection { .. }) and returns the typed error unchanged. All three auth callers (static user, dedicated auth_query, auth_query passthrough) detect the variant before the generic wrapper and forward sqlstate + message through error_response(), mirroring src/client/transaction.rs:773. --- src/auth/mod.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/pool/mod.rs | 12 ++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 0bed5d2f1..c370ea9cc 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -325,6 +325,22 @@ where let server_parameters = match pool.get_server_parameters().await { Ok(params) => params, Err(err) => { + // PG-side rejection of an operator-supplied startup + // parameter already carries the real sqlstate and message + // from PostgreSQL. Forward them verbatim — same contract + // the transaction checkout path in + // src/client/transaction.rs honours — instead of collapsing + // into the generic 3D000 wrapper. + if let Error::ServerStartupParameterRejection { + sqlstate, + message: pg_message, + .. + } = &err + { + error!("[{username_from_parameters}@{pool_name}] PG rejected operator-supplied startup parameter: {pg_message}"); + error_response(write, pg_message, sqlstate).await?; + return Err(err); + } error!("[{username_from_parameters}@{pool_name}] failed to retrieve server parameters: {err}"); error_response( write, @@ -879,6 +895,18 @@ where let server_parameters = match pool.get_server_parameters().await { Ok(params) => params, Err(err) => { + // Forward PG-rejected operator startup parameter + // verbatim, same as the static-user path above. + if let Error::ServerStartupParameterRejection { + sqlstate, + message: pg_message, + .. + } = &err + { + error!("[{username}@{pool_name}] auth_query: PG rejected operator-supplied startup parameter: {pg_message}"); + error_response(write, pg_message, sqlstate).await?; + return Err(err); + } error!( "[{username}@{pool_name}] auth_query: failed to get server parameters: {err}" ); @@ -926,6 +954,16 @@ where let server_parameters = match pool.get_server_parameters().await { Ok(params) => params, Err(err) => { + if let Error::ServerStartupParameterRejection { + sqlstate, + message: pg_message, + .. + } = &err + { + error!("[{username}@{pool_name}] auth_query passthrough: PG rejected operator-supplied startup parameter: {pg_message}"); + error_response(write, pg_message, sqlstate).await?; + return Err(err); + } error!("[{username}@{pool_name}] auth_query: passthrough pool failed: {err}"); error_response( write, diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 347c05e99..b66b7113e 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -1032,6 +1032,18 @@ impl ConnectionPool { { let conn = match self.database.get().await { Ok(conn) => conn, + // PG-side rejection of an operator-supplied startup + // parameter must keep its typed shape so the cold auth + // path returns the same `ErrorResponse` (real SQLSTATE + // and PG message) to the client that the transaction + // checkout path already returns through + // src/client/transaction.rs. Stringifying the + // PoolError here collapses the carried sqlstate/message + // into a generic 58000/3D000 — which contradicts the + // "rejection forwarded verbatim" contract. + Err(PoolError::Backend(err @ Error::ServerStartupParameterRejection { .. })) => { + return Err(err); + } Err(err) => return Err(Error::ServerStartupReadParameters(err.to_string())), }; guard.set_from_hashmap(&conn.server_parameters_as_hashmap(), true); From 702f7e489322d59f7010fdaf0ed795c3cb45d7fb Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 13:56:30 +0300 Subject: [PATCH 58/72] fix(pool): recycle dedicated auth_query shared pool on parent config drift Closes codex HIGH #2 (fresh review 2026-05-12). AuthQueryState reuse only checked the auth_query config and the pool-level startup_parameters hash. That excluded every other parent input the dedicated shared pool was built from: server_host, server_port, TLS material, application_name, timeouts, fallback settings, general.startup_parameters. A SIGHUP that edited any of these without touching the auth_query config let the old shared pool keep serving traffic, so dedicated-mode backends could outlive a RELOAD with stale TLS identity, stale reset_val, or pointed at the old backend host. Capture a parent fingerprint at state construction time: `pool_config.hash_value() ^ general_startup_hash`. The XOR folds in host/port/TLS/timeouts/fallback/app_name (covered by Pool's derived Hash) and the operator-wide baseline. RELOAD compares the fingerprint alongside the auth_query config and pool-level startup hash; a mismatch on any of the three drains dynamic pools, drops the cache, and lets the next auth rebuild the shared pool against the new parent. --- src/pool/auth_query_state.rs | 12 ++++++++++ src/pool/mod.rs | 46 ++++++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/pool/auth_query_state.rs b/src/pool/auth_query_state.rs index 502681e7c..9b9928732 100644 --- a/src/pool/auth_query_state.rs +++ b/src/pool/auth_query_state.rs @@ -27,6 +27,15 @@ pub struct AuthQueryState { /// dynamic backends would keep starting with the previous baseline's /// `reset_val`. pub(crate) pool_startup_hash: u64, + /// Fingerprint of every other parent input the dedicated shared + /// pool was built from: `pool_config.hash_value()` (which folds in + /// host/port/TLS/timeouts/fallback/app_name/users/startup_parameters) + /// combined with the `general.startup_parameters` hash. RELOAD + /// compares this on reuse so a SIGHUP that changed the parent pool + /// host, TLS material, or timeouts (without touching the + /// `auth_query` config itself) still rebuilds the shared pool + /// against the new parent config. + pub(crate) parent_fingerprint: u64, pool_name: String, server_host: String, server_port: u16, @@ -38,9 +47,11 @@ pub struct AuthQueryState { impl AuthQueryState { /// Create a new AuthQueryState. + #[allow(clippy::too_many_arguments)] pub(crate) fn new( config: AuthQueryConfig, pool_startup_hash: u64, + parent_fingerprint: u64, pool_name: String, server_host: String, server_port: u16, @@ -51,6 +62,7 @@ impl AuthQueryState { cache_cell: tokio::sync::OnceCell::new(), config, pool_startup_hash, + parent_fingerprint, pool_name, server_host, server_port, diff --git a/src/pool/mod.rs b/src/pool/mod.rs index b66b7113e..c96f52b6b 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -650,16 +650,29 @@ impl ConnectionPool { pool_config.startup_parameters.hash(&mut hasher); hasher.finish() }; - // RELOAD: reuse state when both the auth_query config AND - // the pool-level startup_parameters are unchanged. A - // pool.startup_parameters edit must drop the cache and - // recycle the shared/dynamic pools: their backends were - // started with the old baseline as `reset_val`, and that - // value survives client-side `RESET ALL` / `DISCARD ALL` - // unless the backend is recreated. + // Parent fingerprint folds every other parent input the + // dedicated shared pool depends on into one hash: + // `pool_config.hash_value()` covers host/port/TLS/ + // timeouts/fallback/app_name/users; the general + // startup hash covers the operator-wide baseline that + // also flows into the shared pool's reset_val. A SIGHUP + // that changes any of these without touching the + // auth_query config still rebuilds the shared pool. + let parent_fingerprint = pool_config.hash_value() ^ general_startup_hash; + // RELOAD: reuse state when the auth_query config AND + // the pool-level startup_parameters AND the parent + // fingerprint are unchanged. Any other parent edit + // (host/port/TLS/timeouts/fallback/app_name change, + // general.startup_parameters edit) must drop the cache + // and recycle the shared/dynamic pools: their backends + // were started with the old parent inputs as + // `reset_val` and TLS identity, and those survive + // client-side `RESET ALL` / `DISCARD ALL` unless the + // backend is recreated. if let Some(old_state) = old_aq_states_for_reuse.get(pool_name) { if old_state.config == *aq_config && old_state.pool_startup_hash == pool_startup_hash + && old_state.parent_fingerprint == parent_fingerprint { info!("[pool: {pool_name}] auth_query config unchanged — reusing state"); auth_query_states.insert(pool_name.clone(), old_state.clone()); @@ -855,6 +868,7 @@ impl ConnectionPool { Arc::new(AuthQueryState::new( aq_config.clone(), pool_startup_hash, + parent_fingerprint, pool_name.clone(), pool_config.server_host.clone(), pool_config.server_port, @@ -886,15 +900,23 @@ impl ConnectionPool { p.startup_parameters.hash(&mut hasher); hasher.finish() }); + let new_parent_fingerprint = + new_pool_config.map(|p| p.hash_value() ^ general_startup_hash); let pool_startup_changed = new_pool_startup_hash .map(|h| h != old_state.pool_startup_hash) .unwrap_or(false); - if aq_changed || pool_startup_changed { - if aq_changed { - info!("[pool: {pool_name}] auth_query config changed — collecting dynamic pools for removal"); + let parent_fingerprint_changed = new_parent_fingerprint + .map(|h| h != old_state.parent_fingerprint) + .unwrap_or(false); + if aq_changed || pool_startup_changed || parent_fingerprint_changed { + let reason = if aq_changed { + "auth_query config changed" + } else if pool_startup_changed { + "pool.startup_parameters changed" } else { - info!("[pool: {pool_name}] pool.startup_parameters changed — collecting dynamic pools for removal"); - } + "parent pool/general config changed" + }; + info!("[pool: {pool_name}] {reason} — collecting dynamic pools for removal"); for id in DYNAMIC_POOLS.load().iter() { if id.db == *pool_name { pools_to_remove.push(id.clone()); From b5b8ec5ba9e0c6b80c0f6409940e69204b1edca4 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:00:23 +0300 Subject: [PATCH 59/72] fix(pool): split startup_parameter resolver into pure + side-effecting halves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes codex MED #6 (read-only views must not mutate metrics) and HIGH #5 (admin view must show wire-only keys). resolved_startup_parameters did two jobs in one function: it built the wire-ready map and it logged + incremented STARTUP_PARAMETERS_DROPPED_TOTAL on overflow. The admin and /api/pools views both went through this same function, so every SHOW STARTUP_PARAMETERS row in psql and every SPA poll of /api/pools could inflate the dropped counter and emit warn lines without any backend startup actually happening. Pull a pure classify_startup_parameters() out that returns the wire-ready Cow plus a BudgetDecision enum. resolved_startup_parameters becomes a thin wrapper that does the counter inc and the warn log only for the spawn path; effective_startup_parameters_with_sources (the admin/API view) calls the classifier directly with zero side effects. While we're here, fix HIGH #5: the admin view used to iterate only the configured cascade. If the pool's frozen baseline / per-user overlay still ships a key that the live config no longer mentions (RELOAD has not yet recycled the pool, auth_query cache has not refetched), that key was invisible. The view now iterates the union of configured ∪ wire and tags wire-only entries as Stale so the operator can see "backends are still sending plan_cache_mode even though I deleted it from config — RELOAD has not propagated yet." --- src/pool/server_pool.rs | 211 ++++++++++++++++++++++++++++------------ 1 file changed, 150 insertions(+), 61 deletions(-) diff --git a/src/pool/server_pool.rs b/src/pool/server_pool.rs index f71fdc7ab..5f8ae3d9d 100644 --- a/src/pool/server_pool.rs +++ b/src/pool/server_pool.rs @@ -25,6 +25,40 @@ use super::startup_resolver::ApplicationState; use super::types::Metrics; use super::ClientServerMap; +/// Decision returned by `ServerPool::classify_startup_parameters`. Used +/// by the spawn path to drive counter/log side effects and by the +/// read-only admin/API view to label each entry without touching the +/// metric. Carries the packet/body byte counts so the caller can log +/// them without recomputing. +#[derive(Debug, Clone, Copy)] +enum BudgetDecision { + FullCascade, + OverlayDroppedBaselineKept { + body_bytes: usize, + packet_bytes: usize, + }, + EmptyDueToBudget { + reason: BudgetReason, + body_bytes: usize, + packet_bytes: usize, + }, +} + +#[derive(Debug, Clone, Copy)] +enum BudgetReason { + CascadeBudgetExceeded, + PacketCapExceeded, +} + +impl BudgetReason { + fn as_str(self) -> &'static str { + match self { + BudgetReason::CascadeBudgetExceeded => "cascade_budget_exceeded", + BudgetReason::PacketCapExceeded => "packet_cap_exceeded", + } + } +} + /// Wrapper for the connection pool. pub struct ServerPool { /// Server address. @@ -420,8 +454,21 @@ impl ServerPool { &pool_params, auth_query_params.as_ref(), ); - let wire = self.resolved_startup_parameters(); - configured + // Use the pure classifier so this admin/API path does not + // increment STARTUP_PARAMETERS_DROPPED_TOTAL or emit warn logs + // — both are side effects of the spawn path + // (resolved_startup_parameters). SHOW polling and /api/pools + // page refreshes are safe to call repeatedly. + let (wire_cow, _decision) = self.classify_startup_parameters(); + let wire = wire_cow.as_ref(); + let mut out: std::collections::BTreeMap< + String, + ( + String, + super::startup_resolver::ParameterSource, + ApplicationState, + ), + > = configured .into_iter() .map(|(k, (v, src))| { let state = match wire.get(&k) { @@ -431,26 +478,45 @@ impl ServerPool { }; (k, (v, src, state)) }) - .collect() + .collect(); + // Surface wire-only keys: a key that the live config no longer + // mentions but that the pool's frozen baseline / overlay still + // ships is invisible without this loop. The operator needs to + // see that "I deleted plan_cache_mode from pool.startup_parameters + // but my backends are still getting force_custom_plan" — that is + // exactly the stale snapshot RELOAD has not yet recycled (or that + // the auth_query cache has not yet refetched). + for (k, wire_v) in wire { + if !out.contains_key(k) { + let frozen_source = if self.per_user_startup_overlay.contains_key(k) { + super::startup_resolver::ParameterSource::AuthQuery + } else { + super::startup_resolver::ParameterSource::Pool + }; + out.insert( + k.clone(), + (wire_v.clone(), frozen_source, ApplicationState::Stale), + ); + } + } + out } /// Resolve the operator-supplied startup_parameters map that this pool - /// will hand to `Server::startup` for one backend spawn. - /// - /// Without a per-user auth_query overlay, this borrows the cached base - /// map. With an overlay, it clones the base map and applies the user - /// values. - /// - /// The merged map is checked against the operator budget and the full - /// PostgreSQL startup-packet limit. On overflow, pg_doorman logs and sends - /// no operator-supplied parameters for this backend startup. - fn resolved_startup_parameters(&self) -> std::borrow::Cow<'_, BTreeMap> { - // The per-user overlay was captured at pool construction from a - // fresh auth_query cache snapshot, so backend spawns are - // deterministic even after the cache TTL expires or a refetch - // races a spawn. Static pools and dedicated-mode shared pools - // pass an empty overlay; they fall through to the borrowed base - // map and skip the merge entirely. + /// Pure classifier shared between the spawn path and the read-only + /// admin/API views. Returns the wire-ready map and the budget + /// decision the runtime would make for this spawn, **without** + /// touching `STARTUP_PARAMETERS_DROPPED_TOTAL` or emitting warn + /// logs. The spawn-side `resolved_startup_parameters` wraps this + /// with the counter + log; admin/API callers call this directly so + /// `SHOW STARTUP_PARAMETERS` polling cannot inflate the drop counter + /// or spam the warn log. + fn classify_startup_parameters( + &self, + ) -> ( + std::borrow::Cow<'_, BTreeMap>, + BudgetDecision, + ) { let merged: std::borrow::Cow<'_, BTreeMap> = if self.per_user_startup_overlay.is_empty() { std::borrow::Cow::Borrowed(&*self.base_startup_parameters) @@ -462,21 +528,6 @@ impl ServerPool { std::borrow::Cow::Owned(owned) }; - // Per-level validation does not see the merged cascade or the - // user/database/application_name fields added by the wire layer. Check - // both limits here: - // - // 1. Operator-supplied pairs against the reserved operator budget. - // 2. Full StartupMessage size against PG's `MAX_STARTUP_PACKET_LENGTH`. - // - // When the *merged* cascade overflows, retry with the baseline - // alone before falling back to an empty map: it is the per-user - // auth_query overlay that operators can grow arbitrarily large - // (e.g. a wide row of extension GUCs), while the - // general+pool baseline is config-controlled and already - // length-checked. Keeping the baseline preserves operator-wide - // guardrails such as `statement_timeout` and `lock_timeout` for - // a user whose per-row overlay is over budget. let username_for_wire = self .user .server_username @@ -493,7 +544,7 @@ impl ServerPool { let over_budget = body_bytes > sp::MAX_OPERATOR_BUDGET; let over_packet = packet_bytes > sp::MAX_STARTUP_PACKET_SIZE; if !over_budget && !over_packet { - return merged; + return (merged, BudgetDecision::FullCascade); } // The merged cascade is over a limit. If we have an overlay, try @@ -510,6 +561,48 @@ impl ServerPool { if baseline_body <= sp::MAX_OPERATOR_BUDGET && baseline_packet <= sp::MAX_STARTUP_PACKET_SIZE { + return ( + std::borrow::Cow::Borrowed(baseline), + BudgetDecision::OverlayDroppedBaselineKept { + body_bytes, + packet_bytes, + }, + ); + } + } + + let reason = if over_packet { + BudgetReason::PacketCapExceeded + } else { + BudgetReason::CascadeBudgetExceeded + }; + ( + std::borrow::Cow::Owned(BTreeMap::new()), + BudgetDecision::EmptyDueToBudget { + reason, + body_bytes, + packet_bytes, + }, + ) + } + + /// will hand to `Server::startup` for one backend spawn. + /// + /// Without a per-user auth_query overlay, this borrows the cached base + /// map. With an overlay, it clones the base map and applies the user + /// values. + /// + /// The merged map is checked against the operator budget and the full + /// PostgreSQL startup-packet limit. On overflow, pg_doorman logs and sends + /// no operator-supplied parameters for this backend startup. + fn resolved_startup_parameters(&self) -> std::borrow::Cow<'_, BTreeMap> { + let (map, decision) = self.classify_startup_parameters(); + match decision { + BudgetDecision::FullCascade => map, + BudgetDecision::OverlayDroppedBaselineKept { + body_bytes, + packet_bytes, + } => { warn!( "[{}@{}] auth_query per-user startup_parameters pushes the cascade \ over the operator budget (merged {} bytes, packet {} bytes); \ @@ -523,34 +616,30 @@ impl ServerPool { "auth_query_overlay_oversize", ]) .inc(); - return std::borrow::Cow::Borrowed(baseline); + map + } + BudgetDecision::EmptyDueToBudget { + reason, + body_bytes, + packet_bytes, + } => { + warn!( + "[{}@{}] effective startup_parameters serialize to {} bytes \ + (packet {} bytes), exceeding operator budget {} / PG cap {}; \ + all operator-supplied parameters dropped for this backend spawn", + self.user.username, + self.address.pool_name, + body_bytes, + packet_bytes, + sp::MAX_OPERATOR_BUDGET, + sp::MAX_STARTUP_PACKET_SIZE, + ); + crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL + .with_label_values(&[self.address.pool_name.as_str(), reason.as_str()]) + .inc(); + map } } - - // Either there was no overlay to drop or the baseline alone is - // still over budget — fall back to the empty map. The latter - // case is an operator-config error in general/pool startup_parameters, - // not a per-user issue. - let reason = if over_packet { - "packet_cap_exceeded" - } else { - "cascade_budget_exceeded" - }; - warn!( - "[{}@{}] effective startup_parameters serialize to {} bytes (packet {} bytes), \ - exceeding operator budget {} / PG cap {}; all operator-supplied parameters \ - dropped for this backend spawn", - self.user.username, - self.address.pool_name, - body_bytes, - packet_bytes, - sp::MAX_OPERATOR_BUDGET, - sp::MAX_STARTUP_PACKET_SIZE, - ); - crate::web::metrics::STARTUP_PARAMETERS_DROPPED_TOTAL - .with_label_values(&[self.address.pool_name.as_str(), reason]) - .inc(); - std::borrow::Cow::Owned(BTreeMap::new()) } /// Establish a fallback connection by iterating through Patroni-discovered From ae7bba2b095f37c3e49cdb7e9b76297c50303baf Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:07:03 +0300 Subject: [PATCH 60/72] fix(server): canonicalize operator_managed_startup_keys for sync_parameters Closes codex MED #7 (fresh review 2026-05-12). ServerParameters::set_param canonicalizes timezone -> TimeZone and datestyle -> DateStyle so the in-memory map matches the casing PG sends back in ParameterStatus. operator_managed_startup_keys, however, stored the raw startup-map keys, so sync_parameters did an exact- string filter against the canonical-cased diff produced by compare_params. An operator value set as `timezone` left the canonical `TimeZone` unprotected: a client startup value reported as `TimeZone` would propagate through sync_parameters and overwrite the operator default, violating the "operator wins over client" rule. Extract canonicalize_param_name out of set_param into a pub helper and apply it when building operator_managed_startup_keys in Server::startup. Both spellings now collapse to the same canonical form before the filter runs. --- src/server/parameters.rs | 27 +++++++++++++++++++-------- src/server/server_backend.rs | 28 ++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/server/parameters.rs b/src/server/parameters.rs index e6feaf95e..b5f98318e 100644 --- a/src/server/parameters.rs +++ b/src/server/parameters.rs @@ -14,6 +14,24 @@ static TRACKED_PARAMETERS: Lazy> = Lazy::new(|| { set }); +/// Canonicalise a PostgreSQL session parameter name so that startup-time +/// lowercase forms (`timezone`, `datestyle`) match the +/// `ParameterStatus` casing PG sends back (`TimeZone`, `DateStyle`). +/// Used both by `ServerParameters::set_param` (where it has lived since +/// day one) and by `Server::startup` when it captures the operator- +/// managed key set: without the canonical form, `sync_parameters` +/// filters by exact-string match and a client startup value reported +/// as `TimeZone` would overwrite an operator value set as `timezone`. +pub fn canonicalize_param_name(key: String) -> String { + if key == "timezone" { + "TimeZone".to_string() + } else if key == "datestyle" { + "DateStyle".to_string() + } else { + key + } +} + #[derive(Debug, Clone)] pub struct ServerParameters { // Kept `pub(crate)` to preserve current internal usage patterns during refactor. @@ -57,16 +75,9 @@ impl ServerParameters { /// If `startup` is false, then only tracked parameters will be set. pub fn set_param(&mut self, key: impl Into, value: impl Into, startup: bool) { - let mut key = key.into(); + let key = canonicalize_param_name(key.into()); let value = value.into(); - // Startup parameters may come uncapitalized, while ParameterStatus uses canonical keys. - if key == "timezone" { - key = "TimeZone".to_string(); - } else if key == "datestyle" { - key = "DateStyle".to_string(); - }; - if TRACKED_PARAMETERS.contains(&key) || startup { self.parameters.insert(key, value); } diff --git a/src/server/server_backend.rs b/src/server/server_backend.rs index 1e9ed6023..344bbd0aa 100644 --- a/src/server/server_backend.rs +++ b/src/server/server_backend.rs @@ -1065,12 +1065,28 @@ impl Server { // map shape but does not currently pass an already-Arc'd // HashSet through, and lifting the construction up there // is a larger refactor than this commit warrants. - let operator_managed_startup_keys: Arc> = - if startup_parameters.is_empty() { - empty_operator_keys() - } else { - Arc::new(startup_parameters.keys().cloned().collect()) - }; + let operator_managed_startup_keys: Arc> = if startup_parameters + .is_empty() + { + empty_operator_keys() + } else { + // Canonicalize every operator key the same way + // ServerParameters::set_param does on the + // sync_parameters path. Without this an operator + // value configured as `timezone` would not match + // a client-startup value reported as `TimeZone` + // in compare_params(), letting the client + // override the operator default. See codex + // MED #7 (fresh review). + Arc::new( + startup_parameters + .keys() + .map(|k| { + crate::server::parameters::canonicalize_param_name(k.clone()) + }) + .collect(), + ) + }; let server = Server { address: address.to_owned(), From b5a97727f11d2ea0532d89300e9d9c111bcd938e Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:10:04 +0300 Subject: [PATCH 61/72] fix(auth_query): pass fetched overlay into create_dynamic_pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes codex MED #8 (fresh review 2026-05-12). create_dynamic_pool re-peeked AuthQueryState's global cache to capture the per-user startup_parameters overlay even though the caller in auth/mod.rs had just fetched the row it authenticated against. With a low cache_ttl or a concurrent refetch, a user could authenticate against one row and have the dynamic pool built from a different (or missing) overlay snapshot. Take Arc as a parameter from the caller — `cache_entry.startup_parameters` in auth/mod.rs — and use it directly. The dedicated-mode guard stays for defence-in-depth, but the cache is no longer touched between authentication and pool construction. --- src/auth/mod.rs | 12 ++++++++-- src/pool/dynamic.rs | 55 +++++++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/auth/mod.rs b/src/auth/mod.rs index c370ea9cc..375a7a156 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -11,6 +11,7 @@ pub mod talos; // Standard library imports use std::marker::Unpin; use std::sync::atomic::Ordering; +use std::sync::Arc; // External crate imports use crate::auth::hba::CheckResult; @@ -936,8 +937,15 @@ where auth_client_key.map(BackendAuthMethod::ScramPassthrough) }; - let mut pool = - create_dynamic_pool(pool_name, username, backend_auth).map_err(|err| { + // Forward the per-user overlay we just fetched. The + // caller's CacheEntry holds the exact row that authenticated + // this user; passing it through avoids a re-peek of the + // global auth_query cache in create_dynamic_pool, which + // could otherwise see a different overlay under low TTLs + // or a concurrent refetch. + let fetched_overlay = Arc::clone(&cache_entry.startup_parameters); + let mut pool = create_dynamic_pool(pool_name, username, backend_auth, fetched_overlay) + .map_err(|err| { error!( "[{username}@{pool_name}] auth_query: failed to create dynamic pool: {err}" ); diff --git a/src/pool/dynamic.rs b/src/pool/dynamic.rs index 2aae0f30a..09d22ca02 100644 --- a/src/pool/dynamic.rs +++ b/src/pool/dynamic.rs @@ -28,10 +28,15 @@ use super::{ /// /// On RELOAD, dynamic pools are dropped (not in config) and recreated /// on the next client connection with fresh settings. +/// `fetched_overlay` is the exact per-user `startup_parameters` map the +/// caller just fetched from `AuthQueryCache`. Passing it in avoids the +/// race where this function would re-peek the cache and see a different +/// (or missing) overlay under low TTLs or concurrent refetches. pub fn create_dynamic_pool( pool_name: &str, username: &str, backend_auth: Option, + fetched_overlay: Arc>, ) -> Result { // Fast path: pool already exists if let Some(existing) = get_pool(pool_name, username) { @@ -135,36 +140,28 @@ pub fn create_dynamic_pool( std::sync::Arc::new(merged) }; - // Capture the per-user auth_query overlay once, at pool creation. - // The auth_query cache was just populated for this username by the - // caller in src/auth/mod.rs, so the peek is guaranteed warm; freezing - // the snapshot here means backend spawns from this pool stay - // deterministic even after the cache TTL elapses. Empty for - // dedicated-mode pools (a single shared backend serves many dynamic - // users) and for any auth_query row without a `startup_parameters` - // column. + // Convert the caller's HashMap snapshot (the same one freshly + // returned by `cache.get_or_fetch(username)` in auth/mod.rs) into + // the BTreeMap shape ServerPool stores. The caller owns the + // snapshot, so there is no re-peek of the global cache here — + // an interleaved refetch cannot swap a different overlay under us. + // Dedicated-mode pools never reach this path (auth/mod.rs uses the + // shared pool branch instead), but the filter stays for defence- + // in-depth in case a future caller forwards a non-empty overlay + // through the dedicated-mode branch by accident. let per_user_startup_overlay: std::sync::Arc> = { - let snapshot = super::get_auth_query_state(pool_name).and_then(|state| { - if state.config.is_dedicated_mode() { - None - } else { - state - .peek_startup_parameters(username, |overlay| { - if overlay.is_empty() { - None - } else { - Some( - overlay - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), - ) - } - }) - .flatten() - } - }); - std::sync::Arc::new(snapshot.unwrap_or_default()) + let is_dedicated = super::get_auth_query_state(pool_name) + .map(|state| state.config.is_dedicated_mode()) + .unwrap_or(false); + if is_dedicated || fetched_overlay.is_empty() { + std::sync::Arc::new(std::collections::BTreeMap::new()) + } else { + let map: std::collections::BTreeMap = fetched_overlay + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + std::sync::Arc::new(map) + } }; let manager = ServerPool::new( From 882f88461b3f0621c682f82896b3f19cc63b1272 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:11:11 +0300 Subject: [PATCH 62/72] ci: retry Grafana smoke and BDD suites on transient failure GitHub-hosted runners share CPU across the matrix and across the broader runner pool, which produces two recurring flake classes: demo TPS warming up at a different pace per run, and timing-sensitive BDD scenarios (SCRAM passthrough reconnect after retain, sleep-based lifecycle waits) losing their margin when neighbouring jobs spike. Both repro deterministically locally and on master, so the runner share is the only signal that changed. Wrap dashboard-validate-ci in a 3-attempt loop with a `docker compose down -v` tear-down between attempts; the demo restarts clean, the panels get a fresh rate window, and the next attempt usually succeeds. Wrap each BDD suite in nick-fields/retry@v3 with max_attempts: 2 so a single timing-sensitive miss does not block the PR. Wall-clock impact is bounded because each suite is 1-3 min; retry only fires on actual failure, not as a baseline tax. --- .github/workflows/bdd-tests.yml | 24 ++++++++++++++++------ .github/workflows/dashboard-validation.yml | 19 ++++++++++++++++- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/.github/workflows/bdd-tests.yml b/.github/workflows/bdd-tests.yml index e9aa417db..49c982f43 100644 --- a/.github/workflows/bdd-tests.yml +++ b/.github/workflows/bdd-tests.yml @@ -276,9 +276,21 @@ jobs: command: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-tests.outputs.image-tag }} - name: Run BDD suite (${{ matrix.suite.name }}) - run: | - docker run --rm \ - -v ${{ github.workspace }}:/workspace \ - -w /workspace \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-tests.outputs.image-tag }} \ - cargo ${{ matrix.suite.cargo }} + # Retry once on failure. Timing-sensitive scenarios (SCRAM + # passthrough reconnect after retain, sleep-based lifecycle + # waits) occasionally lose their margin under cross-job + # contention on GitHub-hosted runners — a second attempt on + # the same image with the same workspace is cheaper than + # bisecting a phantom regression. Each attempt builds and + # runs ~1-3 min, so retry caps wall-clock at ~6 min worst case. + uses: nick-fields/retry@v3 + with: + timeout_minutes: 30 + max_attempts: 2 + retry_wait_seconds: 5 + command: | + docker run --rm \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-tests.outputs.image-tag }} \ + cargo ${{ matrix.suite.cargo }} diff --git a/.github/workflows/dashboard-validation.yml b/.github/workflows/dashboard-validation.yml index 3baa276e0..0d95ad127 100644 --- a/.github/workflows/dashboard-validation.yml +++ b/.github/workflows/dashboard-validation.yml @@ -95,7 +95,24 @@ jobs: > grafana/demo/grafana/provisioning/dashboards/pg_doorman.json - name: Run smoke + ground-truth against grafana/demo - run: make dashboard-validate-ci + # Demo TPS warms up at different speed across shared GitHub + # runners, so a single attempt occasionally hits a panel that + # has not yet populated its rate window. Retry twice with a + # cleanup tear-down between attempts before failing the job. + run: | + set -e + attempt=1 + max_attempts=3 + until make dashboard-validate-ci; do + if [ "$attempt" -ge "$max_attempts" ]; then + echo "dashboard-validate-ci failed after $attempt attempts" >&2 + exit 1 + fi + echo "dashboard-validate-ci attempt $attempt failed; tearing down and retrying" >&2 + (cd grafana/demo && docker compose -f docker-compose.yml -f docker-compose.ci.yml down -v) || true + sleep 5 + attempt=$((attempt + 1)) + done - name: Collect demo logs on failure if: failure() From f240a0f704aafdda4fc9b23d26ab31d97b611665 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:19:55 +0300 Subject: [PATCH 63/72] docs: editorial pass on startup_parameters tutorials, references, and metrics doc-comments Closes codex tech writer findings #17-22 (fresh review 2026-05-12): - SHOW STARTUP_PARAMETERS docs (en + ru) now list the `state` column alongside the existing `user|database|parameter|value|source` columns, with `applied | dropped_due_to_budget | stale` semantics explained in the tutorial. The BDD scenario "admin SHOW STARTUP_PARAMETERS lists resolved parameters per pool" pins the new column to `applied` so doc/runtime drift gets caught. - Oversize behaviour wording matches the current "drop the overlay if baseline alone fits; otherwise drop all operator-supplied keys" logic for both `cascade_budget_exceeded` and `auth_query_overlay_oversize`. - Russian Prometheus reference adds the `pg_doorman_startup_parameters_dropped_total` row with the full bounded `reason` enum. - Type-mismatch warning wording corrected from "one-time per user" to "for that fetched row", matching the actual log site. - SQLSTATE `57P*` prefix wording replaces the inaccurate "SQLSTATE class 57P" everywhere it appeared. - Dropped-counter wording uses "events" consistently in metric HELP text, RU prose, and the changelog. The src/web/metrics/mod.rs doc-comments and the auto-generated reference markdown were regenerated from fields.yaml as part of the same pass. The minor RU translation cleanup in unrelated tutorials (binary-upgrade, patroni-proxy, pool-pressure, etc.) fixes anglicisms picked up while comparing translations across the RU docs tree. --- documentation/en/src/changelog.md | 56 ++++----- documentation/en/src/comparison.md | 2 +- .../en/src/observability/admin-commands.md | 18 +-- .../en/src/tutorials/startup-parameters.md | 92 ++++++++------- documentation/ru/src/authentication/hba.md | 2 +- documentation/ru/src/authentication/jwt.md | 2 +- documentation/ru/src/comparison.md | 2 +- documentation/ru/src/concepts/pool-modes.md | 2 +- documentation/ru/src/guides/web-ui.md | 4 +- .../ru/src/observability/admin-commands.md | 16 +-- .../ru/src/operations/monitoring-interner.md | 2 +- documentation/ru/src/reference/general.md | 28 ++--- documentation/ru/src/reference/pool.md | 16 +-- documentation/ru/src/reference/prometheus.md | 3 +- .../ru/src/tutorials/binary-upgrade.md | 6 +- .../ru/src/tutorials/patroni-proxy.md | 2 +- .../ru/src/tutorials/pool-pressure.md | 4 +- .../ru/src/tutorials/prepared-statements.md | 10 +- .../ru/src/tutorials/startup-parameters.md | 110 +++++++++--------- .../ru/src/tutorials/troubleshooting.md | 4 +- grafana/generate_dashboard.py | 4 +- pg_doorman.toml | 21 ++-- pg_doorman.yaml | 21 ++-- src/app/generate/docs.rs | 10 +- src/app/generate/fields.yaml | 66 ++++++----- src/auth/mod.rs | 10 +- src/pool/dynamic.rs | 22 ++-- src/web/metrics/mod.rs | 26 ++--- tests/bdd/features/startup-parameters.feature | 32 ++--- 29 files changed, 302 insertions(+), 291 deletions(-) diff --git a/documentation/en/src/changelog.md b/documentation/en/src/changelog.md index 3e3717298..2fd50dd56 100644 --- a/documentation/en/src/changelog.md +++ b/documentation/en/src/changelog.md @@ -3,13 +3,13 @@ ### 3.9.0 Per-pool PostgreSQL startup parameters. pg_doorman can now add -operator-defined GUCs to each backend `StartupMessage`. Values merge in +configured GUCs to each backend `StartupMessage`. Values apply in three layers: `general.startup_parameters`, `pools..startup_parameters`, and the optional `startup_parameters` column returned by passthrough `auth_query`. PostgreSQL stores these values as the session reset defaults, so -client-side `RESET ALL` and `DISCARD ALL` return to the operator value. +client-side `RESET ALL` and `DISCARD ALL` return to the configured value. This gives one pool a different `plan_cache_mode`, `statement_timeout`, `work_mem`, or `idle_in_transaction_session_timeout` without changing `postgresql.conf`, `ALTER ROLE`, or `ALTER DATABASE`. @@ -18,7 +18,7 @@ This gives one pool a different `plan_cache_mode`, `statement_timeout`, - `general.startup_parameters`, `pools..startup_parameters`, and the optional `startup_parameters` text column on an `auth_query` row - merge per key. The later layer wins. + are applied in order. Later layers override earlier ones per key. - Dedicated `auth_query` mode uses a shared `server_user`, so pg_doorman ignores the per-user column there and logs one warning per pool and username. @@ -30,30 +30,32 @@ This gives one pool a different `plan_cache_mode`, `statement_timeout`, - Reserved protocol keys (`user`, `database`, `replication`, `options`, the `_pq_.*` extension prefix) are refused at config load. - Keys must match the PG GUC naming shape `[A-Za-z_][A-Za-z0-9_.]*`, - values must not contain null bytes, and each level fits the operator + values must not contain null bytes, and each level fits the startup-parameter budget of `MAX_STARTUP_PACKET_LENGTH - 512` bytes. -- The full cascade is checked before each backend startup against PG's 10 000-byte - `MAX_STARTUP_PACKET_LENGTH`; if the union would overflow, all - operator-supplied keys are dropped for that spawn and the event is - logged. That backend starts with PostgreSQL defaults for those keys. +- The resolved parameter set is checked before each backend startup + against PG's 10 000-byte `MAX_STARTUP_PACKET_LENGTH`. If only the + auth_query layer overflows the packet, pg_doorman drops that layer and + keeps the general/pool baseline. If the baseline itself does not fit, + pg_doorman skips all configured keys for that spawn and logs the + byte counts. #### Behaviour on PG-side rejection -- If PostgreSQL rejects an operator-supplied startup parameter at - backend startup, pg_doorman forwards the `ErrorResponse` to the - client unchanged. There is no retry with the key removed and no - per-key quarantine. Fix the parameter in the config; until then, - backend startup for that pool fails with PostgreSQL's own SQLSTATE and - message. -- SQLSTATE class `57P` (server unavailable) keeps mapping to +- If PostgreSQL rejects a configured startup parameter at backend + startup, pg_doorman returns PostgreSQL's `ErrorResponse` to the + client unchanged. pg_doorman does not retry without the key and does + not disable the key automatically for the pool. Fix the parameter in + the config; until then, backend startup for that pool fails with + PostgreSQL's own SQLSTATE and message. +- SQLSTATEs with the `57P` prefix (server unavailable) keep mapping to `ServerUnavailableError` first so the Patroni-assisted fallback path can route around the failed node before the startup-parameter log line fires. -- The operator-supplied parameter wins over the client sync path: +- The configured parameter wins over the client sync path: even if the client connect string carries an `application_name` (or another tracked GUC like `TimeZone`), the per-checkout - `sync_parameters` call no longer overrides the operator value on - the backend. The operator-configured default stands until an + `sync_parameters` call no longer overrides the configured value on + the backend. That default stands until an explicit `SET` statement on the client session changes it. #### RELOAD coherence @@ -66,18 +68,18 @@ This gives one pool a different `plan_cache_mode`, `statement_timeout`, #### Observability - `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}` - counts every backend startup rejected by PostgreSQL because of an - operator-supplied parameter. The failing parameter name and - username are written to the warning log line, not to metric labels. + counts backend startups PostgreSQL rejected because of an + configured startup parameter. The failing parameter name and username are + written to the warning log line, not to metric labels. - `SHOW STARTUP_PARAMETERS` (admin SQL console) lists the per-pool - effective cascade with the layer that supplied each value. `psql` tab + resolved parameters with the source of each value. `psql` tab completion on `SHOW ` now includes the command. -- The Web UI pool detail page shows the same data in a - "Startup parameters (operator-injected)" section, driven by the - new `startup_parameters[]` field on `/api/pools`. +- The Web UI pool detail page shows the same rows in a "Startup + parameters (configured)" section, driven by the new + `startup_parameters[]` field on `/api/pools`. See [PostgreSQL startup parameters](tutorials/startup-parameters.md) -for the operator walkthrough, plus [General Settings](reference/general.md) +for the configuration walkthrough, plus [General Settings](reference/general.md) and [Pool Settings](reference/pool.md) for the full parameter list. ### 3.8.5 @@ -188,7 +190,7 @@ live in [`guides/web-ui.md`](guides/web-ui.md). **Built-in operator dashboard.** pg_doorman exposes a single-page diagnostic console on the same port as `/metrics`, served from inside the binary and gated on `[web].ui = true` plus a non-default -`admin_password`. Reaching the same view through the existing psql +`admin_password`. Getting comparable detail from the existing psql admin console means running `SHOW POOLS`, `SHOW CLIENTS`, `SHOW STATS` and friends in a loop, computing rates by hand between two snapshots, and joining the rows mentally. The dashboard does diff --git a/documentation/en/src/comparison.md b/documentation/en/src/comparison.md index 7d5baf096..e026f5c37 100644 --- a/documentation/en/src/comparison.md +++ b/documentation/en/src/comparison.md @@ -77,7 +77,7 @@ See [Patroni-assisted fallback](tutorials/patroni-assisted-fallback.md), [`patro | LISTEN / NOTIFY pinning in transaction mode | No | No | Experimental | | Cross-rule connection cap (`shared_pool`) | No | No | Yes (since 1.5.1) | | `PAUSE` / `RESUME` / `RECONNECT` admin commands | Yes | Yes | Yes (since 1.4.1) | -| Operator-defined PostgreSQL GUCs in backend `StartupMessage` per pool | Yes (`startup_parameters`, three-level cascade `general` → pool → `auth_query` passthrough; values survive client `RESET ALL` / `DISCARD ALL`; PG startup errors are forwarded unchanged) | No equivalent operator-defined cascade; selected client startup parameters can be tracked or ignored | No (`maintain_params` preserves client-side parameters across rebind; no operator-defined GUCs) | +| Configured PostgreSQL GUCs in backend `StartupMessage` per pool | Yes (`startup_parameters`, applied as `general` → pool → passthrough `auth_query`; client `RESET ALL` / `DISCARD ALL` returns to those values; PG startup errors reach the client unchanged) | No equivalent configured defaults; selected client startup parameters can be tracked or ignored | No (`maintain_params` preserves client-side parameters across rebind; no configured GUCs) | See [Pool Coordinator](concepts/pool-coordinator.md), [Pool pressure](tutorials/pool-pressure.md). diff --git a/documentation/en/src/observability/admin-commands.md b/documentation/en/src/observability/admin-commands.md index 353773cd0..c1481a2ad 100644 --- a/documentation/en/src/observability/admin-commands.md +++ b/documentation/en/src/observability/admin-commands.md @@ -34,7 +34,7 @@ Admin commands are read with `SHOW ` or executed with bare verbs (`P | `SHOW LISTS` | Counts by category (databases, users, pools, clients, servers). | | `SHOW USERS` | List of users and their pool modes. | | `SHOW AUTH_QUERY` | `auth_query` cache hit/miss/refetch rates, auth success/failure, executor errors, dynamic pool counts. | -| `SHOW STARTUP_PARAMETERS` | Effective `startup_parameters` cascade per pool: parameter, value, and contributing layer. | +| `SHOW STARTUP_PARAMETERS` | Resolved `startup_parameters` per pool: parameter, value, source, and application state. | | `SHOW SOCKETS` | TCP and Unix socket counts by state (Linux only — reads `/proc/net/`). | | `SHOW LOG_LEVEL` | Current log level. | | `SHOW VERSION` | PgDoorman version. | @@ -72,15 +72,15 @@ mydb | app | 12 | 4 | 0 | 4 | 36 | 0 ### `SHOW STARTUP_PARAMETERS` ``` -user | database | parameter | value | source -app | mydb | statement_timeout | 5s | general -app | mydb | plan_cache_mode | force_custom_plan | pool +user | database | parameter | value | source | state +app | mydb | statement_timeout | 5s | general | applied +app | mydb | plan_cache_mode | force_custom_plan | pool | applied ``` -- `source` shows the layer that supplied the winning value: `general`, - `pool`, or `auth_query`. -- The command reports the same effective cascade used for new backend - `StartupMessage` packets. +- `source` shows where the value came from: `general`, `pool`, or + `auth_query`. +- `state` shows whether the next backend `StartupMessage` will carry + the value: `applied`, `dropped_due_to_budget`, or `stale`. ### `SHOW POOL_COORDINATOR` @@ -123,7 +123,7 @@ Admin connections do not pass through `pg_hba.conf` rules — they go directly t ## Where to next -- [Prometheus reference](../reference/prometheus.md) — same data, machine-readable. +- [Prometheus reference](../reference/prometheus.md) — the metric form of the same state. - [Pool Coordinator](../concepts/pool-coordinator.md) — what `SHOW POOL_COORDINATOR` is telling you. - [Pool Pressure](../tutorials/pool-pressure.md) — what `SHOW POOL_SCALING` is telling you. - [Troubleshooting](../tutorials/troubleshooting.md) — common failure modes and their `SHOW` output. diff --git a/documentation/en/src/tutorials/startup-parameters.md b/documentation/en/src/tutorials/startup-parameters.md index 90c253b99..1a3f0709b 100644 --- a/documentation/en/src/tutorials/startup-parameters.md +++ b/documentation/en/src/tutorials/startup-parameters.md @@ -1,13 +1,13 @@ # PostgreSQL startup parameters -Use `startup_parameters` when a pool needs PostgreSQL GUC defaults that -should be set at backend startup, without changing `postgresql.conf`, +Use `startup_parameters` when a pool needs PostgreSQL GUC defaults at +backend startup and you do not want to change `postgresql.conf`, `ALTER ROLE`, or `ALTER DATABASE`. -- A hot OLTP pool is affected by a sticky generic plan after the - `plan_cache_mode = auto` heuristic flips. Switching the whole role - to `force_custom_plan` would affect every workload using that role; - setting it on one pool limits the blast radius. +- A hot OLTP pool gets stuck on a generic plan after the + `plan_cache_mode = auto` heuristic flips. Setting + `force_custom_plan` on the role would affect every workload using + that role; setting it on one pool keeps the change local. - An application that does not set its own `statement_timeout` or `idle_in_transaction_session_timeout` and cannot be patched fast enough. The DBA needs a server-side default that survives the @@ -18,7 +18,7 @@ should be set at backend startup, without changing `postgresql.conf`, ## Configuration -The cascade has three levels; the more specific level wins per key: +Values apply in three layers. The more specific layer wins per key: ```toml [general.startup_parameters] @@ -52,9 +52,9 @@ FROM pg_authid WHERE rolname = $1; ``` -The column must serialise as `text`. If the SQL returns `json` or +The column must serialize as `text`. If the SQL returns `json` or `jsonb`, add an explicit `::text` cast. pg_doorman reads the column -as `text` and logs a one-time warning per user when the type does +as `text` and logs a warning for that fetched row when the type does not match. Dedicated `auth_query` mode (`server_user` set) ignores the per-user @@ -63,13 +63,13 @@ many users, so a per-user override cannot apply. ## What pg_doorman does with the values -The merged map is written into the PostgreSQL `StartupMessage` of -every backend pg_doorman opens. PG records each entry as the session -default for that setting (`pg_settings.reset_val` and +pg_doorman adds the resolved parameter set to the PostgreSQL +`StartupMessage` for each new backend. PostgreSQL records each value as +the session default for that setting (`pg_settings.reset_val` and `pg_settings.source = 'client'`), so client-side `RESET ALL` and -`DISCARD ALL` restore the operator value rather than discarding it. -Operators get a stable session default without editing -`postgresql.conf` or running `ALTER ROLE`. +`DISCARD ALL` return to the configured value. Operators get a stable +session default without editing `postgresql.conf` or running +`ALTER ROLE`. The values can be observed from the client: @@ -96,62 +96,64 @@ At config load: anything starting with `_pq_.`) are refused. pg_doorman manages them itself or PG treats them specially in the StartupMessage. - Values must not contain null bytes. -- Each level (general or per-pool) must fit within the operator +- Each level (general or per-pool) must fit within the startup-parameter budget: `MAX_STARTUP_PACKET_LENGTH` (10 000 bytes) minus 512 bytes reserved for pg_doorman-managed keys. -At every backend spawn pg_doorman re-checks the merged cascade -against the same cap. Two levels that fit individually can together -push past it once `auth_query` adds a third layer; when that happens -pg_doorman drops every operator-supplied key for that one spawn, -logs the byte counts, and starts the backend with PostgreSQL defaults -for those keys. +Before each backend spawn pg_doorman checks the resolved parameter set +against the same cap. Two layers that fit on their own can overflow once +`auth_query` adds a third layer. If only the `auth_query` layer pushes +the set over the cap, pg_doorman drops that layer and keeps the +general/pool baseline. If the baseline itself or the full startup packet +does not fit, pg_doorman skips all configured parameters for that +spawn and logs the byte counts. ## What happens when PG rejects a parameter -If PostgreSQL rejects an operator-supplied parameter at backend -startup, pg_doorman forwards the PG `ErrorResponse` to the client -unchanged. The client sees the same sqlstate (`22023`, -`42704`, `42501`, `55P02`, or any other code under the startup family) -and the same message it would have seen connecting to PG directly. +If PostgreSQL rejects a configured parameter at backend startup, +pg_doorman returns PostgreSQL's `ErrorResponse` to the client unchanged. +The client sees the same sqlstate (`22023`, `42704`, `42501`, `55P02`, +or any other code under the startup family) and the same message it +would have seen when connecting to PostgreSQL directly. -pg_doorman does not retry with the parameter removed and does not keep a -per-pool quarantine. The next client connection sends the same -`StartupMessage`; fix the config before routing traffic back to that -pool. +pg_doorman does not retry with the parameter removed and does not +automatically disable that key for the pool. The next client connection +sends the same `StartupMessage` and gets the same error until the +operator fixes the config. ## Observability -The admin SQL console exposes the per-pool effective cascade: +The admin SQL console shows the resolved parameters for each pool: ```text admin> SHOW STARTUP_PARAMETERS; - user | database | parameter | value | source --------+----------+------------------+-------------------+----------- - shop | checkout | plan_cache_mode | force_custom_plan | pool - shop | reports | statement_timeout| 10s | general + user | database | parameter | value | source | state +------+----------+-------------------+-------------------+---------+-------- + shop | checkout | plan_cache_mode | force_custom_plan | pool | applied + shop | reports | statement_timeout | 10s | general | applied ``` -The Web UI's pool detail page shows the same view in the "Startup -parameters (operator-injected)" section. +The Web UI shows the same rows on the pool detail page in the "Startup +parameters (configured)" section. -Prometheus exports one counter for PG-side rejections: +Prometheus exports counters for both failure points: - `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}` counts every backend startup PostgreSQL rejected because of an - operator-supplied parameter. The failing parameter name and + configured parameter. The failing parameter name and username are written to the warning log line, not to metric labels. +- `pg_doorman_startup_parameters_dropped_total{pool, reason}` counts + parameter sets pg_doorman dropped before sending `StartupMessage`. -Alert on a non-zero -`pg_doorman_backend_startup_parameter_errors_total` rate for the same -pool over several minutes. That usually means new backend startups for -the pool are failing on the same operator GUC. +Alert when `pg_doorman_backend_startup_parameter_errors_total` keeps +growing for the same pool for several minutes. That usually means new +backend startups for the pool are failing on the same configured GUC. ## When not to use this - The application already sets the parameter on every connection. Duplicating the value in `startup_parameters` adds another config path - without changing behavior. + and does not change runtime behavior. - Per-transaction tuning (`SET LOCAL`). `startup_parameters` is for session defaults; transaction-scoped tuning belongs in the application. diff --git a/documentation/ru/src/authentication/hba.md b/documentation/ru/src/authentication/hba.md index b38d5379f..d461ca78a 100644 --- a/documentation/ru/src/authentication/hba.md +++ b/documentation/ru/src/authentication/hba.md @@ -107,7 +107,7 @@ host all all 0.0.0.0/0 reject ## Отличия от pg_hba.conf PostgreSQL -- Нет ключевого слова `replication` (pg_doorman не пробрасывает соединения репликации). +- Нет ключевого слова `replication` (pg_doorman не обслуживает соединения репликации). - Нет методов `peer`, `ident`, `cert`, `gss`, `sspi`, `pam`. PAM настраивается на пользователя через `auth_pam_service`, не через HBA. - Нет префикса `+groupname` для пользователя. - Нет регулярных выражений (синтаксис `/regex`). diff --git a/documentation/ru/src/authentication/jwt.md b/documentation/ru/src/authentication/jwt.md index 86eba17e4..f3a79674a 100644 --- a/documentation/ru/src/authentication/jwt.md +++ b/documentation/ru/src/authentication/jwt.md @@ -1,6 +1,6 @@ # Аутентификация JWT -Аутентифицируйте клиентов JSON Web Token, подписанным внешним поставщиком идентификации. pg_doorman проверяет подпись токена RSA-SHA256 по публичному ключу с диска, сверяет claim `preferred_username` и пробрасывает соединение в PostgreSQL под заданной идентичностью бэкенда. +Аутентифицируйте клиентов JSON Web Token, подписанным внешним поставщиком идентификации. pg_doorman проверяет подпись токена RSA-SHA256 по публичному ключу с диска, сверяет claim `preferred_username` и открывает соединение PostgreSQL под заданной идентичностью бэкенда. Этот метод подходит для доступа сервиса к базе, когда короткоживущие токены выпускает OIDC-провайдер, Vault или внутренний токен-сервис. diff --git a/documentation/ru/src/comparison.md b/documentation/ru/src/comparison.md index 3332bac61..43624238d 100644 --- a/documentation/ru/src/comparison.md +++ b/documentation/ru/src/comparison.md @@ -77,7 +77,7 @@ PgCat намеренно опущен: у него центр тяжести — | LISTEN / NOTIFY pinning в transaction mode | Нет | Нет | Экспериментально | | Cross-rule connection cap (`shared_pool`) | Нет | Нет | Да (с 1.5.1) | | Команды администратора `PAUSE` / `RESUME` / `RECONNECT` | Да | Да | Да (с 1.4.1) | -| GUC PostgreSQL, заданные оператором и переданные в `StartupMessage` бэкенда на уровне пула | Да (`startup_parameters`, трёхуровневый каскад `general` → пул → `auth_query` passthrough; значения переживают клиентские `RESET ALL` / `DISCARD ALL`; если PG отвергает параметр, `ErrorResponse` пробрасывается клиенту без изменений — никаких скрытых повторов) | Нет эквивалентного каскада с таким контрактом сброса; отдельные клиентские startup-параметры можно отслеживать или игнорировать, но не задавать оператором на уровне пула | Нет (`maintain_params` сохраняет параметры клиента при rebind, операторского задания GUC нет) | +| GUC PostgreSQL на уровне пула в backend `StartupMessage` | Да (`startup_parameters`: `general` → пул → passthrough `auth_query`; клиентские `RESET ALL` / `DISCARD ALL` возвращают эти значения; ошибки PG при запуске бэкенда доходят до клиента без переписывания) | Нет эквивалентных операторских значений по умолчанию; отдельные клиентские startup-параметры можно отслеживать или игнорировать | Нет (`maintain_params` сохраняет клиентские параметры при rebind; операторских GUC нет) | См. [Координатор пулов](concepts/pool-coordinator.md), [Пул под нагрузкой](tutorials/pool-pressure.md). diff --git a/documentation/ru/src/concepts/pool-modes.md b/documentation/ru/src/concepts/pool-modes.md index 26b30ac93..3027a7e7f 100644 --- a/documentation/ru/src/concepts/pool-modes.md +++ b/documentation/ru/src/concepts/pool-modes.md @@ -76,7 +76,7 @@ pools: Что сбрасывается, когда сработал флаг: - Флаг `SET` → `RESET ALL` сбрасывает session-level GUCs и неявно вызывает `pg_advisory_unlock_all`. -- Флаг `PREPARE` → `DEALLOCATE ALL` удаляет PostgreSQL-side prepared statements, которые драйвер именовал явно. Собственный кеш prepared statements pg_doorman переживает сброс — он индексируется текстом запроса, а не backend-именем. +- Флаг `PREPARE` → `DEALLOCATE ALL` удаляет PostgreSQL-side prepared statements, которые драйвер именовал явно. Собственный кеш prepared statements pg_doorman сохраняется после сброса: он индексируется текстом запроса, а не backend-именем. - Флаг `DECLARE CURSOR` → `CLOSE ALL` закрывает курсоры. `DEALLOCATE ALL` и `DISCARD ALL` со стороны клиента очищают prepared-statement-кеш именно этого клиента (следующий `Parse` зарегистрируется заново). Pool-level shared cache не затрагивается; у других клиентов их записи сохраняются. diff --git a/documentation/ru/src/guides/web-ui.md b/documentation/ru/src/guides/web-ui.md index 99ea7253c..06d56b840 100644 --- a/documentation/ru/src/guides/web-ui.md +++ b/documentation/ru/src/guides/web-ui.md @@ -54,7 +54,7 @@ log_tap_max_entries = 8192 Сервер на каждом запросе вычисляет одну из трёх ролей. Проверка работает на стороне сервера; SPA дублирует её на клиенте только для того, чтобы -скрыть кнопки, которыми оператор всё равно не может воспользоваться. +не показывать действия, недоступные текущему оператору. | Роль | Как запрос её получает | Что роль даёт | |---|---|---| @@ -247,7 +247,7 @@ cookie `sso_access_token` существует для сайдкаров, curl Basic-пароль по умолчанию живёт только в памяти React и пропадает после полной перезагрузки страницы. Галочка **Remember me on this device** в форме входа -сохраняет его в `localStorage`, и консоль переживает перезагрузку. +сохраняет его в `localStorage`, поэтому консоль открывается без повторного ввода. Очистка хранилища сайта в браузере удаляет и Basic, и SSO-запись. ## Журнал доступа diff --git a/documentation/ru/src/observability/admin-commands.md b/documentation/ru/src/observability/admin-commands.md index d427ed999..b3c277678 100644 --- a/documentation/ru/src/observability/admin-commands.md +++ b/documentation/ru/src/observability/admin-commands.md @@ -34,7 +34,7 @@ psql "host=127.0.0.1 port=6432 user=admin dbname=pgdoorman" | `SHOW LISTS` | Счётчики по категориям (databases, users, pools, clients, servers). | | `SHOW USERS` | Список пользователей и их режимы пула. | | `SHOW AUTH_QUERY` | Кэш `auth_query`: попадания/промахи/перезапросы, успехи/отказы аутентификации, ошибки исполнителя, счётчики динамических пулов. | -| `SHOW STARTUP_PARAMETERS` | Эффективный каскад `startup_parameters` по каждому пулу: параметр, значение и уровень, который дал итоговое значение. | +| `SHOW STARTUP_PARAMETERS` | Итоговые `startup_parameters` по каждому пулу: параметр, значение, источник и состояние применения. | | `SHOW SOCKETS` | Счётчики TCP- и Unix-сокетов по состоянию (только Linux — читает `/proc/net/`). | | `SHOW LOG_LEVEL` | Текущий уровень логирования. | | `SHOW VERSION` | Версия pg_doorman. | @@ -72,15 +72,15 @@ mydb | app | 12 | 4 | 0 | 4 | 36 | 0 ### `SHOW STARTUP_PARAMETERS` ``` -user | database | parameter | value | source -app | mydb | statement_timeout | 5s | general -app | mydb | plan_cache_mode | force_custom_plan | pool +user | database | parameter | value | source | state +app | mydb | statement_timeout | 5s | general | applied +app | mydb | plan_cache_mode | force_custom_plan | pool | applied ``` -- `source` показывает уровень, который дал итоговое значение: - `general`, `pool` или `auth_query`. -- Команда выводит тот же эффективный каскад, который используется при - сборке `StartupMessage` для новых бэкендов. +- `source` показывает источник значения: `general`, `pool` или + `auth_query`. +- `state` показывает, будет ли значение отправлено в ближайший + `StartupMessage`: `applied`, `dropped_due_to_budget` или `stale`. ### `SHOW POOL_COORDINATOR` diff --git a/documentation/ru/src/operations/monitoring-interner.md b/documentation/ru/src/operations/monitoring-interner.md index 6cdcd8b88..fabce80b3 100644 --- a/documentation/ru/src/operations/monitoring-interner.md +++ b/documentation/ru/src/operations/monitoring-interner.md @@ -31,7 +31,7 @@ pg_doorman. Хранилище разделено на две независим `rate(pg_doorman_query_interner_synthetic_misses_total[5m])`. Норма — плоский ноль. Любой всплеск означает: либо TTL вытеснил запись, на которую сослался клиент, либо драйвер рассчитывает - на безымянный prepared statement, переживший Sync. + на безымянный prepared statement, который остаётся доступным после Sync. ### Детализация diff --git a/documentation/ru/src/reference/general.md b/documentation/ru/src/reference/general.md index 36209204e..badb9ba8f 100644 --- a/documentation/ru/src/reference/general.md +++ b/documentation/ru/src/reference/general.md @@ -675,27 +675,27 @@ hostnossl all all 192.168.1.0/24 trust ### startup_parameters -Базовые параметры запуска PostgreSQL, которые записываются в -`StartupMessage` каждого бэкенда. `startup_parameters` уровня пула -переопределяют эти значения по ключу, а строки passthrough `auth_query` -могут переопределить их на уровне пользователя. +Базовые параметры PostgreSQL, которые pg_doorman добавляет в +`StartupMessage` каждого нового бэкенда. `startup_parameters` уровня +пула переопределяют эти значения по ключу, а passthrough `auth_query` +может переопределить их для конкретного пользователя. При загрузке конфигурации pg_doorman проверяет зарезервированные протокольные ключи (`user`, `database`, `replication`, `options`, `_pq_.*`), имена GUC, нулевые байты и размер этого уровня. Перед каждым -запуском бэкенда слитый каскад снова проверяется против -`MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается, pg_doorman -пропускает параметры, заданные оператором, для этого запуска и пишет +запуском бэкенда объединённый набор параметров снова проверяется по +лимиту `MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается, +pg_doorman пропускает операторские параметры для этого запуска и пишет предупреждение. -Если PostgreSQL отвергает заданный оператором параметр на старте -бэкенда, pg_doorman пробрасывает `ErrorResponse` PG клиенту без -изменений: никакой повторной попытки без этого ключа, никакого -скрытого карантина. Кумулятивный счётчик отказов экспонируется как +Если PostgreSQL отвергает параметр при запуске бэкенда, pg_doorman +возвращает клиенту `ErrorResponse` PostgreSQL без изменений: повторной +попытки без этого ключа не будет, сам ключ автоматически не отключается. +Накопительный счётчик отказов экспортируется как `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}`; -имя параметра и пользователя — в строке лога уровня warn. Эффективный -каскад по каждому пулу виден через `SHOW STARTUP_PARAMETERS` в -административной SQL-консоли и через `/api/pools` в веб-интерфейсе. +имя параметра и пользователя остаются в строке лога уровня `warn`. +Итоговые параметры по каждому пулу видны через `SHOW STARTUP_PARAMETERS` +в административной SQL-консоли и через `/api/pools` в веб-интерфейсе. По умолчанию: `{}`. diff --git a/documentation/ru/src/reference/pool.md b/documentation/ru/src/reference/pool.md index 78c2cf8af..d7f06e2b9 100644 --- a/documentation/ru/src/reference/pool.md +++ b/documentation/ru/src/reference/pool.md @@ -134,17 +134,17 @@ ### startup_parameters -Параметры запуска PostgreSQL уровня пула, которые записываются в -`StartupMessage` каждого бэкенда. Значения переопределяют +Параметры PostgreSQL уровня пула, которые pg_doorman добавляет в +`StartupMessage` каждого нового бэкенда. Значения переопределяют `general.startup_parameters` по ключу. В passthrough-пулах `auth_query` -пользовательская колонка `startup_parameters` может переопределить и этот -уровень. +колонка `startup_parameters` может переопределить и этот уровень для +конкретного пользователя. При загрузке конфигурации pg_doorman проверяет зарезервированные протокольные ключи, имена GUC, нулевые байты и размер этого уровня. Перед -каждым запуском бэкенда слитый каскад снова проверяется против -`MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается, pg_doorman -пропускает параметры, заданные оператором, для этого запуска и пишет +каждым запуском бэкенда объединённый набор параметров снова проверяется +по лимиту `MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается, +pg_doorman пропускает операторские параметры для этого запуска и пишет предупреждение. По умолчанию: `{}`. @@ -210,7 +210,7 @@ SQL-запрос для получения учётных данных. Он д Дополнительные колонки игнорируются, кроме необязательной `startup_parameters` типа `text`. В passthrough-режиме pg_doorman читает -её как JSON-объект с пользовательскими параметрами запуска PostgreSQL. +её как JSON-объект с параметрами запуска PostgreSQL для этого пользователя. Dedicated-режим игнорирует эту колонку и пишет предупреждение. В качестве плейсхолдера для имени пользователя используйте `$1`. diff --git a/documentation/ru/src/reference/prometheus.md b/documentation/ru/src/reference/prometheus.md index c2832b0f0..18e2ff016 100644 --- a/documentation/ru/src/reference/prometheus.md +++ b/documentation/ru/src/reference/prometheus.md @@ -66,7 +66,8 @@ pg_doorman экспортирует следующие метрики: | `pg_doorman_pools_bytes_total` | Накопительный счётчик байт, переданных через пулы соединений, по направлению (`received`/`sent`), пользователю и базе. Для пропускной способности используйте `rate(pg_doorman_pools_bytes_total[5m])`. | | `pg_doorman_pools_bytes` | Устаревшая gauge-версия `pg_doorman_pools_bytes_total`; будет удалена в 3.10. | | `pg_doorman_pool_size` | Сконфигурированный максимальный размер пула на пользователя и базу. Полезен для расчёта оставшейся ёмкости пула вместе с pg_doorman_pools_servers. | -| `pg_doorman_backend_startup_parameter_errors_total` | Накопительный счётчик отказов при запуске бэкенда из-за `startup_parameters`, заданных оператором. Лейблы: пул и SQLSTATE. Отклонённый параметр и имя пользователя пишутся в строку лога уровня warn, а не в лейблы метрики. | +| `pg_doorman_backend_startup_parameter_errors_total` | Накопительный счётчик запусков бэкенда, которые PostgreSQL отклонил из-за `startup_parameters`. Лейблы: пул и SQLSTATE. Отклонённый параметр и имя пользователя пишутся в строку лога уровня `warn`, а не в лейблы метрики. | +| `pg_doorman_startup_parameters_dropped_total` | Накопительный счётчик событий, когда pg_doorman отбросил `startup_parameters` до отправки `StartupMessage`. Лейблы: пул и причина (`cascade_budget_exceeded`, `packet_cap_exceeded`, `auth_query_oversize`, `auth_query_overlay_oversize`, `auth_query_bad_type`, `auth_query_invalid_json`, `auth_query_invalid_shape`, `auth_query_invalid_entry`, `dedicated_mode`). | ### Метрики запросов и транзакций diff --git a/documentation/ru/src/tutorials/binary-upgrade.md b/documentation/ru/src/tutorials/binary-upgrade.md index 91eaafd49..ad9682f84 100644 --- a/documentation/ru/src/tutorials/binary-upgrade.md +++ b/documentation/ru/src/tutorials/binary-upgrade.md @@ -184,10 +184,10 @@ now`), и старый процесс выходит. - Если `client_anonymous_prepared_cache_size` нового конфига меньше, лишние anonymous-записи вытесняются по LRU. Именованная часть не - ограничена и переживает миграцию полностью. Оставшиеся записи + ограничена и переносится полностью. Оставшиеся записи работают нормально. -- Anonymous prepared statements (`Parse` с пустым именем) переживают - миграцию, но требуют повторного `Parse` перед `Bind` в новом процессе. +- Anonymous prepared statements (`Parse` с пустым именем) переносятся в + новый процесс, но требуют повторного `Parse` перед `Bind`. - `DEALLOCATE ALL` после миграции очищает переданный кеш. Повторный `Parse` с тем же именем использует новый текст запроса. diff --git a/documentation/ru/src/tutorials/patroni-proxy.md b/documentation/ru/src/tutorials/patroni-proxy.md index 0091a37e3..ea62c59ba 100644 --- a/documentation/ru/src/tutorials/patroni-proxy.md +++ b/documentation/ru/src/tutorials/patroni-proxy.md @@ -6,7 +6,7 @@ - **Открытие членов кластера** через опрос `/cluster` с интервалом `cluster_update_interval` (по умолчанию 3 с) и по запросу `GET /update_clusters`. - **Маршрутизация по ролям.** Каждый listen-порт привязан к одной или нескольким ролям (`leader`, `sync`, `async`, `any`). Соединения с этого порта попадают на члена, у которого совпадает одна из указанных ролей. -- **Least-connections.** Для портов с несколькими допустимыми членами прокси держит счётчик соединений на каждого члена и отправляет новое соединение туда, где их меньше. Счётчики переживают cluster update. +- **Least-connections.** Для портов с несколькими допустимыми членами прокси держит счётчик соединений на каждого члена и отправляет новое соединение туда, где их меньше. Обновление кластера не сбрасывает эти счётчики. - **Отбрасывает реплики со старыми данными.** `max_lag_in_bytes` per-port исключает членов, у которых `replication_lag` (из `/cluster`) выше порога. Leader по лагу никогда не исключается. - **Пропускает не-running.** Допускаются только члены со `state: "running"`; `starting`, `stopped`, `crashed` и узлы с тегом `noloadbalance` фильтруются. diff --git a/documentation/ru/src/tutorials/pool-pressure.md b/documentation/ru/src/tutorials/pool-pressure.md index 884ab5b11..205bc66bb 100644 --- a/documentation/ru/src/tutorials/pool-pressure.md +++ b/documentation/ru/src/tutorials/pool-pressure.md @@ -906,7 +906,7 @@ herd) возвращается. с 50 ms и неделю наблюдайте `reserve_acq` и `evictions`. 4. Оставьте `min_connection_lifetime` на дефолте 30 000 ms, если у вас нет явной цели ускорить кросс-пуловую ребалансировку; - понижение увеличивает частоту eviction и churn соединений. + понижение увеличивает частоту выселений и повторных подключений. За чем следить после каждого изменения (все в `SHOW POOL_COORDINATOR`): @@ -1336,7 +1336,7 @@ PgBouncer и pg_doorman оба пулят соединения, но давле | Одновременные `connect()` к бэкенду в одном пуле | Однопоточный, обрабатывает события последовательно в пределах пула, вызовы `connect()` выпускаются по одному. | Ограничено `scaling_max_parallel_creates` (по умолчанию 2 на пул): не больше N одновременных подключений к PostgreSQL в пуле, лишние задачи ждут на ограничитель всплесков. | | Anticipation возвратов | Нет. Клиенты ждут следующего доступного соединения в порядке прихода, в пределах `wait_timeout`. | Event-driven anticipation: возвращающееся соединение будит ровно одного из ожидающих в очереди, часто ещё до того, как будет выпущен хоть один новый `connect()`. | | Прогрев `min_pool_size` | Поддерживается на каждом такте event loop (без отдельной задачи replenish). | Периодический фоновый replenish (`retain_connections_time`, по умолчанию 30 s), который отступает, когда ограничитель всплесков занят. | -| Повторный логин после ошибки | `server_login_retry` (по умолчанию 15 s) блокирует новые попытки логина после отказа бэкенда. | Аналога нет. Ошибки логина бэкенда пробрасываются клиенту на каждую попытку. | +| Повторный логин после ошибки | `server_login_retry` (по умолчанию 15 s) блокирует новые попытки логина после отказа бэкенда. | Аналога нет. Ошибки логина бэкенда возвращаются клиенту на каждую попытку. | | Jitter на lifetime | Нет. `server_lifetime` точный. | ±20% jitter на `server_lifetime` и `idle_timeout`, чтобы избежать одновременного массового закрытия. | | Ключ поиска пула | `(database, user, auth_type)` | `(database, user)` | | Честность между пользователями на общем лимите | First come first served на `max_db_connections`. | Reserve arbiter оценивает запросы по `(starving, queued_clients)`. | diff --git a/documentation/ru/src/tutorials/prepared-statements.md b/documentation/ru/src/tutorials/prepared-statements.md index 63375f0c6..154e02cbd 100644 --- a/documentation/ru/src/tutorials/prepared-statements.md +++ b/documentation/ru/src/tutorials/prepared-statements.md @@ -18,8 +18,8 @@ PgDoorman прозрачно переписывает каждый аноним Это уникальная возможность PgDoorman. PgBouncer (1.21+) и Odyssey поддерживают prepared statements в transaction mode, но только для -**именованных** statement; анонимный `Parse` пробрасывается без -изменений и перепланируется при каждом обращении. Ограничения кеша, +**именованных** statement; анонимный `Parse` проходит без +изменений и планируется заново при каждом обращении. Ограничения кеша, LRU, TTL и observability нужны не вместо производительности, а чтобы эта оптимизация оставалась управляемой под динамическим SQL. @@ -69,7 +69,7 @@ psycopg. Прикладной код выглядит как обычный па ## Почему это проблема в transaction-mode В transaction pooling один backend по очереди обслуживает разных -клиентов. Если пулер пробрасывает пустой `Parse` как есть, каждый +клиентов. Если пулер отправляет пустой `Parse` как есть, каждый `Bind` клиента приходит на backend, у которого плана для этого запроса нет. Горячие OLTP-пути платят CPU планировщика на каждом обращении. @@ -225,12 +225,12 @@ PgDoorman держит состояние prepared statements на трёх ур | Пулер | Кеш Parse/плана для анонимного prepared statement | | --------------- | :--------------------------------------------------- | | **PgDoorman** | Да: прозрачная подмена на `DOORMAN_` | -| PgBouncer 1.21+ | Нет: только named, анонимный пробрасывается as-is | +| PgBouncer 1.21+ | Нет: только named, анонимный проходит as-is | | Odyssey | Нет: только named, `pool_reserve_prepared_statement` | | PgCat | Нет: только named | В PgBouncer поддержка prepared statements появилась в 1.21, но -ограничена **именованными**: анонимный `Parse` пробрасывается без +ограничена **именованными**: анонимный `Parse` проходит без изменений, и каждый `Bind` запускает планировщик. Флаг `pool_reserve_prepared_statement` в Odyssey требует именованных statement; на анонимный трафик он не влияет. PgCat ведёт себя diff --git a/documentation/ru/src/tutorials/startup-parameters.md b/documentation/ru/src/tutorials/startup-parameters.md index 11c5d50fc..7c1fdd0ae 100644 --- a/documentation/ru/src/tutorials/startup-parameters.md +++ b/documentation/ru/src/tutorials/startup-parameters.md @@ -1,8 +1,8 @@ # Параметры запуска PostgreSQL -Иногда параметры PostgreSQL нужно задавать для каждого серверного -соединения, которое открывает pg_doorman, без правки `postgresql.conf`, -`ALTER ROLE` или `ALTER DATABASE`. Типичные случаи: +PgDoorman может задавать параметры PostgreSQL при открытии серверного +соединения, не меняя `postgresql.conf`, `ALTER ROLE` или +`ALTER DATABASE`. Это полезно, например, в таких случаях: - В горячем OLTP-пуле план переключается на generic после решения эвристики `plan_cache_mode = auto` и обратно уже не возвращается. @@ -10,18 +10,20 @@ другую нагрузку под этой ролью, а изменить нужно только один пул. - Приложение не задаёт `statement_timeout` или `idle_in_transaction_session_timeout`, а быстро доработать его нельзя. - Администратору БД нужен сессионный дефолт, который переживает - клиентский `RESET ALL`. + Администратору БД нужно сессионное значение по умолчанию, которое + сохранится после клиентского `RESET ALL`. - Одно приложение должно стабильно показывать конкретный `application_name`, независимо от значения, которое передаст драйвер, чтобы `pg_stat_activity` и аудит оставались читаемыми. -Для этого в pg_doorman есть `startup_parameters`: карта GUC PostgreSQL, -которую pg_doorman передаёт в каждое новое серверное соединение пула. +Для этого используется `startup_parameters`: набор GUC PostgreSQL, +который pg_doorman добавляет в `StartupMessage` новых серверных +соединений пула. ## Конфигурация -Каскад состоит из трёх уровней. Более узкий уровень выигрывает по ключу. +Значения применяются в три слоя. Более узкий слой переопределяет ключ +из предыдущего. ```toml [general.startup_parameters] @@ -32,8 +34,8 @@ plan_cache_mode = "force_custom_plan" work_mem = "64MB" ``` -После `SIGHUP` или `RELOAD` через консоль администратора каждый новый -бэкенд пула `checkout` стартует со значениями +После `SIGHUP` или `RELOAD` через консоль администратора каждое новое +серверное соединение пула `checkout` открывается со значениями `statement_timeout = 5s`, `plan_cache_mode = force_custom_plan` и `work_mem = 64MB`. В других пулах остаётся только `statement_timeout = 5s` из `general`; остальные значения берутся из @@ -58,21 +60,22 @@ WHERE rolname = $1; Колонка должна возвращаться как `text`. Если SQL отдаёт `json` или `jsonb`, добавьте явное приведение типа `::text`. pg_doorman читает её -именно как `text` и один раз пишет предупреждение для каждого -пользователя, у которого тип не совпал. +именно как `text` и пишет предупреждение для полученной строки, если +тип не совпал. Dedicated-режим `auth_query`, когда `server_user` задан, игнорирует эту колонку и один раз пишет предупреждение на пару `(пул, пользователь)`. -Один общий бэкенд не может одновременно иметь разные значения для -разных пользователей. +В этом режиме один серверный пул обслуживает разных пользователей, +поэтому per-user значения применить нельзя. ## Что pg_doorman делает со значениями -Слитая карта записывается в `StartupMessage` каждого бэкенда, который -открывает pg_doorman. PostgreSQL запоминает эти значения как сессионные -дефолты (`pg_settings.reset_val` и `pg_settings.source = 'client'`). -Поэтому клиентские `RESET ALL` и `DISCARD ALL` возвращают именно -значение, заданное оператором, а не исходное значение PostgreSQL. +pg_doorman добавляет итоговый набор параметров в `StartupMessage` +каждого нового бэкенда. PostgreSQL сохраняет эти значения как +сессионные значения по умолчанию (`pg_settings.reset_val` и +`pg_settings.source = 'client'`). Поэтому клиентские `RESET ALL` и +`DISCARD ALL` возвращают операторские значения, а не исходные +значения PostgreSQL. Значение видно со стороны клиента: @@ -101,58 +104,61 @@ checkout=> SET plan_cache_mode = 'auto'; RESET ALL; SHOW plan_cache_mode; ими сам, либо PostgreSQL обрабатывает их в `StartupMessage` особым образом. - Значения не должны содержать нулевой байт. -- Каждый уровень (`general` или `pool`) должен помещаться в - операторский бюджет: `MAX_STARTUP_PACKET_LENGTH` (10000 байт) минус - 512 байт, зарезервированных под служебные ключи pg_doorman. - -Перед запуском каждого бэкенда pg_doorman заново проверяет уже слитый -каскад против того же лимита. Два уровня, которые помещались по -отдельности, могут вместе выйти за бюджет, особенно когда `auth_query` -добавляет третий слой. В таком случае pg_doorman пропускает все GUC, -заданные оператором, для этого запуска, пишет размеры в лог и открывает -бэкенд с настройками PostgreSQL по умолчанию. +- Каждый уровень (`general` или `pool`) должен помещаться в лимит для + операторских параметров: `MAX_STARTUP_PACKET_LENGTH` (10000 байт) + минус 512 байт, зарезервированных под служебные ключи pg_doorman. + +Перед запуском каждого бэкенда pg_doorman заново проверяет объединённый +набор параметров по тому же лимиту. Два уровня, которые помещались по +отдельности, могут вместе выйти за лимит, особенно когда `auth_query` +добавляет третий слой. Если лимит превышает только слой `auth_query`, +pg_doorman отбрасывает этот слой и сохраняет baseline из `general` и +пула. Если не помещается сам baseline или полный startup-пакет, +pg_doorman пропускает все операторские параметры для этого +запуска и пишет размеры в лог. ## Что происходит, если PG отвергает параметр -Если PostgreSQL отвергает заданный оператором параметр на старте -бэкенда, pg_doorman возвращает клиенту PG-родной `ErrorResponse` как -есть. Клиент видит тот же sqlstate (`22023`, `42704`, `42501`, -`55P02` или любой другой код из стартового семейства) и то же -сообщение, что увидел бы при прямом подключении к PG. +Если PostgreSQL отвергает заданный оператором параметр при запуске +бэкенда, pg_doorman возвращает клиенту `ErrorResponse` PostgreSQL без +изменений. Клиент видит тот же sqlstate (`22023`, `42704`, `42501`, +`55P02` или любой другой код из стартового семейства) и то же сообщение, +что увидел бы при прямом подключении к PostgreSQL. -pg_doorman не пытается переподключиться без этого параметра, не -скрывает ключ и не ведёт per-pool карантин. Следующее подключение -клиента отправит тот же `StartupMessage` и упадёт так же, пока +pg_doorman не пробует повторить подключение без отклонённого параметра +и не отключает этот ключ автоматически для пула. Следующее подключение +клиента отправит тот же `StartupMessage` и получит ту же ошибку, пока оператор не исправит конфигурацию. ## Наблюдаемость -Эффективный каскад по каждому пулу виден через административную +Итоговые параметры по каждому пулу видны через административную SQL-консоль: ```text admin> SHOW STARTUP_PARAMETERS; - user | database | parameter | value | source --------+----------+------------------+-------------------+----------- - shop | checkout | plan_cache_mode | force_custom_plan | pool - shop | reports | statement_timeout| 10s | general + user | database | parameter | value | source | state +------+----------+-------------------+-------------------+---------+-------- + shop | checkout | plan_cache_mode | force_custom_plan | pool | applied + shop | reports | statement_timeout | 10s | general | applied ``` -Веб-интерфейс показывает тот же набор в секции «Startup parameters -(operator-injected)» на странице пула. +Веб-интерфейс показывает эти же строки на странице пула в секции +«Startup parameters (operator-injected)». В Prometheus: - `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}` - считает каждый запуск бэкенда, отвергнутый PostgreSQL из-за + считает попытки запуска бэкенда, которые PostgreSQL отклонил из-за параметра, заданного оператором. Имя параметра и пользователя - пишутся в warn-строке лога; в лейблы они не включены, чтобы + пишутся в строку лога уровня `warn`; в лейблы они не включены, чтобы динамические `auth_query`-пулы не раздували количество серий. -Разумная отправная точка для оповещения: ненулевая скорость роста -`pg_doorman_backend_startup_parameter_errors_total` для одного и того -же пула в течение нескольких минут означает, что каждое подключение -к пулу падает на одном и том же GUC и конфигурацию нужно править. +Разумная отправная точка для алерта: если +`pg_doorman_backend_startup_parameter_errors_total` растёт по одному и +тому же пулу несколько минут подряд, новые подключения к этому пулу +падают на одном и том же GUC. Конфигурацию нужно исправить до возврата +трафика. ## Когда это не нужно @@ -160,8 +166,8 @@ admin> SHOW STARTUP_PARAMETERS; `startup_parameters` добавляет ещё одну настройку без изменения поведения. - Тюнинг на одну транзакцию (`SET LOCAL`). `startup_parameters` задают - сессионные дефолты; параметры уровня транзакции должно выставлять - приложение. + сессионные значения по умолчанию; параметры уровня транзакции должно + выставлять приложение. - Значения, которые зависят от текущего запроса. Параметры запуска действуют для всех транзакций бэкенда на протяжении его жизни; режима «на один statement» нет. diff --git a/documentation/ru/src/tutorials/troubleshooting.md b/documentation/ru/src/tutorials/troubleshooting.md index 1342c129d..ab73289fc 100644 --- a/documentation/ru/src/tutorials/troubleshooting.md +++ b/documentation/ru/src/tutorials/troubleshooting.md @@ -18,7 +18,7 @@ SELECT usename, passwd FROM pg_shadow WHERE usename = 'your_user'; ### Когда username пула отличается от роли на backend -Когда обращённый к клиенту `username` в PgDoorman не совпадает с реальной ролью PostgreSQL, passthrough работать не может — нечего пробрасывать. Дайте явные credentials: +Когда обращённый к клиенту `username` в PgDoorman не совпадает с реальной ролью PostgreSQL, passthrough работать не может: у pg_doorman нет пароля для backend-роли. Дайте явные credentials: ```yaml users: @@ -29,7 +29,7 @@ users: pool_size: 40 ``` -Это же путь для JWT-аутентификации, где клиент не присылает пароль и пробрасывать нечего. +Это же путь для JWT-аутентификации, где клиент не присылает пароль. ```admonish tip title="Где взять хеш пароля" `pg_doorman generate --host …` интроспектирует PostgreSQL и собирает конфиг с уже подставленными хешами. Быстрее, чем копировать руками из `pg_shadow`. diff --git a/grafana/generate_dashboard.py b/grafana/generate_dashboard.py index 7acd8aa95..f0bbcaac4 100644 --- a/grafana/generate_dashboard.py +++ b/grafana/generate_dashboard.py @@ -739,7 +739,7 @@ def expanded_row(title: str): "{{sqlstate}}", ), ], unit="ops", w=12, - desc="Per-pool rate of backend startups PG rejected because of an operator-supplied parameter. Split by SQLSTATE: 22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param. Non-zero for the same pool over a few minutes means every connect through that pool fails on the same operator GUC — fix general/pool/auth_query. Filters on $user/$database do not apply — the counter has only the `pool` label.", + desc="Rate of backend startups PostgreSQL rejected because of a configured startup parameter. Split by SQLSTATE: 22023 invalid_value, 42704 undefined_object, 42501 insufficient_privilege, 55P02 cant_change_runtime_param. Growth for the same pool over several minutes means new connects through that pool fail on the same GUC; fix general/pool/auth_query. Filters on $user/$database do not apply: the counter has only the `pool` label.", ) p_sp_errors_by_pool = ts_panel( "PG-Side Rejections by Pool", [ @@ -757,7 +757,7 @@ def expanded_row(title: str): "{{reason}}", ), ], unit="ops", w=24, - desc="Operator-supplied entries pg_doorman dropped BEFORE the StartupMessage went on the wire — the failure mode the PG-side counter above cannot see. Reasons: cascade_budget_exceeded (merged map past 9 488 bytes), packet_cap_exceeded (full packet past PG MAX_STARTUP_PACKET_LENGTH 10 000 bytes), auth_query_oversize (per-user JSON column past operator budget), auth_query_overlay_oversize (overlay pushes cascade over budget but baseline alone fits — pg_doorman ships the baseline), auth_query_bad_type / auth_query_invalid_json / auth_query_invalid_shape (column type, JSON parse, or non-object payload), auth_query_invalid_entry (one or more JSON entries failed validation), dedicated_mode (per-user GUC ignored because the pool shares one backend across users). Non-zero on any reason needs operator attention. Filters on $user/$database do not apply — the counter has only `pool` and `reason` labels.", + desc="Startup parameter drop events before pg_doorman sends StartupMessage. Reasons: cascade_budget_exceeded (resolved set above 9 488 bytes), packet_cap_exceeded (full packet above PG MAX_STARTUP_PACKET_LENGTH 10 000 bytes), auth_query_oversize (per-user JSON column above the startup-parameter budget), auth_query_overlay_oversize (auth_query overlay overflows but the general/pool baseline still fits), auth_query_bad_type / auth_query_invalid_json / auth_query_invalid_shape (column type, JSON parse, or non-object payload), auth_query_invalid_entry (one or more JSON entries failed validation), dedicated_mode (per-user GUC ignored because the pool shares one backend across users). Any increase should be investigated. Filters on $user/$database do not apply: the counter has only `pool` and `reason` labels.", ) # --------------------------------------------------------------------------- diff --git a/pg_doorman.toml b/pg_doorman.toml index 564d3055f..34e62f498 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -407,17 +407,17 @@ hba = [] # """ # -------------------------------------------------------------------------- -# PostgreSQL Startup Parameters (operator-defined GUCs) +# PostgreSQL Startup Parameters (operator-set GUCs) # -------------------------------------------------------------------------- -# Baseline PostgreSQL GUCs that pg_doorman adds to every backend -# StartupMessage. Pools override values per key; auth_query in -# passthrough mode can override them per user. Config load +# Baseline PostgreSQL GUCs that pg_doorman adds to each new +# backend StartupMessage. Pools override values per key; +# passthrough auth_query can override them per user. Config load # validates reserved keys, GUC names, null bytes, and this level's -# size. The merged cascade is checked again at backend startup; if -# it does not fit PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes), -# pg_doorman skips operator-supplied GUCs for that startup and logs -# a warning. +# size. Before backend startup, pg_doorman checks the resolved +# parameter set again; if it does not fit PG's +# MAX_STARTUP_PACKET_LENGTH (10000 bytes), pg_doorman skips +# operator-set GUCs for that startup and logs a warning. # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters = { plan_cache_mode = "force_custom_plan", work_mem = "64MB" } @@ -621,8 +621,9 @@ log_client_parameter_status_changes = false # backend StartupMessage. Wins over general.startup_parameters # per key; auth_query in passthrough mode wins over this. # Config load validates reserved keys, GUC names, null bytes, and -# this level's size. The merged cascade is checked again at backend -# startup against PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). +# this level's size. Before backend startup, pg_doorman checks the +# resolved parameter set again against PG's +# MAX_STARTUP_PACKET_LENGTH (10000 bytes). # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters = { plan_cache_mode = "force_custom_plan" } diff --git a/pg_doorman.yaml b/pg_doorman.yaml index 3344b9f2f..7618ac673 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -447,17 +447,17 @@ general: # host all all 0.0.0.0/0 reject # -------------------------------------------------------------------------- - # PostgreSQL Startup Parameters (operator-defined GUCs) + # PostgreSQL Startup Parameters (operator-set GUCs) # -------------------------------------------------------------------------- - # Baseline PostgreSQL GUCs that pg_doorman adds to every backend - # StartupMessage. Pools override values per key; auth_query in - # passthrough mode can override them per user. Config load + # Baseline PostgreSQL GUCs that pg_doorman adds to each new + # backend StartupMessage. Pools override values per key; + # passthrough auth_query can override them per user. Config load # validates reserved keys, GUC names, null bytes, and this level's - # size. The merged cascade is checked again at backend startup; if - # it does not fit PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes), - # pg_doorman skips operator-supplied GUCs for that startup and logs - # a warning. + # size. Before backend startup, pg_doorman checks the resolved + # parameter set again; if it does not fit PG's + # MAX_STARTUP_PACKET_LENGTH (10000 bytes), pg_doorman skips + # operator-set GUCs for that startup and logs a warning. # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters: @@ -667,8 +667,9 @@ pools: # backend StartupMessage. Wins over general.startup_parameters # per key; auth_query in passthrough mode wins over this. # Config load validates reserved keys, GUC names, null bytes, and - # this level's size. The merged cascade is checked again at backend - # startup against PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). + # this level's size. Before backend startup, pg_doorman checks the + # resolved parameter set again against PG's + # MAX_STARTUP_PACKET_LENGTH (10000 bytes). # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters: diff --git a/src/app/generate/docs.rs b/src/app/generate/docs.rs index 05b332f43..621884d6b 100644 --- a/src/app/generate/docs.rs +++ b/src/app/generate/docs.rs @@ -470,16 +470,16 @@ fn write_prometheus_metrics_section(out: &mut String) { let _ = writeln!(out, "| `pg_doorman_auth_query_executor` | DEPRECATED, removed in 3.10. Gauge mirror of `pg_doorman_auth_query_executor_total`. |"); let _ = writeln!(out, "| `pg_doorman_auth_query_dynamic_pools` | Auth query dynamic pool lifecycle metrics by type and database. Types include: `current` (currently active dynamic pools), `created` (total pools created since startup), `destroyed` (total pools garbage-collected or removed on RELOAD). Only relevant in passthrough mode. |\n"); - // Operator-supplied startup_parameters - let _ = writeln!(out, "### Operator-supplied startup_parameters\n"); + // Configured startup_parameters + let _ = writeln!(out, "### Configured startup_parameters\n"); let _ = writeln!( out, - "These metrics cover two failure points for operator-supplied startup parameters. `pg_doorman_backend_startup_parameter_errors_total` counts values that PostgreSQL rejected after pg_doorman sent the `StartupMessage`. `pg_doorman_startup_parameters_dropped_total` counts values pg_doorman dropped before sending because the merged cascade was too large or an auth_query JSON entry was invalid.\n" + "These metrics cover two failure points for configured startup parameters. `pg_doorman_backend_startup_parameter_errors_total` counts backend startups PostgreSQL rejected after pg_doorman sent the `StartupMessage`. `pg_doorman_startup_parameters_dropped_total` counts drop events before `StartupMessage`, either because the resolved parameter set was too large or because an auth_query JSON value was invalid.\n" ); let _ = writeln!(out, "| Metric | Description |"); let _ = writeln!(out, "|--------|-------------|"); - let _ = writeln!(out, "| `pg_doorman_backend_startup_parameter_errors_total` | Counter by `(pool, sqlstate)`. Increments when PostgreSQL rejects a backend startup and the `ErrorResponse` names a startup parameter sent by pg_doorman. SQLSTATE class `57P` is excluded because Patroni-assisted fallback handles those errors. The failing parameter name and username are written to the warning log line, not to labels. pg_doorman first parses the common `parameter \"\"` phrase, then scans the message for any sent key in double quotes. If neither lookup finds a key, the counter is not incremented. |"); - let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman drops operator-supplied entries before sending `StartupMessage`. Reasons: `cascade_budget_exceeded`, `packet_cap_exceeded`, `auth_query_oversize`, `auth_query_overlay_oversize`, `auth_query_bad_type`, `auth_query_invalid_json`, `auth_query_invalid_shape`, `auth_query_invalid_entry`, `dedicated_mode`. |\n"); + let _ = writeln!(out, "| `pg_doorman_backend_startup_parameter_errors_total` | Counter by `(pool, sqlstate)`. Increments when PostgreSQL rejects a backend startup and the `ErrorResponse` names a startup parameter sent by pg_doorman. SQLSTATEs with the `57P` prefix are excluded because Patroni-assisted fallback handles those errors. The failing parameter name and username are written to the warning log line, not to labels. pg_doorman first parses the common `parameter \"\"` phrase, then scans the message for any sent key in double quotes. If neither lookup finds a key, the counter is not incremented. |"); + let _ = writeln!(out, "| `pg_doorman_startup_parameters_dropped_total` | Counter by `(pool, reason)`. Increments when pg_doorman drops startup parameters before sending `StartupMessage`. Reasons: `cascade_budget_exceeded`, `packet_cap_exceeded`, `auth_query_oversize`, `auth_query_overlay_oversize`, `auth_query_bad_type`, `auth_query_invalid_json`, `auth_query_invalid_shape`, `auth_query_invalid_entry`, `dedicated_mode`. |\n"); // Server Metrics let _ = writeln!(out, "### Server Metrics\n"); diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index 4e1e8a2a9..9631ffd79 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -54,7 +54,7 @@ sections: en: "Access Control (pg_hba - Recommended)" ru: "Контроль доступа (pg_hba — рекомендуется)" startup_parameters: - en: "PostgreSQL Startup Parameters (operator-defined GUCs)" + en: "PostgreSQL Startup GUCs" ru: "Параметры запуска PostgreSQL (GUC, задаваемые оператором)" pool_server: en: "Server Connection Settings" @@ -1152,36 +1152,37 @@ fields: startup_parameters: config: en: | - Baseline PostgreSQL GUCs that pg_doorman adds to every backend - StartupMessage. Pools override values per key; auth_query in - passthrough mode can override them per user. Config load + Baseline PostgreSQL GUCs that pg_doorman adds to each new + backend StartupMessage. Pools override values per key; + passthrough auth_query can override them per user. Config load validates reserved keys, GUC names, null bytes, and this level's - size. The merged cascade is checked again at backend startup; if - it does not fit PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes), - pg_doorman skips operator-supplied GUCs for that startup and logs - a warning. + size. Before backend startup, pg_doorman checks the resolved + parameter set again; if it does not fit PG's + MAX_STARTUP_PACKET_LENGTH (10000 bytes), pg_doorman skips + configured GUCs for that startup and logs a warning. Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } ru: | Базовые GUC PostgreSQL, которые pg_doorman добавляет в - StartupMessage каждого бэкенда. Пулы могут переопределять - значения по ключу; auth_query в режиме passthrough может - переопределять их на уровне пользователя. При загрузке - конфигурации проверяются зарезервированные ключи, имена GUC, - нулевые байты и размер этого уровня. Слитый каскад проверяется - снова при запуске бэкенда; если он не помещается в лимит PG + StartupMessage каждого нового бэкенда. Пулы могут + переопределять значения по ключу; auth_query в режиме + passthrough может переопределять их для конкретного + пользователя. При загрузке конфигурации проверяются + зарезервированные ключи, имена GUC, нулевые байты и размер + этого уровня. Объединённый набор параметров снова проверяется + при запуске бэкенда; если он не помещается в лимит PG MAX_STARTUP_PACKET_LENGTH (10000 байт), pg_doorman пропускает - GUC, заданные оператором, для этого запуска и пишет предупреждение. + операторские GUC для этого запуска и пишет предупреждение. Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } doc: | - Map of PostgreSQL configuration parameter names to string values. pg_doorman writes them into each backend `StartupMessage`; PostgreSQL stores them as the session reset defaults, so client `RESET ALL` / `DISCARD ALL` returns to these values. + Map of PostgreSQL configuration parameter names to string values. pg_doorman writes them into each new backend `StartupMessage`; PostgreSQL stores them as the session reset defaults, so client `RESET ALL` / `DISCARD ALL` returns to these values. Cascade order: `general.startup_parameters`, then `pools..startup_parameters`, then the optional `startup_parameters` JSON column returned by passthrough `auth_query`. Later layers win per key. Dedicated-mode `auth_query` pools ignore the per-user column because one shared backend serves multiple roles. - Validation at config load rejects reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`), invalid GUC names, null bytes, and per-level maps that exceed the operator budget. Before each backend startup, pg_doorman checks the full merged cascade against PG's `MAX_STARTUP_PACKET_LENGTH` (10 000 bytes); if it does not fit, pg_doorman drops all operator-supplied keys for that startup and logs the event. + Validation at config load rejects reserved protocol keys (`user`, `database`, `replication`, `options`, anything starting with `_pq_.`), invalid GUC names, null bytes, and per-level maps that exceed the startup-parameter budget. Before each backend startup, pg_doorman checks the resolved parameter set against PG's `MAX_STARTUP_PACKET_LENGTH` (10 000 bytes); if it does not fit, pg_doorman drops the auth_query overlay when the baseline still fits, otherwise it drops all configured keys for that startup and logs the event. - If PostgreSQL rejects a parameter at backend startup, pg_doorman returns the PG `ErrorResponse` to the client unchanged. There is no retry with the key removed and no per-key quarantine. The cumulative count is exported as `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}`; the parameter name and username are written to the corresponding warning log line. + If PostgreSQL rejects a parameter at backend startup, pg_doorman returns PostgreSQL's `ErrorResponse` to the client unchanged. There is no retry with the key removed, and pg_doorman does not automatically disable that key for the pool. The cumulative count is exported as `pg_doorman_backend_startup_parameter_errors_total{pool, sqlstate}`; the parameter name and username are written to the corresponding warning log line. - Inspect the effective per-pool cascade with `SHOW STARTUP_PARAMETERS` or the `/api/pools` REST endpoint. + Inspect the resolved per-pool values with `SHOW STARTUP_PARAMETERS` or the `/api/pools` REST endpoint. default: "{} (empty)" pool: @@ -1323,11 +1324,11 @@ fields: config: en: | Don't evict connections younger than this (milliseconds). - Protects freshly created connections from eviction churn + Protects freshly created connections from repeated evictions between user pools sharing the same database. ru: | Не изымать соединения моложе этого значения (миллисекунды). - Защищает свежесозданные соединения от постоянного churn'а + Защищает свежесозданные соединения от постоянных выселений между user-пулами, разделяющими одну базу данных. doc: | Minimum age (in milliseconds) a connection must reach before it can be evicted by the @@ -1490,21 +1491,22 @@ fields: backend StartupMessage. Wins over general.startup_parameters per key; auth_query in passthrough mode wins over this. Config load validates reserved keys, GUC names, null bytes, and - this level's size. The merged cascade is checked again at backend - startup against PG's MAX_STARTUP_PACKET_LENGTH (10000 bytes). + this level's size. Before backend startup, pg_doorman checks the + resolved parameter set again against PG's + MAX_STARTUP_PACKET_LENGTH (10000 bytes). Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } ru: | - Переопределения параметров PostgreSQL для этого пула, - передаваемые в StartupMessage бэкенда. Имеют приоритет над - general.startup_parameters по ключу; auth_query в режиме - passthrough имеет приоритет над настройками пула. При загрузке - конфигурации проверяются зарезервированные ключи, имена GUC, - нулевые байты и размер этого уровня. Слитый каскад снова - проверяется при запуске бэкенда против лимита PG - MAX_STARTUP_PACKET_LENGTH (10000 байт). + Переопределения параметров PostgreSQL для этого пула. pg_doorman + добавляет их в StartupMessage каждого нового бэкенда. Значения + имеют приоритет над general.startup_parameters по ключу; + auth_query в режиме passthrough имеет приоритет над настройками + пула. При загрузке конфигурации проверяются зарезервированные + ключи, имена GUC, нулевые байты и размер этого уровня. + Объединённый набор параметров снова проверяется при запуске + бэкенда по лимиту PG MAX_STARTUP_PACKET_LENGTH (10000 байт). Пример: startup_parameters = { plan_cache_mode = "force_custom_plan" } doc: | - Per-pool map of PostgreSQL configuration parameters. Validation rules match those documented for [`general.startup_parameters`](#startup-parameters): reserved keys, GUC naming, null bytes, and the operator budget within PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) `StartupMessage` cap. + Per-pool map of PostgreSQL configuration parameters. Validation rules match those documented for [`general.startup_parameters`](#startup-parameters): reserved keys, GUC naming, null bytes, and the startup-parameter budget within PG's `MAX_STARTUP_PACKET_LENGTH` (10 000-byte) `StartupMessage` cap. In the cascade `general` → `pool` → `auth_query`, this layer overrides `general` per key, and a passthrough auth_query entry overrides this layer. Dedicated-mode `auth_query` pools ignore the per-user column because one shared backend serves multiple users. See [`general.startup_parameters`](#startup-parameters) for validation rules, failure behavior, and observability. default: "{} (empty)" diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 375a7a156..8bd6fd9c7 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -937,12 +937,10 @@ where auth_client_key.map(BackendAuthMethod::ScramPassthrough) }; - // Forward the per-user overlay we just fetched. The - // caller's CacheEntry holds the exact row that authenticated - // this user; passing it through avoids a re-peek of the - // global auth_query cache in create_dynamic_pool, which - // could otherwise see a different overlay under low TTLs - // or a concurrent refetch. + // Use the overlay from the same auth_query row that + // authenticated this user. That keeps dynamic-pool creation + // tied to this login instead of reading the global cache + // again while TTL expiry or a concurrent refetch is changing it. let fetched_overlay = Arc::clone(&cache_entry.startup_parameters); let mut pool = create_dynamic_pool(pool_name, username, backend_auth, fetched_overlay) .map_err(|err| { diff --git a/src/pool/dynamic.rs b/src/pool/dynamic.rs index 09d22ca02..1d95dfc23 100644 --- a/src/pool/dynamic.rs +++ b/src/pool/dynamic.rs @@ -28,10 +28,9 @@ use super::{ /// /// On RELOAD, dynamic pools are dropped (not in config) and recreated /// on the next client connection with fresh settings. -/// `fetched_overlay` is the exact per-user `startup_parameters` map the -/// caller just fetched from `AuthQueryCache`. Passing it in avoids the -/// race where this function would re-peek the cache and see a different -/// (or missing) overlay under low TTLs or concurrent refetches. +/// `fetched_overlay` is the per-user `startup_parameters` map from the +/// auth_query row that authenticated this user. Passing it in ties pool +/// creation to that row instead of reading the cache again. pub fn create_dynamic_pool( pool_name: &str, username: &str, @@ -140,15 +139,12 @@ pub fn create_dynamic_pool( std::sync::Arc::new(merged) }; - // Convert the caller's HashMap snapshot (the same one freshly - // returned by `cache.get_or_fetch(username)` in auth/mod.rs) into - // the BTreeMap shape ServerPool stores. The caller owns the - // snapshot, so there is no re-peek of the global cache here — - // an interleaved refetch cannot swap a different overlay under us. - // Dedicated-mode pools never reach this path (auth/mod.rs uses the - // shared pool branch instead), but the filter stays for defence- - // in-depth in case a future caller forwards a non-empty overlay - // through the dedicated-mode branch by accident. + // Convert the caller's HashMap snapshot into the BTreeMap shape + // ServerPool stores. The snapshot comes from the auth_query row used + // for this login, so TTL expiry or an interleaved refetch cannot + // change the overlay while the pool is created. Dedicated-mode pools + // should not reach this path, but keep the guard so a future caller + // cannot attach a per-user overlay to a shared backend pool. let per_user_startup_overlay: std::sync::Arc> = { let is_dedicated = super::get_auth_query_state(pool_name) .map(|state| state.config.is_dedicated_mode()) diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index 7e34c52b8..bb2525a83 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -470,7 +470,7 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// `42501`, `55P02`, or any other code under the startup-parameter /// family — pg_doorman does not pre-filter by SQLSTATE). /// -/// SQLSTATE class `57P*` (server unavailable) is excluded: those +/// SQLSTATEs with the `57P` prefix (server unavailable) are excluded: those /// `ErrorResponse`s are surfaced as `ServerUnavailableError` to drive the /// Patroni-assisted fallback path before the counter branch is reached. /// @@ -487,12 +487,12 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// The parameter name and username are intentionally NOT in the label /// set so a dynamic `auth_query` pool that mints per-tenant roles cannot /// blow up Prometheus series count by reading user input into labels. -/// Counts cases where pg_doorman dropped operator-supplied +/// Counts cases where pg_doorman dropped configured /// `startup_parameters` *before* the StartupMessage went on the wire — /// the failure mode the per-pool `*_errors_total` counter cannot see /// because PG never had a chance to reject. Every reason increments /// the counter by 1 per drop event (one backend spawn that dropped -/// the cascade, one parsed row that contained invalid entries, one +/// the resolved set, one parsed row that contained invalid entries, one /// row whose overlay was ignored because of dedicated mode), so /// `rate by(reason)` is dimensionally consistent regardless of how /// many individual keys the offending row carried. Per-entry detail @@ -504,9 +504,9 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// series per pool name, so a multi-user database collapses into a /// single row. Per-user attribution lives in the warn log line. /// * `reason` — bounded enum: -/// * `cascade_budget_exceeded` — the merged general+pool+auth_query -/// map exceeded the operator budget (`MAX_OPERATOR_BUDGET`, 9 488 -/// bytes). Every operator-supplied key was dropped for that spawn +/// * `cascade_budget_exceeded` — the resolved general+pool+auth_query +/// map exceeded the startup-parameter budget (`MAX_OPERATOR_BUDGET`, 9 488 +/// bytes). Every configured key was dropped for that spawn /// and the backend got PG defaults instead. /// * `packet_cap_exceeded` — the full StartupMessage including user, /// application_name and database would exceed PG's @@ -517,8 +517,8 @@ pub(crate) static LISTENER_REJECTIONS_TOTAL: Lazy = Lazy::new(|| /// parse time, so the per-user overlay is ignored. /// * `auth_query_overlay_oversize` — the merged baseline+overlay was /// over budget, but the baseline alone fits. Keeps general/pool -/// guardrails (statement_timeout, lock_timeout, …) for that -/// user instead of stripping the operator cascade outright. +/// defaults (statement_timeout, lock_timeout, ...) for that user +/// instead of stripping the whole configured parameter set. /// * `auth_query_bad_type` — the auth_query `startup_parameters` /// column has a non-text type (likely `json`/`jsonb`); pg_doorman /// reads it as text, so the row's overlay is dropped. Cast to @@ -542,8 +542,8 @@ pub(crate) static STARTUP_PARAMETERS_DROPPED_TOTAL: Lazy = Lazy:: let counter = IntCounterVec::new( Opts::new( "pg_doorman_startup_parameters_dropped_total", - "Cumulative count of operator-supplied startup_parameters \ - entries pg_doorman dropped before sending StartupMessage. \ + "Cumulative count of startup_parameters drop events before \ + pg_doorman sends StartupMessage. \ Labels: pool, reason (cascade_budget_exceeded, \ packet_cap_exceeded, auth_query_oversize, \ auth_query_overlay_oversize, auth_query_bad_type, \ @@ -565,9 +565,9 @@ pub(crate) static BACKEND_STARTUP_PARAMETER_ERRORS_TOTAL: Lazy = "pg_doorman_backend_startup_parameter_errors_total", "Cumulative count of backend startup attempts pg_doorman \ aborted because PostgreSQL ErrorResponse identified a key \ - this pool sent in StartupMessage (operator-supplied \ - startup_parameters cascade). Labels: pool, sqlstate. \ - SQLSTATE class 57P (server unavailable) is excluded — \ + this pool sent in StartupMessage (configured \ + startup_parameters). Labels: pool, sqlstate. \ + SQLSTATEs with the 57P prefix (server unavailable) are excluded — \ those rejections take the Patroni-assisted fallback path \ instead. The failing parameter name and username are in \ the corresponding warn log line; kept out of the label set \ diff --git a/tests/bdd/features/startup-parameters.feature b/tests/bdd/features/startup-parameters.feature index b24f83dc6..15b047c06 100644 --- a/tests/bdd/features/startup-parameters.feature +++ b/tests/bdd/features/startup-parameters.feature @@ -1,9 +1,9 @@ @startup-parameters Feature: Per-pool startup_parameters - pg_doorman sends operator-supplied PostgreSQL run-time parameters in - every backend StartupMessage on a three-level cascade: general defaults, - per-pool overrides, and (in auth_query passthrough mode) per-user - overrides from an optional JSON column. + pg_doorman sends configured PostgreSQL runtime parameters in every + backend StartupMessage. Values come from general defaults, per-pool + overrides, and (in auth_query passthrough mode) per-user overrides + from an optional JSON column. Scenario: general.startup_parameters apply on a fresh backend Given PostgreSQL started with pg_hba.conf: @@ -113,7 +113,7 @@ Feature: Per-pool startup_parameters Then psql query "SHOW plan_cache_mode" via pg_doorman as user "sp_tuned_user" to database "postgres" with password "tuned_pass" returns "force_custom_plan" And psql query "SHOW plan_cache_mode" via pg_doorman as user "sp_plain_user" to database "postgres" with password "plain_pass" returns "auto" - Scenario: operator-supplied application_name in startup_parameters wins over pool default + Scenario: startup_parameters application_name wins over pool default Given PostgreSQL started with pg_hba.conf: """ local all all trust @@ -272,10 +272,9 @@ Feature: Per-pool startup_parameters pool_size = 2 """ # PostgreSQL rejects the unknown GUC at backend startup; pg_doorman - # forwards the rejection to the client. The very next connect repeats - # the same failure - no quarantine, no silent strip, no graceful - # recovery: the operator must fix the parameter in the config for the - # pool to start working. + # returns that rejection to the client. The next connect repeats the + # same failure: pg_doorman does not strip the key or disable it for the + # pool. The operator must fix the parameter in the config. Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" fails Then psql connection to pg_doorman as user "example_user_1" to database "example_db" with password "test" fails And pg_doorman log contains "nonexistent_guc_yyy" @@ -322,7 +321,7 @@ Feature: Per-pool startup_parameters """ Then the command should succeed # The first SHOW returns the client-set value, the second returns the - # operator-supplied startup default (RESET ALL falls back to reset_val, + # Startup default configured by pg_doorman (RESET ALL falls back to reset_val, # which is the value PG saw in StartupMessage). And the command output should contain "auto" And the command output should contain "force_custom_plan" @@ -507,7 +506,7 @@ Feature: Per-pool startup_parameters """ Then psql connection to pg_doorman as user "pt_md5_user" to database "postgres" with password "md5_pass" succeeds - Scenario: admin SHOW STARTUP_PARAMETERS lists the merged cascade per pool + Scenario: admin SHOW STARTUP_PARAMETERS lists resolved parameters per pool Given PostgreSQL started with pg_hba.conf: """ local all all trust @@ -546,7 +545,10 @@ Feature: Per-pool startup_parameters -U admin -d pgdoorman -A -t -c 'SHOW STARTUP_PARAMETERS' """ Then the command should succeed - # Default psql -A -t output is pipe-delimited: user|database|parameter|value|source. - # The general baseline shows up as source=general; the pool override as source=pool. - And the command output should contain "statement_timeout|10s|general" - And the command output should contain "plan_cache_mode|force_custom_plan|pool" + # Default psql -A -t output is pipe-delimited: + # user|database|parameter|value|source|state. + # The general baseline shows up as source=general, the pool override + # as source=pool, and the runtime state stays `applied` because the + # cascade fits the operator budget for this fixture. + And the command output should contain "statement_timeout|10s|general|applied" + And the command output should contain "plan_cache_mode|force_custom_plan|pool|applied" From 028049937390c4d37c0c18c6dd04be0bf6848eb1 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:22:30 +0300 Subject: [PATCH 64/72] feat(web): render startup_parameter value and state in PoolDetail Closes codex MED #10 (fresh review 2026-05-12). The frontend assumed `value: string` always present. After the backend started masking values for anonymous viewers (HIGH #5 fix) React rendered "undefined" in the pool detail page; after the backend added the `state` field (MED #12 fix) the UI had no way to surface keys whose snapshot is stale or whose cascade overflows the budget. Update the TypeScript shape: `value?: string` (optional) and `state: string` (required, defaults to "applied" in the renderer). The list item shows the value or "***" for anonymous viewers, and prints the state inline next to the source label when state != "applied". Stale overlays render yellow; budget-overflow drops render red. Rebuild frontend/dist so the embedded bundle matches. --- frontend/dist/.source-hash | 2 +- frontend/dist/assets/index-BNakVyVm.js.gz | Bin 0 -> 124312 bytes frontend/dist/assets/index-Vn7yKSwJ.js.gz | Bin 124227 -> 0 bytes frontend/dist/index.html.gz | Bin 323 -> 320 bytes frontend/src/pages/PoolDetail.tsx | 50 ++++++++++++++++------ frontend/src/types.ts | 12 +++++- 6 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 frontend/dist/assets/index-BNakVyVm.js.gz delete mode 100644 frontend/dist/assets/index-Vn7yKSwJ.js.gz diff --git a/frontend/dist/.source-hash b/frontend/dist/.source-hash index 7c0d9a150..9a548bfc0 100644 --- a/frontend/dist/.source-hash +++ b/frontend/dist/.source-hash @@ -1 +1 @@ -12cabe1ff8c8f5ccce033da4484975243900968c66b064aad8e4c0111f86dd11 +446d561ae6e8df254c73533b4d4df243cfb1869596b0d0165640b25085cfa150 diff --git a/frontend/dist/assets/index-BNakVyVm.js.gz b/frontend/dist/assets/index-BNakVyVm.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..2fceea7a874c982e6280a87c8f6047f4bc40dd0f GIT binary patch literal 124312 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
    NHk>^-5hPY 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ℑDcukuZ}F&T9zynaO<6*}5bL`U<5fLS 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 ZkPrc}E#i&#Xny^7{~w*7XJPr`1ppR>M-Bi0 literal 0 HcmV?d00001 diff --git a/frontend/dist/assets/index-Vn7yKSwJ.js.gz b/frontend/dist/assets/index-Vn7yKSwJ.js.gz deleted file mode 100644 index e2e25ad95c8feec6f280f60fa570a34c328689d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124227 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�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 SpvrZ{dgVWQtU<;00RRB=tDFG< literal 323 zcmV-J0lfYniwFP!0000217%T5PsAV)zVELFFvk@g;|PHXqd5& ze=l8jv)Oood41o^eA8T*sA(C1b=3QI)2tu!Dp<)OaHhRNlzeNetz`r=RqMms4P#9Q z%B)5Ks22?Ae3rGir1&4KY!kECzy*yG_neCkjJ~1^O#u_AQUbbn5)llx4gVf&-FL*wh@Km z-|k3@2kZdooba&t8RNt2WYz8bSZlVb4KzMBdxP! Vzra^izOE=&{sRmr?EUru005jMm)igU diff --git a/frontend/src/pages/PoolDetail.tsx b/frontend/src/pages/PoolDetail.tsx index 738204e7e..b3e85243d 100644 --- a/frontend/src/pages/PoolDetail.tsx +++ b/frontend/src/pages/PoolDetail.tsx @@ -701,19 +701,45 @@ function StartupParametersBlock({ } return (
      - {rows.map((p) => ( -
    • - - - {p.parameter} = {p.value} + {rows.map((p) => { + // Anonymous viewers receive `value` undefined - the backend + // redacts operator-supplied values for read-only callers. Render + // it as "***" so the row still shows the parameter and the + // cascade source the operator configured. + const displayValue = p.value ?? "***"; + const state = p.state ?? "applied"; + // `applied` is the steady state - the row matches what backends + // ship. Anything else is operator-visible information that needs + // attention: a stale snapshot (RELOAD has not propagated) or a + // cascade that runs over the StartupMessage budget. + const stateClass = + state === "applied" + ? "text-text-dim" + : state === "dropped_due_to_budget" + ? "text-error" + : "text-warning"; + return ( +
    • + + + {p.parameter} = {displayValue} + + + source: {p.source} + {state !== "applied" ? ( + <> + {" · "} + state: {state} + + ) : null} + - source: {p.source} - -
    • - ))} + + ); + })}
    ); } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 0cd8bfddf..6d1acbd74 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -94,10 +94,20 @@ export interface PoolDto { export interface StartupParameter { parameter: string; - value: string; + // Backend omits `value` for anonymous viewers so the public read-only + // UI does not leak operator-supplied tenant identifiers, audit tags, + // or accidental secrets. Admin and SSO callers receive the full + // value. When undefined, render as "***". + value?: string; // "general" | "pool" | "auth_query" — cascade layer that contributed the // winning value. source: string; + // "applied" | "dropped_due_to_budget" | "stale" — wire-application + // state cross-checked against the pool's frozen snapshot. `applied` + // means the next backend spawn ships this key/value; the other two + // states tell the operator that the pool needs a RELOAD or that the + // operator cascade is over the StartupMessage budget. + state: string; } export interface PoolsDto { From 09c41bd6fe2035d5acf7be08b8b3340566588c7b Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:32:06 +0300 Subject: [PATCH 65/72] chore(reference): regenerate pg_doorman.toml/yaml after fields.yaml edits --- pg_doorman.toml | 6 +++--- pg_doorman.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pg_doorman.toml b/pg_doorman.toml index 34e62f498..7b37f2bf4 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -407,7 +407,7 @@ hba = [] # """ # -------------------------------------------------------------------------- -# PostgreSQL Startup Parameters (operator-set GUCs) +# PostgreSQL Startup GUCs # -------------------------------------------------------------------------- # Baseline PostgreSQL GUCs that pg_doorman adds to each new @@ -417,7 +417,7 @@ hba = [] # size. Before backend startup, pg_doorman checks the resolved # parameter set again; if it does not fit PG's # MAX_STARTUP_PACKET_LENGTH (10000 bytes), pg_doorman skips -# operator-set GUCs for that startup and logs a warning. +# configured GUCs for that startup and logs a warning. # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters = { plan_cache_mode = "force_custom_plan", work_mem = "64MB" } @@ -569,7 +569,7 @@ cleanup_server_connections = true # max_db_connections = 0 # Don't evict connections younger than this (milliseconds). -# Protects freshly created connections from eviction churn +# Protects freshly created connections from repeated evictions # between user pools sharing the same database. # min_connection_lifetime = 30000 diff --git a/pg_doorman.yaml b/pg_doorman.yaml index 7618ac673..edde66cdb 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -447,7 +447,7 @@ general: # host all all 0.0.0.0/0 reject # -------------------------------------------------------------------------- - # PostgreSQL Startup Parameters (operator-set GUCs) + # PostgreSQL Startup GUCs # -------------------------------------------------------------------------- # Baseline PostgreSQL GUCs that pg_doorman adds to each new @@ -457,7 +457,7 @@ general: # size. Before backend startup, pg_doorman checks the resolved # parameter set again; if it does not fit PG's # MAX_STARTUP_PACKET_LENGTH (10000 bytes), pg_doorman skips - # operator-set GUCs for that startup and logs a warning. + # configured GUCs for that startup and logs a warning. # Example: startup_parameters = { plan_cache_mode = "force_custom_plan" } # Default: {} (empty) # startup_parameters: @@ -615,7 +615,7 @@ pools: # max_db_connections: 0 # Don't evict connections younger than this (milliseconds). - # Protects freshly created connections from eviction churn + # Protects freshly created connections from repeated evictions # between user pools sharing the same database. # min_connection_lifetime: 30000 From 802a09ff2812e31f79f870ea466c27f85850fbce Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:44:35 +0300 Subject: [PATCH 66/72] feat(web): add opt-in HTTPS gate for SSO credentials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A TLS-terminating reverse proxy in front of pg_doorman is common, but the proxy → pg_doorman hop can ride a private HTTP leg. On shared or multi-tenant networks an attacker on that segment can replay Bearer JWTs or `sso_access_token` cookies pulled off the wire. Operators need a way to make pg_doorman refuse those tokens unless the request actually arrived over HTTPS. New `[web].sso_require_https` knob, default `false`. When on, the listener accepts Bearer/cookie/query SSO tokens only when the TCP peer is in `trusted_proxies` AND the proxy forwarded `X-Forwarded-Proto: https` (multi-hop chains must be `https` end-to-end — any `http` segment downgrades). Plain-HTTP SSO attempts yield 401 instead of silent Anonymous and bump `pg_doorman_web_sso_validation_errors_total{reason="insecure_transport"}`, so a misrouted operator notices instead of guessing. Basic auth is unaffected — it lives or dies on the constant-time compare, regardless of transport. Default off keeps existing deployments working unchanged: the SSO proxy still reaches pg_doorman over a private HTTP leg, no configuration migration required. Documented in EN/RU web-ui guides and surfaced in the annotated TOML/YAML references. --- documentation/en/src/guides/web-ui.md | 3 +- documentation/ru/src/guides/web-ui.md | 3 +- pg_doorman.toml | 4 + pg_doorman.yaml | 4 + src/app/generate/annotated.rs | 4 + src/app/generate/fields.yaml | 7 + src/config/web.rs | 13 ++ src/web/auth.rs | 241 ++++++++++++++++++++++---- src/web/metrics/mod.rs | 2 +- src/web/metrics/tests.rs | 2 + src/web/peer.rs | 123 +++++++++++++ src/web/server/http.rs | 15 +- src/web/server/state.rs | 8 + src/web/server/tests.rs | 3 + src/web/server/wire.rs | 9 + src/web/tests.rs | 2 + 16 files changed, 409 insertions(+), 34 deletions(-) diff --git a/documentation/en/src/guides/web-ui.md b/documentation/en/src/guides/web-ui.md index e86c26ee8..47851675c 100644 --- a/documentation/en/src/guides/web-ui.md +++ b/documentation/en/src/guides/web-ui.md @@ -129,7 +129,8 @@ proxy: | `sso_allowed_users` | Allowlist on the `preferred_username` (or `sub`) claim. `["*"]` accepts every valid JWT; a literal list restricts access to those usernames. | `["*"]` | | `sso_groups_claim` | Name of the JWT claim that carries the user's group memberships. Read together with `sso_admin_groups`. | `"groups"` | | `sso_admin_groups` | Group names that promote an SSO user to `Admin`. Empty keeps every SSO login at the read-only `Sso` role. | `[]` | -| `trusted_proxies` | CIDR ranges trusted to set `X-Forwarded-For` / `Forwarded`. Empty trusts only the listener's own peer. See [Access log](#access-log). | `[]` | +| `sso_require_https` | Reject Bearer/cookie/query SSO credentials presented over plain HTTP. The listener treats a request as secure only when the TCP peer is in `trusted_proxies` and `X-Forwarded-Proto: https` is forwarded. Defaults to off so SSO keeps working through a TLS-terminating proxy that reaches pg_doorman over a private HTTP leg. | `false` | +| `trusted_proxies` | CIDR ranges trusted to set `X-Forwarded-For` / `Forwarded` / `X-Forwarded-Proto`. Empty trusts only the listener's own peer. See [Access log](#access-log). | `[]` | ### Promoting SSO users to Admin via group claim diff --git a/documentation/ru/src/guides/web-ui.md b/documentation/ru/src/guides/web-ui.md index 06d56b840..580fa4992 100644 --- a/documentation/ru/src/guides/web-ui.md +++ b/documentation/ru/src/guides/web-ui.md @@ -129,7 +129,8 @@ SSO опциональный. По умолчанию (`[web].sso_enabled = fals | `sso_allowed_users` | Allowlist по claim `preferred_username` (или `sub`). `["*"]` принимает любого. Иначе пропускаются только перечисленные имена. | `["*"]` | | `sso_groups_claim` | Имя JWT-claim, в котором лежат группы пользователя. Читается вместе с `sso_admin_groups`. | `"groups"` | | `sso_admin_groups` | Группы, которые поднимают SSO-пользователя до `Admin`. Пустой список оставляет каждый SSO-логин на роли `Sso` только для чтения. | `[]` | -| `trusted_proxies` | CIDR доверенных обратных прокси. Пустой список — доверять только непосредственному TCP-peer. См. [Журнал доступа](#журнал-доступа). | `[]` | +| `sso_require_https` | Отклонять Bearer/cookie/query SSO-credentials, пришедшие по plain HTTP. Запрос считается защищённым только если TCP-peer входит в `trusted_proxies` и прокси прислал `X-Forwarded-Proto: https`. По умолчанию выключено, чтобы SSO продолжал работать в схеме «TLS терминирует прокси → pg_doorman слушает HTTP во внутренней сети». | `false` | +| `trusted_proxies` | CIDR доверенных обратных прокси (используется для `X-Forwarded-For` / `Forwarded` / `X-Forwarded-Proto`). Пустой список — доверять только непосредственному TCP-peer. См. [Журнал доступа](#журнал-доступа). | `[]` | ### Поднятие SSO-пользователя до Admin через claim с группами diff --git a/pg_doorman.toml b/pg_doorman.toml index 7b37f2bf4..574813c5f 100644 --- a/pg_doorman.toml +++ b/pg_doorman.toml @@ -482,6 +482,10 @@ sso_groups_claim = "groups" # Default: [] # sso_admin_groups = ["pg-doorman-admins"] +# Reject SSO credentials presented over plain HTTP. +# Default: false +sso_require_https = false + # ############################################################################ # TALOS AUTHENTICATION (Optional) # ############################################################################ diff --git a/pg_doorman.yaml b/pg_doorman.yaml index edde66cdb..f8f3082e1 100644 --- a/pg_doorman.yaml +++ b/pg_doorman.yaml @@ -524,6 +524,10 @@ web: # Default: [] # sso_admin_groups: ["pg-doorman-admins"] + # Reject SSO credentials presented over plain HTTP. + # Default: false + sso_require_https: false + # ############################################################################ # TALOS AUTHENTICATION (Optional) # ############################################################################ diff --git a/src/app/generate/annotated.rs b/src/app/generate/annotated.rs index a01176b7e..04a3447a1 100644 --- a/src/app/generate/annotated.rs +++ b/src/app/generate/annotated.rs @@ -1239,6 +1239,10 @@ fn write_web_section(w: &mut ConfigWriter, web: &Web) { w.kv(fi, "sso_admin_groups", &format!("[{rendered}]")); } w.blank(); + + write_field_comment(w, fi, "web", "sso_require_https"); + w.kv(fi, "sso_require_https", &web.sso_require_https.to_string()); + w.blank(); } fn write_talos_section(w: &mut ConfigWriter) { diff --git a/src/app/generate/fields.yaml b/src/app/generate/fields.yaml index 9631ffd79..01ad67927 100644 --- a/src/app/generate/fields.yaml +++ b/src/app/generate/fields.yaml @@ -1841,3 +1841,10 @@ fields: ru: "Список групп, которые получают роль Admin (могут управлять пулами)." doc: "An SSO user whose JWT carries any of these names in sso_groups_claim gets full admin access (POST /api/admin/* allowed). Empty (default) keeps SSO users read-only." default: "[]" + + sso_require_https: + config: + en: "Reject SSO credentials presented over plain HTTP." + ru: "Отклонять SSO credentials, пришедшие по plain HTTP." + doc: "When true, Bearer/cookie/query SSO tokens are accepted only if the request peer is in trusted_proxies and the proxy forwarded X-Forwarded-Proto: https. Defaults to false so SSO works through a TLS-terminating proxy reaching pg_doorman over a private HTTP leg. Enable on shared networks where the proxy → pg_doorman hop is exposed." + default: "false" diff --git a/src/config/web.rs b/src/config/web.rs index e10b5b3ef..a6d5c56db 100644 --- a/src/config/web.rs +++ b/src/config/web.rs @@ -74,6 +74,18 @@ pub struct Web { /// SSO user resolves to `Sso`. #[serde(default)] pub sso_admin_groups: Vec, + + /// Reject Bearer/cookie/query SSO credentials when the request did + /// not arrive over HTTPS. The listener treats a request as secure + /// only when its TCP peer is in `trusted_proxies` and the proxy + /// forwarded `X-Forwarded-Proto: https`. Defaults to `false` so + /// existing deployments where the SSO proxy terminates TLS on a + /// different host (and reaches pg_doorman over a private network) + /// keep working without configuration changes. Enable on multi- + /// tenant networks where an attacker could observe the HTTP leg + /// between the proxy and pg_doorman. + #[serde(default)] + pub sso_require_https: bool, } impl Web { @@ -93,6 +105,7 @@ impl Web { trusted_proxies: Vec::new(), sso_groups_claim: Self::default_sso_groups_claim(), sso_admin_groups: Vec::new(), + sso_require_https: false, } } diff --git a/src/web/auth.rs b/src/web/auth.rs index efcc7048a..d8c1b6347 100644 --- a/src/web/auth.rs +++ b/src/web/auth.rs @@ -59,6 +59,32 @@ impl AuthOutcome { } } +/// Whether the listener should accept SSO credentials presented over a +/// plain-HTTP request. `request_is_secure` is the listener's verdict +/// after combining the TCP peer with `X-Forwarded-Proto` (see +/// [`crate::web::peer::request_is_secure`]); `require_https` is the +/// operator's `[web].sso_require_https` knob. +/// +/// Default policy (`require_https=false`) keeps backward compatibility: +/// every deployment where a TLS-terminating proxy reaches pg_doorman +/// over a private HTTP leg keeps working without configuration changes. +/// Opt-in `require_https=true` rejects SSO credentials on plain HTTP so +/// the JWT cannot leak between the proxy and pg_doorman. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SsoTransportPolicy { + pub request_is_secure: bool, + pub require_https: bool, +} + +impl SsoTransportPolicy { + /// Permits SSO credentials when the operator has not opted in to + /// HTTPS-only SSO, or when the request actually arrived over a + /// trusted HTTPS hop. + fn permits_sso(self) -> bool { + !self.require_https || self.request_is_secure + } +} + /// Classify an inbound HTTP request into an `AuthOutcome`. Recognises: /// /// - `Authorization: Basic ` against the admin @@ -76,6 +102,15 @@ impl AuthOutcome { /// credentials to deny timing oracles. Both username and password legs /// are checked together without short-circuit (see the `&` operator /// inside the implementation). +/// +/// `sso_transport` decides whether the SSO branches run at all. When +/// the operator set `[web].sso_require_https = true` and the request +/// did not arrive over a trusted HTTPS hop, every SSO source is +/// skipped and the function falls through to either `Rejected` (an +/// SSO credential was attempted) or `Anonymous` (no credentials at +/// all). Basic credentials are unaffected — they are scheme-bound by +/// `Authorization: Basic` and live or die on the constant-time +/// compare above, regardless of transport. pub fn classify( authorization_header: Option<&str>, cookie_header: Option<&str>, @@ -83,6 +118,7 @@ pub fn classify( admin_username: &str, admin_password: &str, sso: Option<&crate::web::sso::SsoRuntime>, + sso_transport: SsoTransportPolicy, ) -> AuthOutcome { let mut tried = false; @@ -128,22 +164,42 @@ pub fn classify( // credential attempt when SSO is actually configured to consume // them; otherwise an Anonymous request to a public endpoint that // happens to carry such a cookie should still pass. + // + // Transport gate: when `sso_require_https` is on and the request + // did not arrive over a trusted HTTPS hop, skip every SSO branch + // and record one telemetry sample per blocked attempt. A request + // that carried only `Authorization: Bearer …` still sets + // `tried = true` above, so the caller falls through to a 401 — + // important so an operator chasing a misconfigured proxy gets a + // real failure instead of silent Anonymous behaviour. if let Some(rt) = sso { - if let Some(token) = bearer_token { - if let Ok(id) = rt.validate(token) { - return sso_outcome(id); + if sso_transport.permits_sso() { + if let Some(token) = bearer_token { + if let Ok(id) = rt.validate(token) { + return sso_outcome(id); + } } - } - if let Some(token) = query_token { - tried = true; - if let Ok(id) = rt.validate(token) { - return sso_outcome(id); + if let Some(token) = query_token { + tried = true; + if let Ok(id) = rt.validate(token) { + return sso_outcome(id); + } } - } - if let Some(token) = cookie_header.and_then(find_sso_cookie) { - tried = true; - if let Ok(id) = rt.validate(token) { - return sso_outcome(id); + if let Some(token) = cookie_header.and_then(find_sso_cookie) { + tried = true; + if let Ok(id) = rt.validate(token) { + return sso_outcome(id); + } + } + } else { + let presented = bearer_token.is_some() + || query_token.is_some() + || cookie_header.and_then(find_sso_cookie).is_some(); + if presented { + tried = true; + crate::web::metrics::WEB_SSO_VALIDATION_ERRORS + .with_label_values(&["insecure_transport"]) + .inc(); } } } @@ -227,10 +283,43 @@ mod tests { .unwrap() } + fn permissive_transport() -> SsoTransportPolicy { + SsoTransportPolicy { + request_is_secure: false, + require_https: false, + } + } + + fn https_only_transport(is_secure: bool) -> SsoTransportPolicy { + SsoTransportPolicy { + request_is_secure: is_secure, + require_https: true, + } + } + + fn classify_default( + auth: Option<&str>, + cookie: Option<&str>, + query: Option<&str>, + admin_user: &str, + admin_pass: &str, + sso: Option<&SsoRuntime>, + ) -> AuthOutcome { + classify( + auth, + cookie, + query, + admin_user, + admin_pass, + sso, + permissive_transport(), + ) + } + #[test] fn anonymous_when_header_missing() { assert_eq!( - classify(None, None, None, "admin", "secret", None), + classify_default(None, None, None, "admin", "secret", None), AuthOutcome::Anonymous ); } @@ -239,7 +328,7 @@ mod tests { fn admin_when_credentials_match() { let header = format!("Basic {}", b64("admin:secret")); assert_eq!( - classify(Some(&header), None, None, "admin", "secret", None), + classify_default(Some(&header), None, None, "admin", "secret", None), admin("admin") ); } @@ -248,7 +337,7 @@ mod tests { fn rejected_when_password_wrong() { let header = format!("Basic {}", b64("admin:wrong")); assert_eq!( - classify(Some(&header), None, None, "admin", "secret", None), + classify_default(Some(&header), None, None, "admin", "secret", None), AuthOutcome::Rejected ); } @@ -257,7 +346,7 @@ mod tests { fn rejected_when_username_wrong() { let header = format!("Basic {}", b64("evil:secret")); assert_eq!( - classify(Some(&header), None, None, "admin", "secret", None), + classify_default(Some(&header), None, None, "admin", "secret", None), AuthOutcome::Rejected ); } @@ -266,7 +355,7 @@ mod tests { fn rejected_when_scheme_not_basic_and_no_sso() { let header = format!("Bearer {}", b64("admin:secret")); assert_eq!( - classify(Some(&header), None, None, "admin", "secret", None), + classify_default(Some(&header), None, None, "admin", "secret", None), AuthOutcome::Rejected ); } @@ -274,7 +363,7 @@ mod tests { #[test] fn rejected_when_base64_invalid() { assert_eq!( - classify( + classify_default( Some("Basic !!!not-base64!!!"), None, None, @@ -290,7 +379,7 @@ mod tests { fn rejected_when_decoded_has_no_colon() { let header = format!("Basic {}", b64("adminsecret")); assert_eq!( - classify(Some(&header), None, None, "admin", "secret", None), + classify_default(Some(&header), None, None, "admin", "secret", None), AuthOutcome::Rejected ); } @@ -300,7 +389,7 @@ mod tests { let raw = base64::engine::general_purpose::STANDARD.encode([0xff, 0xfe, 0xfd]); let header = format!("Basic {}", raw); assert_eq!( - classify(Some(&header), None, None, "admin", "secret", None), + classify_default(Some(&header), None, None, "admin", "secret", None), AuthOutcome::Rejected ); } @@ -310,7 +399,7 @@ mod tests { // Per RFC 7617 only the FIRST colon is the separator. let header = format!("Basic {}", b64("admin:p:a:s:s")); assert_eq!( - classify(Some(&header), None, None, "admin", "p:a:s:s", None), + classify_default(Some(&header), None, None, "admin", "p:a:s:s", None), admin("admin") ); } @@ -333,7 +422,7 @@ mod tests { let token = mint(600, "alice"); let header = format!("Bearer {}", token); let rt = sso_rt(AllowedUsers::Any); - let out = classify(Some(&header), None, None, "admin", "secret", Some(&rt)); + let out = classify_default(Some(&header), None, None, "admin", "secret", Some(&rt)); match out { AuthOutcome::Sso(id) => { assert_eq!(id.username, "alice"); @@ -347,7 +436,7 @@ mod tests { fn sso_query_token_works() { let token = mint(600, "alice"); let rt = sso_rt(AllowedUsers::Any); - let out = classify(None, None, Some(&token), "admin", "secret", Some(&rt)); + let out = classify_default(None, None, Some(&token), "admin", "secret", Some(&rt)); assert!(matches!(out, AuthOutcome::Sso(_))); } @@ -356,7 +445,7 @@ mod tests { let token = mint(600, "alice"); let cookie = format!("foo=bar; sso_access_token={}; baz=qux", token); let rt = sso_rt(AllowedUsers::Any); - let out = classify(None, Some(&cookie), None, "admin", "secret", Some(&rt)); + let out = classify_default(None, Some(&cookie), None, "admin", "secret", Some(&rt)); assert!(matches!(out, AuthOutcome::Sso(_))); } @@ -368,7 +457,7 @@ mod tests { let basic = format!("Basic {}", b64("admin:secret")); let cookie = format!("sso_access_token={}", token); let rt = sso_rt(AllowedUsers::Any); - let out = classify( + let out = classify_default( Some(&basic), Some(&cookie), None, @@ -390,7 +479,7 @@ mod tests { let basic = format!("Basic {}", b64("admin:wrong")); let cookie = format!("sso_access_token={}", token); let rt = sso_rt(AllowedUsers::Any); - let out = classify( + let out = classify_default( Some(&basic), Some(&cookie), None, @@ -408,7 +497,7 @@ mod tests { let token = mint(-600, "alice"); let header = format!("Bearer {}", token); let rt = sso_rt(AllowedUsers::Any); - let out = classify(Some(&header), None, None, "admin", "secret", Some(&rt)); + let out = classify_default(Some(&header), None, None, "admin", "secret", Some(&rt)); assert_eq!(out, AuthOutcome::Rejected); } @@ -419,7 +508,7 @@ mod tests { let rt = sso_rt(AllowedUsers::List( ["alice".to_string()].into_iter().collect(), )); - let out = classify(Some(&header), None, None, "admin", "secret", Some(&rt)); + let out = classify_default(Some(&header), None, None, "admin", "secret", Some(&rt)); assert_eq!(out, AuthOutcome::Rejected); } @@ -430,7 +519,99 @@ mod tests { // so a credential was attempted but nothing took it: Rejected. let token = mint(600, "alice"); let header = format!("Bearer {}", token); - let out = classify(Some(&header), None, None, "admin", "secret", None); + let out = classify_default(Some(&header), None, None, "admin", "secret", None); assert_eq!(out, AuthOutcome::Rejected); } + + #[test] + fn require_https_rejects_bearer_on_plain_http() { + // sso_require_https = true and the transport is not secure → the + // Bearer JWT is treated as a credential attempt that failed, so + // the caller gets a 401 instead of an Anonymous fall-through. + let token = mint(600, "alice"); + let header = format!("Bearer {}", token); + let rt = sso_rt(AllowedUsers::Any); + let out = classify( + Some(&header), + None, + None, + "admin", + "secret", + Some(&rt), + https_only_transport(false), + ); + assert_eq!(out, AuthOutcome::Rejected); + } + + #[test] + fn require_https_accepts_bearer_on_secure_hop() { + let token = mint(600, "alice"); + let header = format!("Bearer {}", token); + let rt = sso_rt(AllowedUsers::Any); + let out = classify( + Some(&header), + None, + None, + "admin", + "secret", + Some(&rt), + https_only_transport(true), + ); + assert!(matches!(out, AuthOutcome::Sso(_))); + } + + #[test] + fn require_https_does_not_block_basic() { + // Basic credentials live or die on the constant-time compare; + // sso_require_https only gates the SSO branches. + let header = format!("Basic {}", b64("admin:secret")); + let out = classify( + Some(&header), + None, + None, + "admin", + "secret", + None, + https_only_transport(false), + ); + assert_eq!(out, admin("admin")); + } + + #[test] + fn require_https_leaves_anonymous_alone_when_no_sso_attempt() { + // sso_require_https is on but the request carries no SSO source + // at all — it stays Anonymous so the caller can still hit a + // public read-only endpoint over plain HTTP. + let rt = sso_rt(AllowedUsers::Any); + let out = classify( + None, + None, + None, + "admin", + "secret", + Some(&rt), + https_only_transport(false), + ); + assert_eq!(out, AuthOutcome::Anonymous); + } + + #[test] + fn require_https_off_keeps_plain_http_sso_working() { + // The default policy (require_https = false) preserves + // backward-compatible behaviour for the SSO-proxy-fronts-pg_doorman + // deployment where the proxy → pg_doorman hop is private HTTP. + let token = mint(600, "alice"); + let header = format!("Bearer {}", token); + let rt = sso_rt(AllowedUsers::Any); + let out = classify( + Some(&header), + None, + None, + "admin", + "secret", + Some(&rt), + permissive_transport(), + ); + assert!(matches!(out, AuthOutcome::Sso(_))); + } } diff --git a/src/web/metrics/mod.rs b/src/web/metrics/mod.rs index bb2525a83..e6e6ce894 100644 --- a/src/web/metrics/mod.rs +++ b/src/web/metrics/mod.rs @@ -1356,7 +1356,7 @@ pub(crate) static WEB_SSO_VALIDATION_ERRORS: Lazy = Lazy::new(|| let counter = IntCounterVec::new( Opts::new( "pg_doorman_web_sso_validation_errors_total", - "JWT validation failures by reason: signature, expired, audience, no_username, allowlist. A sustained signature spike means the SSO proxy rotated keys without updating sso_public_key_file; allowlist spikes mean someone outside the allowlist is trying to log in.", + "JWT validation failures by reason: signature, expired, audience, no_username, allowlist, insecure_transport. A sustained signature spike means the SSO proxy rotated keys without updating sso_public_key_file; allowlist spikes mean someone outside the allowlist is trying to log in; insecure_transport means [web].sso_require_https is on and a JWT arrived without the trusted-proxy + X-Forwarded-Proto: https hop required to accept it.", ), &["reason"], ) diff --git a/src/web/metrics/tests.rs b/src/web/metrics/tests.rs index 3b47b0f4a..f968c1ede 100644 --- a/src/web/metrics/tests.rs +++ b/src/web/metrics/tests.rs @@ -41,6 +41,7 @@ async fn test_prometheus_server_basic() { sso_config_error: None, trusted_proxies: Vec::new(), sso_admin_groups_configured: false, + sso_require_https: false, }, ) .await; @@ -245,6 +246,7 @@ async fn test_prometheus_server_integration() { sso_config_error: None, trusted_proxies: Vec::new(), sso_admin_groups_configured: false, + sso_require_https: false, }, ) .await; diff --git a/src/web/peer.rs b/src/web/peer.rs index fbd35d1a5..4a92c7216 100644 --- a/src/web/peer.rs +++ b/src/web/peer.rs @@ -48,6 +48,48 @@ fn is_trusted(addr: IpAddr, trusted: &[IpNet]) -> bool { trusted.iter().any(|net| net.contains(&addr)) } +/// Decide whether the inbound request reached pg_doorman over a secure +/// transport. The listener itself terminates plain HTTP, so the only +/// honest signal is the proxy's `X-Forwarded-Proto` — and we trust it +/// only when the TCP peer is in `trusted_proxies`. An attacker who +/// connects directly without going through the proxy controls every +/// header they want to send; the trusted-peer gate is what keeps them +/// from forging `https` to bypass `sso_require_https`. +/// +/// Multi-hop chains (`X-Forwarded-Proto: https, http`) are read +/// left-to-right and accepted only when every hop is `https` — +/// any `http` segment downgrades the result. This matches the access +/// log's right-to-left walk in spirit: the inner-most hop closest to +/// pg_doorman is the one we cannot verify, so a chain that includes +/// a plain leg is treated as plain. +pub fn request_is_secure( + peer_addr: Option, + x_forwarded_proto: Option<&str>, + trusted_proxies: &[IpNet], +) -> bool { + let Some(peer) = peer_addr else { + return false; + }; + if !is_trusted(peer.ip(), trusted_proxies) { + return false; + } + let Some(value) = x_forwarded_proto else { + return false; + }; + let mut seen_any = false; + for hop in value.split(',') { + let hop = hop.trim(); + if hop.is_empty() { + continue; + } + seen_any = true; + if !hop.eq_ignore_ascii_case("https") { + return false; + } + } + seen_any +} + /// Walks `X-Forwarded-For` right-to-left, skipping trusted IPs. /// Returns the first untrusted address found, or `None`. fn walk_xff(xff: Option<&str>, trusted: &[IpNet]) -> Option { @@ -197,4 +239,85 @@ mod tests { ); assert_eq!(out, "198.51.100.42"); } + + #[test] + fn request_is_secure_requires_trusted_peer_and_https() { + assert!(request_is_secure( + Some(sock("10.0.0.1:443")), + Some("https"), + &[net("10.0.0.0/8")], + )); + } + + #[test] + fn request_is_secure_rejects_untrusted_peer() { + assert!(!request_is_secure( + Some(sock("203.0.113.7:443")), + Some("https"), + &[net("10.0.0.0/8")], + )); + } + + #[test] + fn request_is_secure_rejects_missing_header() { + assert!(!request_is_secure( + Some(sock("10.0.0.1:443")), + None, + &[net("10.0.0.0/8")], + )); + } + + #[test] + fn request_is_secure_rejects_http_value() { + assert!(!request_is_secure( + Some(sock("10.0.0.1:443")), + Some("http"), + &[net("10.0.0.0/8")], + )); + } + + #[test] + fn request_is_secure_case_insensitive() { + assert!(request_is_secure( + Some(sock("10.0.0.1:443")), + Some("HTTPS"), + &[net("10.0.0.0/8")], + )); + } + + #[test] + fn request_is_secure_downgrades_on_mixed_chain() { + assert!(!request_is_secure( + Some(sock("10.0.0.1:443")), + Some("https, http"), + &[net("10.0.0.0/8")], + )); + } + + #[test] + fn request_is_secure_accepts_chain_of_https() { + assert!(request_is_secure( + Some(sock("10.0.0.1:443")), + Some("https, https"), + &[net("10.0.0.0/8")], + )); + } + + #[test] + fn request_is_secure_rejects_empty_header() { + assert!(!request_is_secure( + Some(sock("10.0.0.1:443")), + Some(" , "), + &[net("10.0.0.0/8")], + )); + } + + #[test] + fn request_is_secure_rejects_when_no_peer() { + assert!(!request_is_secure( + None, + Some("https"), + &[net("10.0.0.0/8")] + )); + } } diff --git a/src/web/server/http.rs b/src/web/server/http.rs index f9cdd3de6..1a21bbbc1 100644 --- a/src/web/server/http.rs +++ b/src/web/server/http.rs @@ -11,7 +11,7 @@ use tokio::io::{AsyncReadExt, BufReader, BufWriter}; use tokio::net::tcp::OwnedReadHalf; use tokio::net::TcpStream; -use crate::web::auth::{classify, AuthOutcome, Role}; +use crate::web::auth::{classify, AuthOutcome, Role, SsoTransportPolicy}; use crate::web::metrics::write_metrics_response; use super::router::{dispatch, unauthorized_for}; @@ -102,6 +102,15 @@ pub(super) async fn handle_connection(stream: TcpStream, opts: Arc WebServerOptions { sso_config_error: None, trusted_proxies: Vec::new(), sso_admin_groups_configured: false, + sso_require_https: false, } } @@ -41,6 +42,7 @@ fn req<'a>(method: &'a str, raw_path: &'a str) -> ParsedRequest<'a> { authorization: None, cookie: None, x_forwarded_for: None, + x_forwarded_proto: None, forwarded: None, accepts_gzip: false, accepts_json: false, @@ -57,6 +59,7 @@ fn req_json<'a>(method: &'a str, raw_path: &'a str) -> ParsedRequest<'a> { authorization: None, cookie: None, x_forwarded_for: None, + x_forwarded_proto: None, forwarded: None, accepts_gzip: false, accepts_json: true, diff --git a/src/web/server/wire.rs b/src/web/server/wire.rs index b73b1c210..c3057abcc 100644 --- a/src/web/server/wire.rs +++ b/src/web/server/wire.rs @@ -49,6 +49,11 @@ pub(super) struct ParsedRequest<'a> { /// Raw value of the `Forwarded:` header (RFC 7239). Same role as /// `x_forwarded_for`; both are walked. pub(super) forwarded: Option<&'a str>, + /// Raw value of the `X-Forwarded-Proto:` header, if present. Only + /// trusted when the TCP peer is in `[web].trusted_proxies`; used to + /// gate SSO credentials behind HTTPS when + /// `[web].sso_require_https = true`. + pub(super) x_forwarded_proto: Option<&'a str>, pub(super) accepts_gzip: bool, /// True when the request advertises `Accept: application/json`. The SPA /// `fetch()` wrapper sets this on every call; a browser hitting the URL @@ -80,6 +85,7 @@ impl<'a> ParsedRequest<'a> { let mut authorization = None; let mut cookie = None; let mut x_forwarded_for = None; + let mut x_forwarded_proto = None; let mut forwarded = None; let mut accepts_gzip = false; let mut accepts_json = false; @@ -98,6 +104,8 @@ impl<'a> ParsedRequest<'a> { cookie = Some(value); } else if let Some(value) = strip_header_prefix(line, "X-Forwarded-For") { x_forwarded_for = Some(value); + } else if let Some(value) = strip_header_prefix(line, "X-Forwarded-Proto") { + x_forwarded_proto = Some(value); } else if let Some(value) = strip_header_prefix(line, "Forwarded") { forwarded = Some(value); } else if let Some(value) = strip_header_prefix(line, "Accept-Encoding") { @@ -121,6 +129,7 @@ impl<'a> ParsedRequest<'a> { authorization, cookie, x_forwarded_for, + x_forwarded_proto, forwarded, accepts_gzip, accepts_json, diff --git a/src/web/tests.rs b/src/web/tests.rs index f8a28e021..20694b381 100644 --- a/src/web/tests.rs +++ b/src/web/tests.rs @@ -33,6 +33,7 @@ fn opts(ui_active: bool, ui_anonymous: bool) -> WebServerOptions { sso_config_error: None, trusted_proxies: Vec::new(), sso_admin_groups_configured: false, + sso_require_https: false, } } @@ -54,6 +55,7 @@ fn opts_with_sso(ui_anonymous: bool) -> WebServerOptions { sso_config_error: None, trusted_proxies: Vec::new(), sso_admin_groups_configured: false, + sso_require_https: false, } } From 833247380399c88e5d09affec13a86b5ae85446b Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 14:49:57 +0300 Subject: [PATCH 67/72] feat(web): redesign sign-in surface as a Bloomberg-style console The previous sign-in modal was a 320px box tacked over the SPA: no visual anchor, the SSO button shared the chrome of the Basic form, and a returning operator could not tell at a glance which transport their browser was talking to. The new sign-in surface is a full-page console that reuses the JetBrains Mono / amber-tick / hairline-border idiom from the rest of the dashboard. SSO becomes the primary action with the proxy host shown inline so the operator knows where the redirect will route them; Basic is its own section below an `or use local admin` rule, keeping it discoverable without competing for attention. A transport chip in the footer reads `window.location.protocol` and colours itself green on `https:` (success) versus amber on `http:` (warning), so an operator about to send a Bearer JWT over plain HTTP sees the risk before they click. Accessibility tightened along the way: every input now has an explicit label-for/id pair on top of the existing focus trap, `role="dialog"` shell, and amber focus-visible ring inherited from the global stylesheet. --- frontend/dist/.source-hash | 2 +- frontend/dist/assets/index-ANwga4xD.css.gz | Bin 6542 -> 0 bytes frontend/dist/assets/index-BNakVyVm.js.gz | Bin 124312 -> 0 bytes frontend/dist/assets/index-BrGvyLIG.js.gz | Bin 0 -> 125078 bytes frontend/dist/assets/index-DYwzK8Nu.css.gz | Bin 0 -> 6686 bytes frontend/dist/index.html.gz | Bin 320 -> 324 bytes frontend/src/components/AuthGate.tsx | 294 ++++++++++++++++----- 7 files changed, 231 insertions(+), 65 deletions(-) delete mode 100644 frontend/dist/assets/index-ANwga4xD.css.gz delete mode 100644 frontend/dist/assets/index-BNakVyVm.js.gz create mode 100644 frontend/dist/assets/index-BrGvyLIG.js.gz create mode 100644 frontend/dist/assets/index-DYwzK8Nu.css.gz diff --git a/frontend/dist/.source-hash b/frontend/dist/.source-hash index 9a548bfc0..98d1a181d 100644 --- a/frontend/dist/.source-hash +++ b/frontend/dist/.source-hash @@ -1 +1 @@ -446d561ae6e8df254c73533b4d4df243cfb1869596b0d0165640b25085cfa150 +9959de7ac28c52ce3f3366402820ad229cbe716c3717e8a794a579f7aa39dc31 diff --git a/frontend/dist/assets/index-ANwga4xD.css.gz b/frontend/dist/assets/index-ANwga4xD.css.gz deleted file mode 100644 index 4af9830e52cccc26e3efa4dc2895b092d012ad71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6542 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 Ax&QzG diff --git a/frontend/dist/assets/index-BNakVyVm.js.gz b/frontend/dist/assets/index-BNakVyVm.js.gz deleted file mode 100644 index 2fceea7a874c982e6280a87c8f6047f4bc40dd0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124312 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
    NHk>^-5hPY z9s83EF3io5e~QOmG4QE+$tYgCj@zRmh5iA$N|;jv;sUQIYf*s;xQur_W&>KRy*awS zuP;7(eY8v%JJb}au@XY~g|x<532WaYo)A!0l0M|W;y5YQ7*$3!VTp=i=%p@|EDZ^1 zE?oe@W{eoJSY#@72ib(&tQ<6OPHX7LRx7`#cKazhQID6N90iSM-e{m7FT~`I3*e?8 z2YbbG>%C!?>zOBo?e@T=wG>LNRS2t9; z4B7;Fz&PA_vjQ^2!KHS9Je^}`A?@l&U$IAgai0_KVsp9TA~20jf*^ba?<_!kOXQ1F z-7Vu{sBQw@1mTgtvKQ&7-#P5X{Z7=2-IQO$)?g~|Q|D$+(<#m-e(2mhAP4uh8(9i` zY@>f4+q^|TI&|~Ep_@Mv-Taa0=J%G*(Pciz`#k9JzRAVpXc*sy4HB!x;Q{3L5)bUnly|k9_BC$m%6e}d{v5*tKN{XC|r|N zmH}@+50G4fJTbx53B7CHsV&CUpll7ktE7V9r5Wu?Y}^)&>RYPC87t1qa``YTRZ zUtOWV>N*G3*H=&ezuK{XE>vp^uc+yFFQ}QCYV$cav$W35JguGlWA+KSBESf)yzR7C zvHEJ)irak^f}J%}Ye?{vgZ0LF6>up>_T?+uPsmU?RUhF@-Pw5SwDH0uOc_)phO$ev zF`nk;LHE_a$#YS4HPnt$cFkC9QEo`7n$mRDO0^PQN~w>!wdI)vN;vI!kZ12yzN}G+ zqZpdKK=|X~3TmhX)r??fr4~L5!k8^STX|~%xB&+U@GL-Y#@K>slNLMF58hDoqN$CX z5IZ4(83MAWb{d>{3S>&uYzb1bq#MsQhM_f_(Im(3!6a zW}D=Vn~uWIOhSGGo7-wO==Wet3x(FIX!%+CmK2BR91*yuHK5}Z;<6*k5?^$-g7Emb ztX2Y4`w$F7=$xwXLR%tE!sBwg51lWXIwbQq$I?v->zXp#(@0D{So=BZT_KLX7h(>a zoJ{SVy0>Gn)tlSLx$bBl*14m{^^;;>%WZ#_&fVhNVgu_fj1$@zJQeLxCLN-sBNN`Y{lP`+&-+qny$oQP z{#r-e9xgxZxK;*ndx^TVPL1)`4hWJGP0ujxXL`HQFA%v-k2}=SA3M-Y#I^n*kq;fe ziwzHU25KTCn$U~O?UeP3h*H1k!JHX_o-`c*Em20W_%P`>UqB8}7`$JwFk-@8JD*4w zb11We51zpe1Zc>OpSX1igD_C-d3?D?aer?|Fa62Vwbhx!a$I6cdIMgW%Tdlz8QX~C z^W=|Ot4*Gta$6%wW#B1^GAjRlWlyIbMr?4HONV7BZPHH$fW-S6*U=x;`FwhvsBDjF zKt)9I{Yg#Femk+sBq4fo{Lh8PN#z*W%Sqr`%$zybKOJS%wLci>y2cD-l6>JJX3_Ko@NHdxHfiVqd7QA?-=bA<%uYY99{L zK10oEaSNbZ5X>Y^tac@&wCf1ag7yO4=k_d4=&&r%895&^O@xB|g%r0C$ZHkgju5k{ zv%nR~x5m!M9Zsp-c#CUAkSUk*|i z@F63ujh5_-O&Tf{dSj#H1xXI=`^p@S|;C5TE{t7dkD} z&&D54MB44*y3{c=2<%fgNX92Rd`Y&@{T_aFjMF0u{_1tGsY!!y0esyHA`|JGh**UP zsIVO&)9n6a+9*4MQPLw4cHyko>6yTgP#6m3*}{Nk^|Exe(4tEVoY-C)0kBxlsgRS5LUDvJ2obQ4NAlfbjI$$6CoJW5(r^e|ty1 zZEaF#UOq`Q`t5ZH$Qd*C2wSxNJ^tk4ssH{)->&iuY(P;-ij_@7SydpbdBHKin@6TH z(dlg1rp&5LOVNpp*!C#Xm9{29+f2BS zxek50le!$H%l=Td_bMr^)081%e1T$b zt^P|Ij4;oCOt13iW1dSsKCW~Lw)^i~mt^tdzDv^hapxty_)+gAXYp^{mt4e;`!D(R zSo&$7oi0Jj{GgMQuMaypiKkUfH#k#R$Z+(q7nDO6wJow{;t4M(5Z#0Eb9@9ZDDiYJ zD4ucda_o3P*(o?7S+TvirCx}Mx6;K4NQU7Wd(Cn#LIYCCPVD6fO%k!;KmdZG(m0iZ-#{~Rcd{!K&!E*zienLki7pGF7c$&4{F5@(&YR(S0c zU(_i~1;t9+8ID_I%5v*+G$Ip&DLj~)OO4Xqf!Yd@abaggDeXuS1(m_#L(w)$CnIkF zQ^l069ix2jn-gKe|$sNP3L&+q#3L&EU+H5lcRaFun;1imf6Y6eI_mI z`u+VH{REwqbWtRCSbMVu81Ck$&hJLFS1H;cHsuobHr>#;&gs)ydcqPAX1rio&@F-` zu8LzaIGlTJJdwTOTXHwrwH3OFp;rmSdb?w|!hu=?kz>r22vc{`g5C5-ekZi2}@4G zo&m^`_-aa`@_?r@)I14)-Ly_>uhIcVxnilWJ|#m{5jI}FsrA$--RGoZPVA&~V}X;g zZ+x$P(o6%v!L;q6=l=eFN>P1=bU`hD(0ulfx@%fzzO2UdQ^)V9dtJq5v5D5#u}a8I zf;BIeqzjv**Gnb*nn>Wa8v6s#UK-5qDY*CO=~^mO`<*-KZNmdDHPsb#mR zdYu**b(NnC3M%z3J^%_9>*iQ9-@E|0i%`G-Z669qUC+#jhRlcAVkVs~+Hj$I{e=(R zpBgA{0ZKj%m_Bem3&1p=1g81)bFuB-b{dk2oY#nOeV;}X-j1wCC9@@cZ$~EfksD@f z@_^d=8)gI@rcr@I6!-$<#HL4D@<1+G#H%C~XX$lW$v?}(R;LF##!6Z(y$|Z-d-w7btR$_m(m|S6jy1%;aB7b|$%<;H|k_ z##SSM*M-Yo{E9azT(%JAvwr?Et{OdCC`w3O!&M+S$_)-j@LzV8)(B$L7aeE_DQrk< z)(~=WKIZ~ve=5*97G)y3bk5Swjoim36URBdNfgDa=R$^Q?4ZaE3igdJ@t1yup8E=k zyK)0kTO(P<46;%q_99zpR|$WB+heJ5>@4Wdq2DF96+ugOR%xtB zzC*A|+*OlXbv(gJ`aOmAsn^v;8cKw??FdCs-RGvPD{Tw@Or(N=%Z08syskKlb09O3 z+rbgKrWA#U<7&mhxvyRHRZ2R)81;D`WCYhab5ojWJoEM?E&agb-p$bptGv``0tGJJh7rwZV5Y5aK^EzwuS6}hUl{VBvGj*XH!&st#MCu5?*mhS6%Fb2>fVdCkX3 zq_E#G;f=x5MPYn9x$Vq74k#vSNnDI#sAsm4&)BUDb1}f&DjR>V3TP#Rw^#%L!e{sT z{hKLh_}SepU#un(uKITFyR)@i$MwWhkRwDZ9Z*W94nHX(8g3T!`d%kmIkinXy~pGh zG&ydckdh(iOpq&i9lM&p?Pzj4%@TH$oR}MozO@PoM%1R^xN%V%J`$Pjrs8F!dpem! zLd?rkeZbgptN>7R@tw&iu-AoJ+<_(uHd=PNt+(5Joryvq^yA{syltYY32-8VmZ%%mX zEYOutKY^weUC5?_m=7;A;IV)8=8AFv-awxs z`6QC6$E$8A!Oz7o+#E)p@Z0x?ytVgsFi>~D_<;rs?e!B*2iQTeE?W{3Bx=TPAkcUo2P%-5{yW>F^5o&<{DKRfJoC#OOLm zaquf^RZ)oa#8&#P%YhDTm?Tk6<4-+^bO`M*H9gSw*@My|eL`yunyd76tE>mhL(H6+P9kj*<#H;!8))4=cr!uHiprbga4fNabsK3%v2Fj~~CB@My z49B7^=8&U=9wiS7?GS{VVit>JWfSXYZS;JMzv2=uJJhVw1WP@&tG7dkBes(UE?OKk zDRa%`bgM?f#2T@4eIdqU*Z^BY&DO#^Qo|t)+U@FT?h=~r)GKm&Qo%yq09cZQK}Z*}IPOQFRM7~BA9we-eajR6yWGY+^QOUQ&;%Oi)OkO z#C2c##WTnp`oh$|eCdjQw|XwK&2&uUyvo>|s|xiPrq!nb0QEOkYRd_1o^S!F+S;E1YA!gAd8!E9lh4wg1-G@$m?l6pf6qmQOIa$k_q%2T4 zy_ z=`P$%-9*PfUiitY*>$-Wo(mcABPoy%yPACXVUd_0^!N{4^MDbw-UIi8l=eK`Bf74q zWvZO_Vdy4Ori%4PuWpk1;!C0`>Bs15vX@D`QhlMaMFV`&Pws2MeU00L=cwmT!>fMuwEpVpQthd& z?56JuIOY~&<8(|KsB3p)5?(C$`*>Q=SbSMjYpg#Pg-BDylGO2Bs{k&(6xG0I&&8cv zU26%}79|rr#z-z)&ywwB{gux5eCefRTv~kbTyj2t_H0SA)|cQJO)@V&U#vgXj^h@e zF1~!J8+-cl*^B48y;m>l&rF}=Ihwk~r!^VAnn&rz(#x06Yr2x>FJ8P@tV=CROV6G? zeYzwKKYvlLA?s6HX&t_C7MD=3*1~+cSVwU)nU=GFA1(_sP4H6;%uA$UI54wOy~FpqR@Aw|#gxrIDzzll1^0rkaxY`HXr&+ILC$H;YRuNX}PgJ z%%Zrw2&@F|yW=sKabc5UPbP-UQzK5uR1kZz-OQmxgubQpvq1iw)Ng{l)p41MeSbdu z9%4P3Q$cD|H=Npr8K8nPoXerpl%CA-?#9PGY1vV`A~E_&oLRdd9wIguEx?Tbb-er( z|A9)GTa6FVGN6ow4o@4(tM+Uben`U)y>PY{ejbK zN4Wugi8EfSsXVv&pJ7y(NY->Q`Og#Rm*fb*>wyRNV2L@;3pGa75||V zxaH5&+^m($GVa44dNJXwKdmz$4_+0J5{M|WpL%Ej>4)qqh( zddZ3ENyU)iz}^j3@N%8#A*gdZH8h}L%_1gJ^8oz`k4g<3^>OH`50Zo@51gnE6b5hv zCMbZ$Y2co`{29@etv77XSjA#3@@oh3Jg|U!;^sIEuOAMR*P06zw~tH`whg9l8$&il zLhTmP*+1G?Wk|U{4X5RH_K#*LaoyKb>-IK~=82irHO!HW1mP9TH*`favcag3m{2Ss}TE z2|Wl9c*46orF(8t1sR|VDsj!m#EmEk8zF)QK#(r7ksq;0xHMdmhP$pC9ic!97U}R^nMu7X>x)N87Fv-JH|*5aDSfwfdJl4eTE9k!<@s) z!<@G5e&90wSC~6wZwSCheJ!9@ZGxBl_}2HnR$0D7{P#ZZ75m_@@{(;bF^(-L81g13 z&D0wj8-o>wMi*5|5Z25m8X|b>Edb&Os(SN|xNFYmOn2_hHsN?mi+||$5SZ+c)r@}M zxu!f(W-4IG5sG6T60f+*9<1^%o=k$0^w8{FegzNu0@Vi|i4WtPtq=@8IxISd`WXYK z{v5jx;(DRT7`6ekt%rNLslbT)f0BP(zmSWB-{$|LHlO}0Z8}956z00=+$MM;qfbg$ znTF?d>O)2eQ)?vkgSWDFh)mu}(g8`GTjwoxJD8kO5*!z}e_E)&o=pulL24+x3)(*S zze5nK&Hd?^Jvo~Z3Epu#9CpkWD0G(++9LL9V*NP=lXzF5_%XNcsbbSEeFPLAnL&YfzE*d%rUyxb*njsLytc0vf7 zKYr1=?Xo12#jWaSN?qqHPWJBJkHaap0E$M^nqQ7KNgFtLY&60Q#bROxH~Mp=wYoGR zrs)hJ#~iW)B$X3VPFZz09k|5I6w-!+N*P3dUy%!52?=FpLrkC9;Ar4dG)`8bGc^H2 z1e^IDnK>W>exJtirp_TU{TNd*Ew!Qmbp#vRnQ!uVE0+3k%!chMplCxCa7bY{#!jmx zizVWUG@t!;y@1lE!S?WC4Iyim+b4r3qiq+Q$kBlOBTM{QOr{rS{gEaWTU|_#6IL1A z1$GkYwFl)N!s{sS4*7;2?uD0#XDs;`c|-LAG8j)(#9Pzbvp(q@U-$A!O4@{rr1Pa! zq_U*+R7zVmj8niRrXQfUGjh~vH^ce4QQ73J;8gbOA~w5*n|24r5$;f=B{z>mNiI)( zR+a}m%377}Eh*3`cKx@0K@=Bf1=-upP_dgg)1u7nQ#L6GDu{XN&&QK7d|m7dQhA%I z-*-hp>PDhe8{Zk7h<6F2{kT-3+msB_^b4n;10%7vh2HOevb>*&5jZ{^Q;)D?9D_Bj zCM3ye$yp}uLHw{jWK*%TlLaMwPk~{k>^2LU;R~tC3a`mPEh)3<9%@%FED)XK<>NOn~E-k($ zhXfo89axvSy`R~40G;U7o{e^h=<}q2pcfxYIAsTg{C0u7XK1HXP^o|>c%0tkH4r$wIc-2Un_|LR6(VGO&R~j@U_Nu;1{YHB!+0P|24yAr~2t; zmlvs-2!-WnrDI>ImN;=GFqzQkh$fAg9)f-gmiM#9YA=-P9efEd&cn{46G`IOn3cLN z0PhB>Zt0fq1+bS*^uT&AEKX>X3?7rJU*B>uf($9N3lwK4P_rB*KN7kixfXYnY;CFjq-4JF34;HRvX+-hA-7FJ7Iy%1w;=(A7QC9#S{_bhEBe}r&oXF zG2n1whr#3{`!a-cTKXP`$uPn(%z&x?WP=jSb^?>+Ui_;;#>hZE`^biO$orJ*voOn4fuAU#Zs%_(qrMKQ_kHrS{>^+*9HuM&Ccm5~AND`u0d|xlx>0>Xu}XcA z1&aE@X@%n`dJjqOjvP$>%YgwY30*buXYfvMv?eV)85#)&9Y`MX0fk(8K)&Hk+2 zz%scolJAC>u~DdR9MYY8$-|y(y=)Q{0EV>6$ggYa<`HYz)GvXoU>$e8L+4+e+>>Qx$ z9kUu#%OP*!9n|t+XltQ|5zbOLMZ;-mO&hG2K@Zq3C5qNwBu&GJ`~3lIuiKt&k*0w= z^p2BndqKF7Mt5sjyHUm;c;+vMdt{e;)*3b-=Lt9CZr&(E=Q{mGpXG3WAVFx=hr4Wt zsKWeR4oP8OVpy9$T*Z@ya5+cpzjFAshr~~NN#+GFIgQC7dO zJcd_r*aL!_mN0jX4qkabftNdIoYF&VWh^7c^RiKX2R@v*KY!IIe`%e;0r?`Tr$@W- zxrFgD>KH<9Sl*+9jn5aVeKdvuDtX(dSd`ykLMvRXHR$EFO!Z{(g`G5LHPCb|Em7Wc zGC2sBo~my(4NK3cML4&C0!DcwrT2Q4UU)OR^wOExrC0v6KCO9^`V_SR!NBE@@8@=Y z>omsoI(?ug>x=Y(ZmciS2ghSx)-VLI#mv>k@EKFQq4!5dgU(FBGd}tdJ$W99PUGd9@E(bD zusO>DP_g*5D2=iPx@AYf{+**MJ#n!bWx(KzYS%s z&Jce^5M|<(HM}~Q>}Snd8wbLF`KR=cN|9G-uUq88Hl$Z50MfulRfx@>--qy?3j=&9 z%jm(h8&V_p4`2)v)4+2;Sb_BVx8)uhx*0cB26Kb<#oz0~-);m@_h*lw?gpUlLC@Yz zM&8$8Gi{hc(=*u&T5*?9n~)F^c15g%gnVr5IFWcN=bQWzwvzuMtPbT=rDO-l_!M9! z9(cK7;Qy7oae!`ocJ{L3)MVTcU4LIpSTQX+He$+ZPu&$$797pL=jK17`JKMKn%sv6 z{@_Oa$rsoTq&&O1bIr&7yfqcCQ7QTkm3)flu<9foGbwgzWCe6Y$xugXKW%EKvD zdKspqn--54x5dYn+C6wzfTwL2jNb3isCtDv3^x!io|Et(iy#mwuUN(wLqe)vtV#b8 zJqpUE9b{|?m94|T>yXX3&v3ITZa`Z#L%vlD_0P@C{51>@dz4<8Imqts{~FHCy?jl7 z>-9I$UqD0t>P2)ze$9L*^;4Bcgc~Yn@Viwh$n|+5ogYydw2V~)`6UzbEH1ukPUE*0 zd90R;Ydk7G@(ARwg3_HLLc&R}%{1K(>c;4oF+ce3&BTwXGg0JVN~6EDCtGJ+X7miq z$ev?S-Z`K-uVDeCh{HT}-2GDD_-^^DH#he+6WgI8H>=>(?~}3g{vJ5$EX8iaEd2l^ zgIa95$%wN>{4TJ*G#89!BNY3Pq8YgGyj>H?!r<9q9L(tz6e7EQ1*7OofE0V7lMJMj zqB0me{1)xi*Xm0PTg{YGq%!K~$<+zd?p~c`Jki?+*7C4Ny-}AWp$0!vd7nb9$@CSV z{qrumV^er?azK1+{YzvveV27$fh#X74+}&sljj7WU+juSd81HH6taxoe4=lDcZwY< z0AFr%#ysw;pKEr;pj#OYIr0gmygIr?Qhrp_WkZQ9jDA9NC8gv?pR%` zCym@Qsdbb#Vk>5Y={U&`qr62g-N2kgG2DaD>Sd_MbVzRk#;7JosO1hK(<6~#tQ40N z?*sZ)eX(K9VDu!O@J6wy55@)N?YZ)7MQ=B|%+DLgr`BJYm@UEWYi?BAT|HX+gopUy zRD&`2A{%$uLeHS}{RXFuZ6E&9(tY@pjRq4o196jnZsku{lU&qvl=Ma_sAm$e> z3H(EL$>3V}jNd`h9xIvPJMJ<%{*EMrGgI%J`}E*()b18WR&7vm!{)rvi^-G%xzA7gp58iQqs;d-HVK#xJWtD9Rg+b>MNpBl5;zag_eRF1LGipNlhQJ* zn5`btH<5O0eMe;hcEzbwz7UVmN;-}ks^7K^RJNy#JV{T^i_k6?#uRUTp&+Q(s$=fI z=e98|Y)S07W!{(cM7p`ZzBmt}(Ys83q^iSd5HG@rrbK2qc@kEyv=MrM+}f$@j-U#- z0V@wClHYe2J%MH6aV>6$ov+s-j)VxvBe;%*V+071AXf(4p0;_F+yQ;S3k;VU>NnN% zNg}q;UV7wx=dX(0sM!HkdX&$)?-3i!M4VbA0U?%NNu|f4bZxjlv4QQ@*55(2lYbqF z(7yYPCx1(w^BWm1xvsDrFAC_67Y3f=g*CRs-ir(cQoi<+xt8>WJ7)BSvWc%FKQhvM&}ST;^1-khIb<4*X+?yJ);F2fZvkcR%C0NH@A)c z#1N1Z6fmSD-YZhz#uVUKlmABY2Yebde#e`sLT+S{#A{YjErz;&!hOYkdB4xu!3Yg8 z(q(e~(dnofIGdV=it0gV8U$mNt6!Uv7J79AqI=4AV!(*lX5Ru%a+&FXR%=gi7i$Su z*e5Xs3z&%cj6eukr-}HI4#rA_)QD1fmi%WiB(kEFRdZKY6}z8QRb%}Kq|+3+-=r3c zX=s)7!_hV)D{&G%&V3<9$y9_ld@Fi`zryp+R^MmV*5KXpjhPeWp=Yp2*wW(;+|Vxwsg- zJ84G2ap7h>A=Od;{afQ~a~|41@>?B&QJHTN zc0qiqgFWp}_Tdu7<*ZvyZKemys0|o|R+CwG>Whg%ejDI!jl<0mW-9!vL-R!|gs(?w zVC4bP9iGnt#a58FVBTj{cYZ=6LPHgzG?4Cn5ZV`y`huu_O3DI9Co8}3r>mIFmpv2J}N^@{OmxBR{wrckIZZ1NYXdfwvi;B zwl^EW{~q& zhyIdho)WCbc9e8S{MdGl~#pkM+fbM7$(EUM)p1b2eF)InkCkb6UI;eU?#*Kvr zF10T{^+m4s)E8Vo%Lk3>Qn}X?qf*vRE|Fd`|Djjp2fA9<@O9NOeC3+uo9fn|RJ(jr z{X;x-Pa`9pWtlT&sjBppOG-nTf2dh(4tmi>>{v@;wOt|>=02+cD|6iy$A))J+wiJ6 zQEJN4l)B$bN}E;X;(q^XVaK64rO7(gT6R-xjgl@~_VLqRU3!?7Tu*9>ZFd9&WOwv> z*wrY=?no2$J>DIigcsM*)~Eo`FDVX2F$D+5$fvY2>p>GexVWaPsyu&r9gGn? z>QJx%xDo%&k9WZ{4WsL&YCwz>t5_cv{6MN?%?YE_b=Hq|;!|cTrpY@}t>*Zg8!ABX za8U96HsYChqrMzEs>>&CRz8Hk%_@7})az(Dhn|LWp@6CVu>##GEN6i*GA|`P4|1#> zi*6Wl7g>AGE|ba)f??`$4Z5{ixo*s_ebTu7_wBu7wRj~^!r%*1j?fBeLPzoxicH7M zh$Fk?QUyDAGnNPJ+pRorvV%_DYL|u+_5%N|`V}qEi)j=cgd-77#%$28FG!rZojxsO6y3ua{!Tx$;vF5U5l*VlYc!&h~s ztZ!w$c=hGRx0nZj-SWhD0S~Hj06Q}abo*VOFj7n$sJn;;?E)j*A?C+#C|ZCKb-H1m7Bc0&OXbn z+rrd7?2Y%VZ|K2p?b|7ZOBuMIS*vPxUwD9k*Zn3t8eB8Yg}dZ+0PliNRUC%Jgmzzx z8EwNO=>Zwf?JAiL_2~vHE#$K`E zVhXNvP?o$8q$EIMUO_7=E5iciHJ47wJoBJ%3&N1xeWzsUc_5ejqO;|{rsg5ZiZ-z#|9wPIdT5P->G9vHV1=%_T;Q(%aD2iC<(b}GHl}v5jiRT9` zc&Fz`+GCOfOwCRYZ~j50e^QhCwBo0c>w`l9nv`9u<#To8-)U}9K8>GOsNInOrYQm) zPyW@^kO{`3>~N0cA@}$6^VGnmhwMk2;`+3Gf4}B#sFx6UIy^zgVpm#t8Ylpy{k%4B zd^6!)utKFnm0i`NT`qiZo?NJxhGC@g4iZxEnh{upY7tBr$z>n^;wJ~@Gt(YX&a-SsN zvv%2`h|)kv>|2Ve=qWM`!|!0lHYb&{tkTUb!3OsAry=rmTUS!&LX|&zu6G?{NQ-8~ zQZhuQRyL7|HM1xeTU<_NddzlnBC{C%vgAF0FwX0+b(rs~h^d7Y8F_Pwa>y}RDv@+dpXZ}J;=DbJw?uXB$3 z>GeX}kRsuW=iW(7MiKwf+gMYrzeR}*nMc{b>f`C}n^*rmYgxx88~yyM6wZId7M%=t z;F978i9cCvsU{}ekAA?}w{f(b07o%T9Iz@`5;uSF@z{PPMzb1t z)n5OI9sR7B(XLaxrx*hWX_YMa>IWF}m9k=oP5vVui|cDl*b4=h?f~%^z(v+riDz82 zQ`|)$z~bbxDl9HVpJ4V0UD}1hUU(^AG(54$EA{gf zbV#BeZ;hjVY(+Nxco|Cb66-(riUszCB8irlDIvQU1z|MH4|LGjiqVJ<2+V(vmsK|# zN*mKz=^ge-fg}~Xlha7VXE-*6`FijmoL0#vuy$`6-qCE|1ClATWmw5fwNy z`%!_bGb?LR4lBiln6iGAY}bDcj- zog4J+SBYj&=Z~02fj*tav@k)gG-XgTy0(vzV8k^H=||k$Xl_IwvkFe+?EE~#g^h?* z&ZB*x$@oW!&{_#LBo*}K?7~Jf_`xaE((J;5PT5$PZ3g?4faAZR<0*q)?kL*7zqjB* z#W>GMs56l_ZS_*!_=oFFL;}rIyFXjqN(*yDW5&EN`Q_~1oxG3{Gxc3%q~~%Ca5z}T z(jEl%=wK}zZIk37brZ_zKKpVSyE-R{)feH%ssaMBD{=HGHi{45(0V}flY=-w%7wgW ziO((N<7+dH204$u;`a)>{j|^T zu~-v6Co!p;1aLPWQN%$PA>-bRNveLJMe4zH%oC}|C1UsqXO!yFCt-ZeM?#8DX?(QM zpmQw74A{9bym?u|deRaw5KH zuG97>jr_#d$fuj_{(hZ6D5IOC08Q?CQJ*-#+y6s@kc52(KD8{e0 zkpr?B<8KUp1KBwp_r1f;Jtbf6<?1u*aJ%NXMFFn^^JLux@?$(r%?L`ED{P-(BDSzcee9ZoyZj=K z0(@(BRuAl~t^ts@m%&Ln%~|=5xX3E$#+_KS8LnZ@8sO_p&#Y%x=OmRd?GR*qV|BJ` zG)oWZ?A(kM=smxVp8Rw8&t&<@d3Z(NBRp+gyZ`5CeSLlP$+_j(j=I*Ytm4>`iE=Gi zs(1`G4$u$CWWet|zYhQFKmN;xuOQZS6-<+s^AcemjYx73aw9XWW1ke>`+2(f%w<>) zTl$5=^^w9>Ka2`CF0-Qv-F;6DmhbfxRBCU)it3#hKe-6PMW z8W+V6~nRxQgmS{xmPH`lsIo16_yE955VV(qT6p8@sQ z%VMvD#OchxOdEJzXoOdm!3fA(*$=ObpQPQ$w7c{U{PGlA=XMucDNFlTF|))WV*sNE z=~eY|-YK*N8ml9_`lr`y;bbPD;yLbN35*MlZ%3ZMB_t6@Cp9mTxhnf?jN`tlPA=q0 z8ssuz1NLaxR)&lGPVtl3vSCOQ;qO%etz39p&5Z}*bYVfE6D<0eEi1@Wo?SLyGuU`A zxPVlsJyuW=np0ed^ZH!hT2ai1r$!QmxCRMD`G|gpuC5fvmn= zjyd;9eJz*$ay)v^MsU(>qr(t})obBwr-%KoXWY6phCi^Tm_vE9ZJq&OqeSs0$vsO< z&l26UJLy!-s*dGmzNND`hx7B}DV>>kr|U3_{y_K@&JH0pQx^|EA2nN^Kg`1Ssfi^? zVy0q`Y+mELS*7fcObQE6q`=;_iPqv2N%?M5Hqb%1MdEmSPCBl?v*X#}i4x+Pmu%eU zNF23iv2Fp;L0ltDE)`V!rv8IJ^g3yoD>|ewppllx)@_gACRD z{!M@Nklzb-Orxng3~`yw6&mJ+@x8~j7UyRN?_zGC;2OI$5+he52k?jb`d zUFU-ZwzCu7jbq61~va{ck5;_k2!|F^;GLA_YcNVd&O1 zm{4-@R*{WACVDG4K}wQ9cii5S!v-pltg(=>Vx5q;X~EKfE%Dn*E4Vm@+RKn!Lk8?= zMmet|>ZNJi0%G-*qiQ4b_l{}guY5qz;-R&SLC$$=2@Qrm9T7Xvp_(cj9#Q{ho>jNwJY$58YJq=%u9x(wuUwJNY>ejSzM1#P@2D~5jS=RbKvsUn zO=~>(ZOEI0T%VJZQTMrZZK6VY=Ab{Zvnt?q2=Qsu494EboDfbRdl<(Foo&^mnU8b;eYoW88RcwpD+4oDC@>-ug7(x$`~9sPR|> zXhJgczCg^SMS5f@4RBYbcuA>3PdtHOosa7Bh_A|!2lH*(txI^VOg~oEUTFW{&+w2g zU_2GOK^-n~1)M6y>z6lSdjWrF)URLL%I*_1bf3~hbmqmdU*+jINzVQKbHv!EeUn$# zj;9XS7tcpMthv|p!N}s**ukw4@sA1i5U;XU!_ z&ORPamBAIJ0Dc&|qvhcZy|C7>kMrSHq>WR5>Fu0{cZeV+z}csb@e#*HUq=oLihOH! z@LZsNVl(z1-E}U=&m+OiFLO=kd`=XVqse`~C}qc~cj~E3kpopHBqyg$;2ZS|;QN}8 z(xlX~jDwXDD_VCf(j8pDi+8Di|IOm^$?K?~?@4yxmGGP#Vux{!zZbIQOME(HV#hPgW6 z?%I;Nh8l||=FGbmg>yqpZhh7W*qJk=c6VG@)us9iTGfVGB&XHz?=m_xZ%J`A?i8f* z89hc`l|R5*nsJy+09L~NaH<%~NtNfZ5c--db30Z&(QVOv*2+a#X(1f{4>*ftMLpSL zhgVEbesX)xUFN*##0UJ;Ad#mEi9EGPBr7UiKm3-CW2T%sM32%=>4x^;IQ~~006m|RP|J`y>}@U&-`&D?ByfFLA}uek~Sq}O|3AxEY_WCpokmvC%VCVer|mol7jhu zY=p;N7}?^>r|PZd88eH846l@i_Y^*&2*$LYy*RcpF5L|dDXwWOR9LDjO@U*zisHEE zq`WN43MIfR|Ze0f}M#4e(E+I1flIPJk$)GQKq{pp}} zL#886iM?*YwLi3O)SguJY(PK7AXHG6(Kr6Si)PK9g!?XFzvMEkn50vk@*4rPVHpD8V6HbQ8nZMeEW;fBGcHlTc{`nK_UmZn;!c9A;2 zK>~C)#tq9`)rfVjXs8c4PJEmu%~McLg@N1qHPw6QLzx<8js3!`=3FjUW~0@1WdqYd zs1oH4wMJt^Gu1$bc7>+Fk9PzJPD7#`wBaG$E7aMK&;?W(*+*Gsgk-E#$sUIrvzDTo z^+<{c$~;Ddw&HoGT(NL>C9clSs!S;H6}m;F3`B_DEcXX~8-Zr>U{oJS&y(TXVJb_l zB4m){@rX)%YE{&LR6liTg~U4Z_Pfd)n($B7<|%zXpwy4#4L`=u+`Am8>0_fK&h1Ch zy5IiMx|@Z%zdt^Ca1-dorPW+{Y*a6(<-N>pnF{>fP1lO`;j|aUtQE%M{j?b&sBD_Z zVgu7RY~UmLV{)aDBXf=$^wXm`Wz98Q^4 zQPPzSMb!Oln4aI#>^#tI&il0?s?f9kO~UgAAD^@KK0yU^+inA@f~|RozjlVOP_~^= zy-DdchI^D3W-6FZ;z`h;XK$J3L!p0mqBqstim`^wF=~6Nx8*BwPyV` zitD%W@cL078Tu?cP_AJdqS_^Kavn_0`L$u8;f7k1Q051UiW|;ybYi_88@wJH!2`o} z&zw^IRo@9sg;RMCoPU~kGVl-p*ZVHc5p^r0wvI?8bC@#_uE zzg}ks_HL{&l{f3aXHa4{raHJk4GtQ-iv8x0{u{c1TpP#h@SY+^?7h**LWLG9!S`V< z1mzg0%P#)EkEh@-IoC;eTnBZ9v#cbzsneG+%J(r8{tT9eJew33atk!+thu;R@OYd^ zwU;3F!JggcYFCs(mhCa)uM;za3=AjDI6b98>gev&j_!IL-Q|v=4|?+yy293VXew1b zo$SUTY4cN4RKn@Zq4WAVX7$h!rCU|ASU*^Nn7ey_J#MH7HQ|T(v~e8f?l_QVQ!S=N zM0ev!8{D6LbM4cbA~>Fye&_A`zqKD__y__a@vhGNVKRr;7cF8$e4|c2ovZfra z_i^EHsffQ1&3<>d`sd!^Lh!M}wUaxC>o;f+zO)6yNBXwoVH3F`xfX=~Do!PAOhY0P zI*hT;HaC4$p3T_cQXDq)WT*&9ok?+8U%>JogSAsibKgdKIN5UtbKOU~n%QYy7v7Bj ztyJfxVnF|{gQ{PG{cjfWdAFmg9s>#ZZDNVSMA{a{c11O{RV2DC@wFiv)@$k*{aDlrKO8}pfLLUZyp9gv$g*j>Ev;_TgjP$pMFOBxiBZpPI?579#(j!b zwsq|;NGHeKG%N%Y5hw{#v$>ol)5~7uJ*gnu+bZ>wFgXt-N@Y`uFlSqvW;ym4jxJcL z_~rvr22nF455m)(NCIuRZ0ad3G9yP+!f>sZrMrZ&^lf)YJiD2&^AW#5@v(z+=&{M& z*`{gxI3geW0SjjCL{$*hGFl49`5Huelc<^&mUe-@yif`Qn~Fm&)kqkKyfLvKWu~zOe36XmGMl34i`HpA`65%3LR8%uUcpo$dt`03ANobTMrY{FNzXnd&2J<{Q0aH&YYg{5 zGb|Jr8L8IHnK(vD?jPk1(EunMpPY0gr7UgWK3m6)O)aT=wnBS2b+vRjQ{{L3q1xTU zXn>S&DIrCl!`x}V#5|XgIxMveXhlpLyP2}vgq-ws^u2W!% zw;lrvyZx&2T}F@WHIs>L%2Wcxb~L-7~*PZi^@j`4Y#%V#hhR z=_BX1T|B29eK6DF!A#A;41F-CAtu>K6i>M!T_+|xB7gO=Z;QoTyyvo2ysG?Ds^G9v z^lVS8C{#R)T_TkNH8gWh$>)|?`eLfuG0KG&9PLHB-0SA9TzRysta3th(6~_v2Z-I} zV*S|Lvwep}&}Zi+?w(zvh+>cF9RoJ+lxF%9;1=z!>2@b7h;{=;>y04{PbA!7we8M; zf<=v7Wnp}3Olwg`Tc^Q0#G2abr}Sk`0ppn|d>9b9iobW0L*p(vXXsRpPK{5^ns;i} zCQr@Uv{MskA712~3yv(JHSM7*AvHkq5utpAoEyh@^gbVX2Qe)O0rK55G7Roo%?X6janbSGNh1+UU)TdH zP=kq{va$jY|FxwJ`=)W2)%V$J%P#nTLBrWjh`YJRe$&o7`I1|6vGBYz?l^H(Q4mix>Bb@=X^!GVSDn>PPWx2wM5gJd*A04LIJ|V zW&o4WR>!g}SG|}Ike|d{7O7+VWNKA6FFJjN<%eFoMBGfM>9$W4?rz zbIt8Y%P_Z2Uvt;#Yp$HW>TN(%*q(7#(I%;iK`ZKW+kT9-|EkV5HEng2Kzpe7NB&C5 zwrX`qg#2H4+wdsWU*GhXMdz6LN|SI2u=5#v~e*NXzeAfZ{#@4D~rerSLl%~J!j_kxE{<;UygvikxT zp~<9g>C_-1#aL;`vb%yjLai_o3&tJlt?smI^qZ#2q>EBJG&cVE=8}0UtVV`u{FWM< zB5{<8$km}WG)kmB#qDg$^G=n*oF%TVTf)TFk*QfI7q3T3Bl9Y!rLDOu+p0KHP@<}H zk8_wiI=G^`?Vk!p9vx~DNRQRTWt>yYP?dKMt?lXrXUc(n!BTUNO}np1N7BM^cu zZG^AuN6iSHXsR-ukPq%l_J9%eu+9GajLJ~kQ}Kl|^Xp;TwXhnt!To^$rF8h?1HH+? z&btp_wT#m87`PgUDF%&x_E*s=Xz{N0a9nVpp;w!=AJ*^t!!uf}yPH03n`av*A6)dEY}P)cONL<^2`4`Q-UWO@Mb2_wZ8|_^8ocqAJfG-aZR+LGYKzlaTn#<;oUYF@jnv6_J2V;ZyxCKX4omo%AP-sv zf7(xn4waVP8(XTI;`$;9;z>MxBfJI3Jsl#gH&oV<`(|fK2X7qhf~IG^Fxhjlpo?T; z^;XLyYFdp?h{zo_Ac=Q9N9W81LX`S=5fE!k~61BvedMlTZvXQ+)o|# zQ?Vf|di!@Kcx)EMx%hgXzZtD%@6ixsgx_$=jM|ptP3sgAy#1*;T**VTLx)Jw<-5eI z2HvI`4P*oox zT*I%yiJkK9hiESx#%_`gxTGvPT`0BeW^KS0IMl7Fw<0y|uDS$r;ose-=B^>Z)*_-~ zz{p60hM5bB$TdSA?O*ZF_y7ZO7*^IR6@B3yNK-3nYe-dVAugk|c$d&)0wT*P9umrH zNvfOBIdveg{Ou|w#$sgSl2WllO2tG zBq)oKxSxHWs=ie>Kv~Jm+4tS^n~X&OjYeOq>-yAx)!uYpi&D2MqkbJT>$Xx})p=p3 zngQT(g5x66Sn35C5!kyJ9E!R?{!mP6|4&S6-rZ`Dq2U(?9repHB2eM`|_ z!kxuaCohOl@K{5Prj(vBaYRKhTPaKQeKvCXtxY4{4I(#+!1vSU%YjIxKpYbU7fNXr zF<`_|o=j$E1t;!Ce!A=HpBx+UQ!pGP1|Z~Ag>RYA48(Rs(^2zDbQpdoCHYc=Vkz_V zFXf75^MHcBh}7*URXyNADeVTOe6=0fYt690viUMufDuH^aEme{KBJ-E(IhioY+!&i1R>JDZBBXLxMot-8BCMRHa?&Cd zf}9ahlwFF_y?ghykN5C$;A1DknN6U3dGE*^CteVi_zs)Hxdj3qqO{4HnJFhDh{_*r zd2xY1A!=r`%~q@H7SZP^=U&;0?#bj9&%`^#GMc-bsULjgz;>aPxr^42xIqG?d!vPN^1fz%0osZ>vmAyl34rEZw= z>0KG{kMC*@GMLad^@Iw~7Rb#-L=Px<`>Lk>)S^Bmr#9{?x9V@-^J=&=@ECIp8t3cAK&VrfF~L{qo>hn54ZJi(gf1^3q*Cv& zf9@UjyuKh22J5S|T#~5%T;0t){5C z_|Gx3LN6>>=$u(-z&6Rs zhyfIB+P{>81oR|86y?f9_236&?e1Vfvw=ko(<~Kfe7oKiWiqC-HDiZ8oUIb|D3AQI zg~a^0C3Spy>3fU38=KrgS{N&gEEWo3r1*1Jf$}B#m5%AwPn14Q%Xn*Csc_N~o(YO@ z9$}kxaq6qAX;6{Lqu_Mb`37=e5P_f848;wGpe0TIh((Fyrmmxfjzt^RmwI^G5C(a zurN+wqnI?4ZR>YubT;`)KP8{*C^=KR@lz7mm(oHQ`UU-Ew;)c&mBiSgiGhdf6w?Tq z1fCjO@;v8D{ZL(L-b`J*IG=Ft-iu5jl$zW542il4s$Ld8NEQI|^==VTo&IUKS93Mc zztnC~zxT3_^P%z;H8%#xJ!m^u7o!9x5|A%A>~gl86xm8BeURka?mJA>L0 zm*2ZB0{}LP))1U$SFr(=`Ks-fR}b??b+fghE;~~A2D=Qo+ zwQ>CVbC(K{!01qasHYIRP=fsY_la#;jnq~yqoQ7Fds-v46;-Lt>?MiHT@_r(C)?uA zRs>ZwimE}+2EjfYq}6>`0|8#M0aUeOw+1-|v-p{c5g>5m_K9ojzU+EN{9063J)~GO z0zL&0-&xkvg8G%RAiX>fn+7D)szhx!4v@dvgb{^Z*lbyZFxE``Nj*}*Em(%)Qm!Sr zE3G7oJk^mkJf(W^^dksTo-Pf5LqeB!vp$T<<`sQdzSIP>GH*0Sn6+PUgu@xX91TX< z^bJChMweXy<(3l=C1XzJgcHg5k`8jR<>N(ekPW-hX>1@-#$jWFFr&*6!Ux;mlTSmr z5jURWuJ;O>utBdFaPU``C~qs7hQ&AVz^<`SRkOVLNI1CpNO(S2N!0skEm8k*P)A7j z1{JdW$G{TL-v{0?nwqWuV&Jx~m@3^>Sh4bvBEf%RS3AwR8M61pwN~tVS?jBtTr~h2 zJW>_b%4hI7oYkoSh0i zc?EMYXU7P3)XL2SbX0>VcTYtp5cy)C=iV%M5)Iva1kQdmL&n3La15&_GUFEdu+g(t z?~F>2ixF>*Ea%e$iA;huXbc$DDzrL*C9hNq#{C#!CM17#`^H_BeACx$f=}U(NaqGe zk5Kj@85yB9@hs#anu32rj5BRKRH#8gIn=+=>j<>>!mn}`5%6_fXvJ=i( z4&_uR8COpyhN`d`YE%XEqq){1iN4YCJg=tl5fH&W#NIMoLEwh})$|7GpGt3l-pLqS zVKB7aj9g+SGp8R$<1t8ihT~MDFZ&3WD+}^Ma239Lb+r3z|Mb@NeyzN&-cARW=TWj* zLwcH}DHR3rv1e&D+(ssI;}Cujd7a{|a#|?3PV%(oh{&$VhbEWujcYhHMa^37*REw( zjF)Y{)mqsk`aHyry=;ncQum%An-q^&B-$cvq54Sd6V+K!ub9VGwaZFv(0eNF!YUJB zl>~{>b(7BLWi3E#x_NU!=;=$7pD()M9e+IPN;F&1*V`CYv$9}Q5q;gVmFqS&v!R7M zjt(S$b_qNtD2p=fgF$9BlmBKR5bl{A>n=^#)Sg;ffzQ>kULd*`-kOiK20a8`?@nD+(F`iyJ%CX4`%Svi_|gk?<{}n|ZdRLQ)>?FBA06@XwQ#=+q=;M+xRIE! zauP;)Pq5{l0wBqc@xJmNDPZACvwv0Fy9MJ1v&)or0b~a+LU>N4;AP*4+#AFK!#af> zf>oAs-${OofA#c)N~$V#?!m}&Xv!xf7-tz+bsVup>`G)^-L~MD{oS^?z8l|r zxc#{OFfw2aTf7oO?6RJEy!9PHh^sxh#bd|<$BQvr)3!K`sj*GtdDaq*;6_=|HH|c) z`)-Kn{+i*{$sR7iA5L@WXr@w6*tUIQ!>;z%Yjvgt2Rm`0AlK8_8%vdOm9*@Zi2`e7 zW?@$Fnj&9D7pB3&T^>^;fk04MKgfFlszZnZVFr7<{cy(&0#fN9I$R+nq+|glyfA1n ze$_8;x^6#Y!zdwtgqY(Frcm(|(8zmou?Iq+9HA z|J4@l=|HQkhqOKj<_eBH6V2=us-p~DrJ5CK#WDxHkT62gC=6FsD%gGzM`b2n#af%> zXpse~Nn5csgf8N~ALC^p33N9iwu|Y--T4*c2NQR}`MwxYUwz(eTRTfux#MOhBhYek zR{CnYMW|6_&er`o%c_n4wdAjaEhJpDoWo%Jd^JVvjSQQJU4j)R0zR&fa*VM3Q~Oj` z5LE_XC}Ni+7Z)4_exp=4ss$FmIa-B}4qg)!(|0!N!-UXishB)L{aT^2l7Sfxw6!gz zljcoPjJ2q2HI>GZ5G>TZhI?`7-#_Hv9GA*0v+9+ep8&^>ACPUvF9Ky$IV0k=Gv=^N{p#_e@{mw1R|)F1B6e1Tvi~g>uP6`>RA9 z(a(GcvywAyn(EPaRA?No{Bvm$i`w8~6olk<7g`gtuaQ9(MjwG7qr6Vr@m^{>Oms}| zJ!v`KW3=Aa%_HjK34E{l<1q!YtW;$r7FA+guS;0mM)Lr43Gu?$G)}cTh{fiu%7Y6g zWE12bppdKg*a!chS;9Go4o42xh0rnryUaq1`iR&$C!H`Hd}%%p!-A3i0Hat*U*}P6Mcd=Bd~G>iDfz-YjmRyjfk+ z@5AComCfR|Dx1YkDjQMV*HkNIs9GVIs=xel6l}-15<~{fo5<`@a{r!b5DvRf0lrbh z`~O-iNL#PnCVYCK@z$zz13S7j=&GfJi|y@qj!2inOy|C9`+MHjKAVCK;mFOQPj1YN z97=LyLK=IAL~ahz>BCxbbEwG8p(Zz{H<6nz;hpG@o&F2-ruBc9+R*M))IidkLrrg@ zDy@m?X-!uH5QUV<`^KkiCCNEmL2^!+#f*UVEK3TP{GELDXyb={C!m>bWBwS9MR);jYySFq`SM*VB51$ zn{az7hkS6ia)UGT>Qzb>eEsw8UH!T_PT7$&wYMx7xQyVcQF(Iy$Ou^?Qz7eYBm7IK z25ldCQKViHTrKU2Jh*hjrNV6se}^xrbC3B`BDx5g`kEf(!iI2Bfb>>WvxcL6RzPC~ zl0JJsmZEJfsLT2m91{#yYP64ZYGi0}(&X0m6IQ;hz=km@DAKO8{uYAz1?0ZFTP+JS zX?D}EEOb`Sa+#eWmKjtXBtS1v;Pi~vWUG@&2q%00amSW$suZP8XfC8eiI)DrVBYmf zC82eSeWQ`VFkjJ~j?pJY=H(8uT*CQOHAz^@$;rp*a@tYf*RsJ`r5`^_NOu?+f7px= z{n?>L-w;?(LM_<0cu$$#=6&-km|(2Pyt>gBq($$X5?`}W$yU;;yMUVz0__tMVgWhL zoW54kOw^TqR>N?}C*gmHVO^LpO|AZw!2m@DmBW=|?B>ciCN~#$$Q_PWs-{lWUijl* z_~U;Vl_FWDUK9msWK-gVKnmv{Y1cnv2+7pDyu0<_(c^ae5ze4}=iq}^iK>!Y1f~%y zMogni;IF-H&&Urt7O7P~h#uM@Bf*Ku zcA?x&xdhgNhNOH zMWOyg2-8uO7hppsZ*~7D5$RJhm$|gIf5QHtx4Q7?BkM@Z=Tz?EcAB|RQuK=G^rsp#f^h6Rv5ZsCP44qly+~hcjZ#+ ze{Ji!C96y&)p=R8R+H>SEy?b;x>X~FNd;ntQW=Cf-cq6(GHR!Wi_s~z$>~R2a`73N z#aoJfEjIhZnN?UrBZI+l8PpMJqUMxrb2@%SPUw=sUO}ZLtx(*_0rIWv>lXduGF@13 z&c)q(3k|#Zpp(Yi8b4D3a6ZDF$MVjj2O64l`?30g#ixEiW3L}UT+q)Do}zv|Xg|@# z9&bG{e$DsTPM|-@$|?QI z22xCevg0hyfDa{OgWJrQq$tn5CJ+~1fL!u zWjwOVduzNEeV{E%8RXj$62*t-#CUoQF#V|?RBXS-!at~9qPlyR7YRkqIb)bbqixka^dV(4 zA5vCDfL0-8S?6s*%vBbnT2R?61eFyxTm`~YDGLE*?V@E*at$bpkn=5ShmsqMkmGCB zaV4tH`p8JtdC@@Ad95{g+;hu2s~i*E+@++6g|Ctz0gOQ`fVqjqBs-`6X-PB)7INQJ0ALjv;nnY$! z0E<%B%!qmkF>R@u+SY41jH4J&yGXRfh?OBbnA{OnJD-m4ZPrf7WvrB;!y8XhZVTu@{bhS`;R^HtKA z?kFidM1=N;2WI6twLJ4^oD)tkNtP>h+gGgk%q>Z`JnQQeHTlbT<#au>h$ebVmLko= zP6s+eic^Cs&SPOZPYJHgWAHi@Q=kyX;iZrSj~{f()P+b?a|QZ!wL9imV6Wd(qe@Ie z6D=NeeYEdOduidKfNxX6CDB9y9i)*Y<$!%5E`CY-PlR#wiUM zg;X;FE(?UlQ&TiG>;dLXBc#hssaSHuzwdMcm$i0eozRxP`<{-7JfB3L(&%y$4JY06 zN$f}6Ih=2E*ykRFHh!`X32pXDjotx{~d@`YQ0W9Sq{YHPpLEImbX|58PdZnH-!No`g&y#ssJVO9IK*GNUj~R{N zyxR31kYJlMQ>or3ScP&&|cfV!|+_W9G4(q0E9|g%91AnS>NO$!t(i1yeWaiMr zL_Yn*;=ZpA&FCKvfpIxWUyipDFOjhVV2g-2hR$q&sS z*DOlPBbHYNM6Vt?erbsk$a>&E`o86RU4UK>>*zC`Wkpa_Wc(Oekc4%BvuGlmS(#l^ zV{O*ysK}VnSmee~{>S#fENhaplQ`Fsc}N^8TIRTej?b@3x}0J>;f|2?8=X9CK@kdw zd0h*NP#i2Q@CAv&+=!Ycso!3^I=-<&7=Y8f7EDfB=|FCeQe-sp;AMeDDKcRBfO!RYtQg+mYg;ClZ4iD zWTCUtO8~mSJu3k@QbNWemY+ihA1G+u&f86!r$`{Bb+)Wl*71$u2tDi|2UTgJdxJDr z3PMY{a#m0hXe9OVdnp}BA`=k6(F0$N39yR6^+G3Ns`$PK;{T&ZF71r#j#A78DYgeu+_$LUE>g9?{cU*MSMO54td`U6NSH8hO`@%S9P6s>AxKO^EjQ^le48jh<2 zprnl7$t==WHH~rz38PA7M#z9h;mednKMj6*^(C3TgeWkY003%z9VS72vAn4^CuqE> z|FjYZ=)MyKSTZ2CuTM;6zsmU;-o6hOue+#aruJjYO!c5qM>8~Mq}f^6`B(5lD6mdJ zfl1SQ4}K$zNOSDI743uh7T?p18gxRz2f?TP+rCM4;j{Bve%NqpiyOE#jCs8X`i@}; zejYPY&3b#qJgQO`3{OxeoAM5uu;|yRl_C2pq{PCL5@87rg2D;`%kD9G+J!!~DG|~6 zC%v=-VU^--_jDS>NX6Qf<)m}}{+BObHoxp_7SrMVttU^O+`l9qUga_PTShTMwP_ZT38Y|CRk!rD;Ds*;1kU4fE_ZIdiRwJ{2e~^WB!9QYk zjdQJ2sruCs!|ej6Qqk{>>30#Gt1}9EclL}Fr%Xm^c#SbVo+y>TjpRM^$UAR~;xCf; z>dSwH%tHYd)KWw0Yub4`a-T_ZU#+E<>IeGCp_eS8Ji{BkNWKg`d!F zi4p!dN1R84AM)Y(3MZEaooUstZ$|{hYG%W{!Vl?d)!8$e0ly42zf&0SfcLUR)zW@B zapJC0y56W(4aOGTE_oMA9dqTNcEek6Z|(GZdBGLU;&@kc+cc2^9G1&a?NXZXAJQN4ttZEC)d6EsO5i0uAH z?1^3yDsVfCKjhALT7gV&O@FVGT^d4yIpAd{Kr*DKj*w;rn%SxLQz?+GY_ zFczeyA_D5}s~ONbaq2{+gK7+_;0D4;tgGgVI;?8CK?xFZ)V=Cj;DF0Mi#?6Ar5)zO zC=v3zGbtTPc78V_%0t^+Io&SYLy;zBgw#DCZ_0}ItI!}dJu@@6ZfJA1B`Zz#@}d&9NNQ&qPg00prT2F`+%&rQYi zS}@-Paani(6`qjk%b#8dp&xxgN^Qn#uNcm_49LG5dyoQ@?yZ} zE3PZo+b*0>`WPw?QoH`f07>{E^>`~ef51&dVzKP$$avx77E0b*{HOAk;h;FZbsL zsOx$B_E@{2tX!cF%EP~ zjJ@H94&~AfSUaWma#~xRoL#iY#WOs7s*Xj^luLYlWJ~n5^EI@%(#hJ2ija}&HGlFD zr}T-dn&%VJyN@dMwL0P9v~3SjaqpqLw+y`(A8AXO%(|$P$E~!dtu}elxudLpndW0m zn}M2qkM4YT&4B_c6;WsxTVV-vwKjFNmN&8d*=;`LMk2j|slF+`brri@R=^W0>0R2C z5C$DwT12tGFjU|^%r;Vub&q`h>9(BEO}=%!1y(LDBk0P-#h!Mb6AEqaGd$g%2S*Dv zQ3Z|`_c{dVd89qV9r#xv!N1sy$0F#uV~|XQz6X3lP`bI!u&J+5TLX9UV&Xwn{&8uYD4xyZj8y6i!t4J+4gq{ys~1q1$yb9DOQ^nG&FD^t zG&8aYk_GhT&HcQ;RDu*V#7LS$gW}7U0-o{xDEo4K{RN>n#TT0klRzCzZB$lXtGq@9 z31m}J>^)T|N#qlXypEgDHJ=qzh6+%|S##_4U_gOq9K}ca5iU}gEA^BUNIJ@3EGtNPY^1NAx<-bMVi5T#YHZB%#z!xn3(J>!Q0|=fHWI0ceX>$n~|3i%K&4 zG2GbUnEJ&AG@)D$4mZWBazG#3D5#ti>32x&zY*)3tR2ugaL9~6pGiCug>#&sC)D^; z9ipMw@r1E5bnFEq&oX)jCTC-V{EAWz;0Bk%xBGOPK(+X6Ii&mglQK-o><^{>NEl&< zKfg%`m^o`5mfR8~PbAl*ds|Sf@?Xu?spYuO>+f)B5-9Ob$!TPDfi@Rtdj+_2FXr(T z&wkb?GYBDLn^ks5ON)}4gA#x|VAGe~BPXKJo{o>Q$ob7TgHsUB1&=lHx15^@uRme~ zig96)RbNrXHcL1o0eM+9onY412XU*$89s0sfhURQ{*HP+`JkSk3CShk*WZ-#o;qUQ zdHS+D@w-(BEKLvJit(<94E8=}0_o~AM7%d}nRplGY^cTJxw90V*kQN(=kZEeyZc=3 zhppaoX_tj|zT-WAa`*1@&Auj3+9*c1q*bp{Akf`urKbrh)dG8<5e^iVJCw{sgTrS` zy-ORQjilr)<)Go%%S?n_nbl;9^P*BgH?&}l54DNj&Ektm#ODDplLSKVD0Eg5;(}5J z7>T-9Y=(Kyiy$*CQB>OH0LEvaQcnAFS7qxlfr6(rbKPX*``yN-^5aZQF@i%5{=d-9$S5 zOWDNFsr0zhJVN+=33OAVONMqB7_k0&!kuMZX73&)^X`ZQ-J&gvNqs8W@`k9V1;y61 zEMR~VEwu>@MET#l_L3xcGE=!j1CP91h3C zHZ#It;}N_ZW}L2U)1+n zUci`$nPlbLZQ>@onT`#4NK%FR`N;j;dTbh@9qmH@J<+?C2^gX9&6Po;MDf;LeYOW; z(B;-oLv(+9%;5TClnWIC{z?=^!env^Bk)xvq9 zy?1hki(~bR49Ce@O68=Z&5MFhC_cnYE-%3P89Si`5^k2#bm#(k3KhiD%p4$!wNBBL z+u`!3*m57W&qccSzc|`;W9pB>t$VQll^)bSXHLD;^x)If6N&EFNKG9NTTuWXnTRbf z=*Y#wPeiEa=iGY!f6mG5Tb!Sd%4Z{({E@FJgcW5Wa7DT1aLg%HRU&a?9QKZbKA2pH zpB$a7nGb!2!-pdNH0_!$+df9PQ_u*&H%q7pKTXN|snk(bBK4NH#wBO&F-8as3~%7< z$^Ba*?#z$;1A^9{Zpt5oOdz%!w?MLcckfaW>hV)bMAz!zk}6%*iU%D#YBWaqDz1`O zPrGhFWo;q*{lK!}Y)Q+NoBt}S1@TahRdFm!j=NfHU=E9b9Tu&qp2M=e?1s{V>VsZ;WI3s3+A9JS(3;iH%fB#5#X`%w0bUJb zUTf>MDv*6|^f)v3P^#j93&!h}QFe&aqRYsu?Xt8NLPEHZn9bs7%M-WR9V zCyxUQ`0Y}#_#s=-tul&vEP_`&&IBoF_Ac4*TTJ%>%jMEB*f|0*3#%DEwBKR!-<;CsstdKxen{{sB>?{njeSHKrfHzPkTPOQueBoP8~gH~nH z)eO+S6OT0^-1i6}HsQWlfr`RQ2N;Npewn&m?Z5HhPrR{2AerH;Xa@6D{wbtf#d*nJv31gAO{4JqO!2JMQFmARejZG{a?ZVzN1q<-;_sMrp|qV0)$#ovKd5fh zyF0$fDi4k4o=$6!`i{7p!VkB5!?Re%ZWyHYzDAcg&75yz8p)D`El$pj3{5T{a9l&x z;jyNMe#PIhQ&-5kqaU9j9~*rXYgq*Z-Fj0#T0tUsC4SWnaGSENdHbT8w1`s~I_>zM zXxmP0+vJ|lZ-`3c-2$exf-rp3=RHo<`*Z*k;y25|pqj8sGRSfWZ(7gQq~gFE&7QO2 zA8}mkFhHa=fuiDGZGGS(-lS~)Zs|c{)+b=QFr1XzWYzFlW46bgy;s~BR-EZG?gD%~ zoESDiUva2=eH$aWqFkpbYI&V^hwoBJ)QL2$WalxFr(>bNr{^F}D1zN775henqh}E% zSFjPaw>vX&z&(iANK$tcZFT6N@t$e}LM{&&XN~-neVsu^3}DX5$V{cYYX~$AZ!5Mv zbeR35zg96$ulQawSoj1?9B#OW)xK30P`<)}@M?d8>1a8ylI5wx{XBJ=!i?1Cr=a_) z{Blh5XE`CohmQ5ZN5(yC88aF^z8pJu&@tJv6qO;PmLo;)d%XSlz_JO5$=ls{>3e!y z?+FDY&5#dw0Y~^8BZ)sF4C%B?^0I+2f=&H6R!^aE+V{VGNSA?!F}Gt8TYvuwP=oy_ zf(OhOlmdNUy=^}`$wdn(Uf@QOx->c;JBW{hL8Q@>?HAY^0-D&Q9=x0$un~ZZ`v5sfpvqMC)1(dp&oiTH18Y*R)J-Z{U3tgV!DE#Wl^PpI zU-fk@jnqC5^}e>ae%Ipa_yA2cM_!ehZv_S~?O0kQ*%d z@!^i3=r+m03S${r%a#H^s7C%WyjKHu3lShRG9NyV6pg^pv@3P{N z(n54SRzaWH%2l9PDNgR|T37+gJn_wT!!2hwJPa56A-qRiGneIEf&~>GxbZ#Q_)a)d zy|Z`)e6W3{mI%|;L@G$tVEbpHcg(4gJR+7$V=pMAV70?(#}y{WTDpQ~_?)oE`NARq z34s&QUY1YTU-5oF@9xK_MB|aPgD2BC9p4k?!yi3iA(9V74WR|aZ8i;5$1H6pK z-`Vt-G8KUSbV{EFM!b4om`YgbRPi@ZTkyztF0fy4x*cIB#P+N&d@D}`(pp2Pe;9?J ze8&Flb1V8IAU04swlvfjQ?jC(v#Y6H&`l=oS{H;+@OAzO!4u?PsmWjiN>J5F-ok&O zwKUKPQnYrtxc4a?O5YE^*KQ7rs#Sg=-THEaMClz(b~e=cB*~ria~Ff{%|%Ut<-W?7 zmA8D}OzqKN+MHI>P9QmGnS6;W;3Kq?=3fhdJ9NQsBto!fPqPZtaJV^}rkqC_e`CH1 zMbALma>KISsem$;wG4yWHt$2{o%f!>A1Ctr05h4nAFoite5mv0)P7?;w2&ebW~BUc z+IXIr137e?H14)v_e`O!YUYpCTA#@wRi8U~#a|`Yim6gLOc+;5<&vUdQ7k;DKp3oX zWXWqBS%e+(>!hr<4ThE(iYv9$f2AEyztRrYOWuYiy3Daw$^wae5ZS>#)ML%3`z@$u z#RgNC)v#shr1)+vRSjA}=}VSNmzUhWEZ2uA+z*=k(2}&Tytv@61UE~;<=iyQ`~EcV zJ9)5fn)mCbnX38mGJctqXPd|>2yrI~(!uXR)}hwHSpPJwO%|qha)c zJVk#|&b`rV{Wm9ot)J+hc@e)HHxZnHm^FkajnwlCioJQ7;GefC!HMeIhx!b7LpReR z-n!q8W*W@&Eqgj2D1eq|Pv3pRcmL2=2UKG+!as+)++ji ziVh(CPUkRLXWz`j1V35jBN)uF^MP@!BfG;RD-`b0>g?WDWUY>V8Uy6(|meF~c z=ukfV*exgx>C1%SGJaPc@MPpfh6{En-UKi^1BYZT1w7!jej(=JvoLA`NgBDk9$ zxfTL0d8yG7a%IT=kh!={FGCGxnTC1eVH?XJxqJ8ZnAB{pH!pMFYLn{hB}^oq&RvQ+ zBr&0!P^y_2{7$=NoI2Vqr8K?2y%D#eF1mZKs42rfa#xB5 z%P-J?Y_*I|pxyg;*?cqc(lfL==Yz_<=*wkOfWWHtsfLP1M#Xj+P~b=iW&+F^3`MJq zcN1RXYAUcmw6p$)totDYuz(h<4=3Q?n!k{dl5(mlr%Eh*L)YX6_%h?ML3CKLMhD9K zOT+s)$AAMGKez=bCkKLS`1>5e5BrIAlk5{sPC<*U(`el8G=Gt#xr~1-)zM>-sA<7& zvnlSyjMeTs>W3ka;+HAI^R~>wR+9vufstx75%{s%@w{VMTOQ?xiFwr0Pi=55YxPxw zgu_*ZltyP4o3NCc9!)&6w}Y>w!5K1 zO$%DL9`n-)1CNN5Y8b>*`s87b11u1RD708NgYx0+wAbAcxx<;|Gvvu#B zygc9ywDtY>3v#C*o9W_*lvnA3w0H6UW>-sEqiP%7qhxT+t?l*?MGyaW9z3RB__u@P zKzy@Jzn`cA_|w_RcJLGbc92Pk3*!$;JZgXY<7KnBPrq7JFJ^-9wQoC0#aBk@Va)=m z7h`siRFZf|O{T=s4ah9EdoQ5R9p8`lWhzh8(Z~UVn!R`<5~Ze3A~`5|DDEa2K44w# zPNdMynUxbR(z~g0y>?C6WoPn33r&k(S?5sYk|V#$@uG=pBx3`5kU9NbY0Vcln1r%Y zIbBbnYse)&F1dO^r!jL9+$|k^SRp8)MY`m4rmWUlqqIINn|;DEFiyV!s8Ifo_Ht?D zNE7MnySPv{oFTU5L^{y5+=0tD6}nQ*m9;wfGE~8rQUkA>)b5r}WGF+;x|K1cTg}qE z>gI>HyaxC$u{Gb~;J}}!NfgqYYi1DySwb;paMe-E7%@y0KoH8VnNmd%*i{b%0V8po zpAZ8>KkDu{XLs->i9t5P2Z8RO(p;k?)MqVJkSLz?xc1Ij4#`Hboo z2E+x&N3PB&Fl#2=!0MBmV*ZB>JNuG@W0gVMT7p%cJq%&0AJ)ij~FPO&i`}3JO zMoJtbyo1kY$K?q-8;?lMKF$)PIu{XW-hJmTn`)0~oIkk<=3z)CPtQs|)XJ}#{}5NC zek_*uEudYU&%6{x;!r-&!kPBVrsZSy5lb0ECuN)_(M2N7X^Vh)unGCBJ+0XMolj#g ze~oFD8gb(p4!sp2JK?(w)ACapze6vy2$PLS=~I$2<7w(emq)cpS=0bw61Qj)OQWSS zd~Zj1?Ap{U``GwU@hbr)$F*U4IN@dFyjle+qQ?7l_t@pQs;qpEq{`T|`;pR%>+lmO z%>&&Co7&sjZojo=daoKHM{OZ+i2-)~BfvℑDcukuZ}F&T9zynaO<6*}5bL`U<5fLS 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 ZkPrc}E#i&#Xny^7{~w*7XJPr`1ppR>M-Bi0 diff --git a/frontend/dist/assets/index-BrGvyLIG.js.gz b/frontend/dist/assets/index-BrGvyLIG.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..4b52fd87e6fc5986fb6940ea2089a80243592a8b GIT binary patch literal 125078 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 L-q_0NF7E{Z>_|2( literal 0 HcmV?d00001 diff --git a/frontend/dist/assets/index-DYwzK8Nu.css.gz b/frontend/dist/assets/index-DYwzK8Nu.css.gz new file mode 100644 index 0000000000000000000000000000000000000000..64f4e9f74104d6ba3312bdc7252c5e2658852bd3 GIT binary patch literal 6686 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 literal 0 HcmV?d00001 diff --git a/frontend/dist/index.html.gz b/frontend/dist/index.html.gz index aa468ce7d08d3ee7335ed64c76d59e99e43ae000..fb1dd51a603f604a58fece1d9522d32cdb3622b3 100644 GIT binary patch literal 324 zcmV-K0lWSmiwFP!0000217%S?Ps1<}-TNzyS4a$7hd4ups1S$=1}5a%*Tmr1k?)!| z{CX};tEytL@9Vw0dsp4KWNGX{wA6)qQB9u+CN^3^l)?sssO6?9HjN-SP%%BSUIC4-MYB16#9+r*qxJGML1fWo1c07M zDdo<@O3jfui#Ge7P9t{7?E-MCoDfs?jgJDe&+_b@`35uGCEN zZ+E0I0JZ>hO?X=Tit+Jt|NF9h+m#k^)>@s#t~SYy=d)~Cg91&Qi#X~0AiUEur$o~% W?9cEC#n)BymHz-X&f4qt0RRB@e4bDM literal 320 zcmV-G0l)qqiwFP!0000217%UkPQx$|z4t4OuaFod4jkf0AjGjJ=oPv4)G_rEd4?>1 zkJGfODlYbH@6F7c=FTKda}TVe;M+~Je9Wt8C5OnF_6kw*y{-0^5lmDq4{tY&H619k z7{v@gis?>e1d{yg^P_sIpr(V%7c8WAFpKO$V1!PZiLFB=hT`w8{xFWoKPvm~|PmhysMdMFUgywpGX7ybJXY^0pBx#lPK= zW)Ii_&@JJ4@f*h1?P%5W^j>SkRcmn(yV)l*U(RwU8dRv_T2zw84a^zIb4s+#Qv4OZ SpvrZ{dgVWQtU<;00RRB=tDFG< diff --git a/frontend/src/components/AuthGate.tsx b/frontend/src/components/AuthGate.tsx index 534207791..ed6ba7d81 100644 --- a/frontend/src/components/AuthGate.tsx +++ b/frontend/src/components/AuthGate.tsx @@ -265,6 +265,12 @@ function AuthModal({ onSubmit({ username, password }, remember); }; + // Bloomberg-Terminal sign-in surface: full-page, no modal chrome. + // The whole canvas reads as a single carved console, with the amber + // accent reserved for the primary SSO action and the live status + // line at the bottom. We deliberately avoid the gradient/glass + // cliché — the rest of the SPA never uses it, and an operator + // landing here should feel they are still in the same console. return (
    -
    -

    - Sign in -

    - {ssoConfigError && ( -
    + {/* Amber tick line above the heading: the same accent used on + live cells across the dashboard, anchoring the sign-in + surface to the rest of the SPA. */} + - )} - {ssoProxyUrl && ( -
    - -

    - {ssoAdminPossible - ? "SSO can grant read or admin access depending on your group memberships." - : "SSO grants read-only access including logs and SQL text."} -

    -
    - - or - -
    -
    - )} -
    -

    - {currentBasic - ? "That user/password did not work. Check [general].admin_username and [general].admin_password in pg_doorman.toml." - : "Sign in with the admin_username / admin_password from [general] in pg_doorman.toml."} -

    -
    +
    + + + {ssoProxyUrl ? "sso + basic" : "basic only"} + +
    + +
    + ); +} + +function SsoBlock({ + proxyUrl, + ssoAdminPossible, + redirecting, + onRedirect, +}: { + proxyUrl: string; + ssoAdminPossible: boolean; + redirecting: boolean; + onRedirect: () => void; +}) { + // Trim the proxy host out of the URL so the operator sees where SSO + // will route them before clicking. The try/catch handles a typo'd + // sso_proxy_url at render time — the runtime `safeProxyUrl` check + // only fires when the operator actually clicks the button. + let host: string | null = null; + try { + host = new URL(proxyUrl).host; + } catch { + host = null; + } + return ( +
    +

    + Single sign-on +

    + +

    + {host ? ( + <> + Routes via {host}.{" "} + + ) : null} + {ssoAdminPossible + ? "Group membership in the JWT decides whether you land in read-only Sso or full Admin." + : "SSO grants read-only access including logs and SQL text."} +

    +
    + ); +} + +function BasicBlock({ + currentBasic, + username, + password, + remember, + onUsername, + onPassword, + onRemember, + onSubmit, + ssoVisible, +}: { + currentBasic: { username: string; password: string } | null; + username: string; + password: string; + remember: boolean; + onUsername: (next: string) => void; + onPassword: (next: string) => void; + onRemember: (next: boolean) => void; + onSubmit: (e: FormEvent) => void; + ssoVisible: boolean; +}) { + return ( +
    + {ssoVisible && ( + + )} + {!ssoVisible && ( +

    + Local admin +

    + )} + {currentBasic && ( +

    + That user/password was rejected. Recheck{" "} + [general].admin_username and{" "} + [general].admin_password in{" "} + pg_doorman.toml. +

    + )} + +
    + setUsername(e.target.value)} - className="mb-3 w-full rounded border border-border-strong bg-surface-2 px-2 py-1.5 text-sm text-text" + onChange={(e) => onUsername(e.target.value)} + className="block h-10 w-full border border-border-strong bg-surface-2 px-3 text-sm text-text focus:border-accent focus:outline-none" /> -
    +
    + setPassword(e.target.value)} - className="mb-3 w-full rounded border border-border-strong bg-surface-2 px-2 py-1.5 text-sm text-text" + onChange={(e) => onPassword(e.target.value)} + className="block h-10 w-full border border-border-strong bg-surface-2 px-3 text-sm text-text focus:border-accent focus:outline-none" /> - - - -
    +
    + + +
    ); } + +function TransportChip() { + // Read the live protocol so the operator can tell at a glance + // whether they are about to hand a Bearer JWT to a plain-HTTP + // listener. Falls back to the insecure rendering when `window` is + // not present (SSR / test render) — there is no honest signal in + // that context, and "http" is the safer default for a chip that + // exists to warn about insecure transport. + const protocol = + typeof window !== "undefined" ? window.location.protocol : ""; + const secure = protocol === "https:"; + const className = secure + ? "border-success/40 text-success" + : "border-warning/40 text-warning"; + return ( + + + transport · {secure ? "https" : "http"} + + ); +} From dd09d3630f7f8a65d4475ac67d28a5f0bb75489b Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 15:03:24 +0300 Subject: [PATCH 68/72] chore(gitignore): ignore nested cargo target dirs and internal-plans/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/target` only anchored to the repo root, so `cargo build` inside `patches/openssl-src/` left `target/debug/.cargo-lock` showing up as untracked. Replace with `target/` so any cargo build directory under the tree is ignored. Also ignore `/docs/internal-plans/` alongside the existing `/docs/superpowers/` exclusion — same role: private session-scoped planning notes that should not enter public history. --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d35026c8d..ec19ade37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .idea .junie .claude -/target +target/ .DS_Store .direnv .pre-commit-config.yaml @@ -60,6 +60,9 @@ flamegraph-output/ # Superpowers spec drafts (private session artifacts) /docs/superpowers/ +# Internal planning notes (private session artifacts) +/docs/internal-plans/ + # Per-session agent handoff scratchpads — not part of public history. .local/ From b6463d396d2ad80839e4544d5695501a1bcb7a76 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 15:28:43 +0300 Subject: [PATCH 69/72] fix(web): allow http sso_proxy_url so SSO works behind a TLS-terminating proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend redirect helper rejected any sso_proxy_url that did not start with `https://` (with a localhost escape hatch), logged `sso_proxy_url must use https` to the console, and left the "Sign in via SSO" button doing nothing. That contradicts the supported deployment shape where the corporate TLS-terminating proxy advertises an `http://` URL pointing at pg_doorman's own internal address, with TLS terminated upstream. Drop the protocol gate from `safeProxyUrl`. The URL still has to parse so a typo in `pg_doorman.toml` keeps showing in devtools; choosing http vs https stays with the operator. Backend `[web].sso_require_https` (default false) remains the explicit opt-in for environments that want pg_doorman to refuse plain-HTTP SSO tokens — that's the right place to enforce transport policy. --- frontend/dist/.source-hash | 2 +- frontend/dist/assets/index-BPAqXFlE.js.gz | Bin 0 -> 125002 bytes frontend/dist/assets/index-BrGvyLIG.js.gz | Bin 125078 -> 0 bytes frontend/dist/index.html.gz | Bin 324 -> 325 bytes frontend/src/lib/sso.ts | 25 ++++++---------------- 5 files changed, 8 insertions(+), 19 deletions(-) create mode 100644 frontend/dist/assets/index-BPAqXFlE.js.gz delete mode 100644 frontend/dist/assets/index-BrGvyLIG.js.gz diff --git a/frontend/dist/.source-hash b/frontend/dist/.source-hash index 98d1a181d..30b03cc7b 100644 --- a/frontend/dist/.source-hash +++ b/frontend/dist/.source-hash @@ -1 +1 @@ -9959de7ac28c52ce3f3366402820ad229cbe716c3717e8a794a579f7aa39dc31 +c354cdb48204d35e96a6e6ea90ef3743de01fc3841c0516829bb812c40c924c8 diff --git a/frontend/dist/assets/index-BPAqXFlE.js.gz b/frontend/dist/assets/index-BPAqXFlE.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..51d504faccf3b636c3d8266abe4d7a34491e9add GIT binary patch literal 125002 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 #?w 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 L-q_0NF7E{Z>_|2( diff --git a/frontend/dist/index.html.gz b/frontend/dist/index.html.gz index fb1dd51a603f604a58fece1d9522d32cdb3622b3..5ecf0ea58a664c41dfd729b21335e8e0556a218f 100644 GIT binary patch literal 325 zcmV-L0lNMliwFP!0000217%T7Ps1<_z3*3qT%irzc4#VrU>79B0VFt~Eb-bvnv~e< zHhw+sx@nqrN&NMm?dR&sB}=;=L`z+$XVv(LU}B>sL@8`Ah*~b2V%Z3SEfwP<>zNQ$ z3&xEGn;no5wlx`nw0wVC6mu!mAsKx95gCG(-X`W8+p*o51{4m>^|lLAv>8@fs+AeW zqLSk$f<1dQ9is*hn?7aY$x6>wb}s#q1Zb&!beZugaWMzpzq4t6B&65Q1{K3 zODT6IR%(h&S+v>rbQrNqZXJMI<%F2BZ+sM(Esx2f0HN~H!M1pKz5o4Kgs1WwWnHO> z;$nBCT>xwW=$!Dd_!;Bl=jQKu{<1DD;-s}Yid`*}+a1rcVhsv3aVp}Z^MmkC%bXI8 Xv#>wGM-*RIO;`R0bp;pP_5lC@5SEuj literal 324 zcmV-K0lWSmiwFP!0000217%S?Ps1<}-TNzyS4a$7hd4ups1S$=1}5a%*Tmr1k?)!| z{CX};tEytL@9Vw0dsp4KWNGX{wA6)qQB9u+CN^3^l)?sssO6?9HjN-SP%%BSUIC4-MYB16#9+r*qxJGML1fWo1c07M zDdo<@O3jfui#Ge7P9t{7?E-MCoDfs?jgJDe&+_b@`35uGCEN zZ+E0I0JZ>hO?X=Tit+Jt|NF9h+m#k^)>@s#t~SYy=d)~Cg91&Qi#X~0AiUEur$o~% W?9cEC#n)BymHz-X&f4qt0RRB@e4bDM diff --git a/frontend/src/lib/sso.ts b/frontend/src/lib/sso.ts index e99b420d3..e81672521 100644 --- a/frontend/src/lib/sso.ts +++ b/frontend/src/lib/sso.ts @@ -59,10 +59,12 @@ export function captureTokenFromUrl(): string | null { /** * Send the user agent to the SSO proxy with the current href as - * redirect target. Validates the proxy URL: must parse, must use https - * (or be localhost for development). A bad URL logs to the console - * and aborts the redirect, so a typo in `pg_doorman.toml` shows in - * devtools instead of leaving the SPA stuck on a half-redirect. + * redirect target. Validates that the URL parses; protocol choice is + * left to the operator (an internal HTTPS-terminating proxy reaching + * pg_doorman over a private HTTP leg is a supported deployment, see + * [web].sso_require_https). A non-parseable URL logs to the console + * and aborts so a typo in `pg_doorman.toml` shows in devtools + * instead of leaving the SPA stuck on a half-redirect. * * Returns `true` when navigation was scheduled, `false` when the URL * was rejected — the caller can use this to clear a "Redirecting…" @@ -77,25 +79,12 @@ export function redirectToSso(proxyUrl: string): boolean { } function safeProxyUrl(proxyUrl: string): URL | null { - let url: URL; try { - url = new URL(proxyUrl); + return new URL(proxyUrl); } catch { console.error("sso_proxy_url is not a valid URL:", proxyUrl); return null; } - const isLocal = url.hostname === "localhost" || url.hostname === "127.0.0.1"; - if (url.protocol !== "https:" && !isLocal) { - console.error( - "sso_proxy_url must use https (got", - url.protocol, - "for", - url.hostname, - ")", - ); - return null; - } - return url; } interface SsoTokenMessage { From e0b0f468eaa74586d23f1fc4f8205914df4685da Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 16:00:39 +0300 Subject: [PATCH 70/72] ci(bdd): bump cargo retry to 3 with 30s wait so DNS flakes settle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two retries (max_attempts: 2) were not enough to recover from a GitHub Actions DNS outage to `index.crates.io` that lasted longer than cargo's own ~25-second network retry loop, draining both attempts in the same window (run #25734696524, Patroni proxy job failed twice in a row on `Could not resolve host: index.crates.io`). Three attempts plus a 30-second wait between them gives the DNS outage time to clear before the next attempt enters cargo's network loop. The third attempt only fires when both prior ones failed, so the timing-sensitive flake family (SCRAM passthrough reconnect, sleep-based lifecycle waits) does not pay an extra minute on single-attempt successes. Also drop the now-incorrect "no retry around the cargo step" header comment — retry has covered that step for several runs already. --- .github/workflows/bdd-tests.yml | 39 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/bdd-tests.yml b/.github/workflows/bdd-tests.yml index 49c982f43..9633e131e 100644 --- a/.github/workflows/bdd-tests.yml +++ b/.github/workflows/bdd-tests.yml @@ -204,13 +204,10 @@ jobs: # Single matrixed job replaces the 21 nearly-identical per-language / # per-suite jobs that used to live here. Each suite differs only in - # the cargo command it runs; share everything else. - # - # Why no `nick-fields/retry` around the cargo step: the legacy jobs - # wrapped `cargo test` in max_attempts: 2, which masks intermittent - # BDD failures rather than surfaces them. Retry stays on the network - # `docker pull` step, where it covers GHCR manifest visibility lag - # right after a fresh push (the typical real-world flake). + # the cargo command it runs; share everything else. See the cargo + # step below for the retry policy and why three attempts beat two + # on the dominant GHA flake families (DNS to crates.io, timing- + # sensitive lifecycle scenarios). bdd-tests: name: 'BDD: ${{ matrix.suite.name }}' needs: prepare-tests @@ -276,18 +273,28 @@ jobs: command: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-tests.outputs.image-tag }} - name: Run BDD suite (${{ matrix.suite.name }}) - # Retry once on failure. Timing-sensitive scenarios (SCRAM - # passthrough reconnect after retain, sleep-based lifecycle - # waits) occasionally lose their margin under cross-job - # contention on GitHub-hosted runners — a second attempt on - # the same image with the same workspace is cheaper than - # bisecting a phantom regression. Each attempt builds and - # runs ~1-3 min, so retry caps wall-clock at ~6 min worst case. + # Three attempts cover two distinct flake families that hit BDD + # jobs on GitHub-hosted runners: + # + # 1. Timing-sensitive scenarios (SCRAM passthrough reconnect + # after retain, sleep-based lifecycle waits) occasionally + # lose their margin under cross-job contention. + # 2. Transient DNS resolution failures on `index.crates.io` + # can take both attempts to clear when only two were + # allowed — cargo's own three-retry network loop runs over + # ~25 seconds, so an outage longer than that drains both + # attempts (see run #25734696524, Patroni proxy job). + # + # A 30-second wait between attempts gives the DNS flake time to + # settle without doubling the wall-clock for the timing-flake + # case (the third attempt only runs when the first two failed). + # Each attempt builds and runs ~1-3 min, so worst-case wall is + # still inside the 30-minute timeout. uses: nick-fields/retry@v3 with: timeout_minutes: 30 - max_attempts: 2 - retry_wait_seconds: 5 + max_attempts: 3 + retry_wait_seconds: 30 command: | docker run --rm \ -v ${{ github.workspace }}:/workspace \ From 5b7758608779feda474eaaee66c4c1cb5fee0ed2 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 16:19:16 +0300 Subject: [PATCH 71/72] ci(bdd): share host network and widen cargo retry so DNS isolation cannot wedge a job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bumped outer retry from the previous commit was treating a symptom: the BDD container kept failing with `Could not resolve host: index.crates.io` because the default docker bridge network does not always re-export the runner's systemd-resolved stub at 127.0.0.53. The container's resolver was simply broken for the whole job, so neither cargo's own internal retries nor the outer attempt-level retries had a path out — all three of cargo's tries kept hitting the same dead resolver in quick succession. Run the BDD container with `--network=host` so it inherits the runner's working `/etc/resolv.conf` directly, bypassing the bridge resolver entirely. Each matrix entry runs on its own ephemeral runner, so sharing the host network does not create loopback-port collisions between suites. While here, raise the cargo-internal envelope for the residual case where DNS works but crates.io rate-limits or returns 5xx mid-fetch: `CARGO_NET_RETRY=10` (default 2) gives cargo five times more attempts on its own network loop, and `CARGO_HTTP_TIMEOUT=60` (default 30 s) doubles the per-attempt ceiling. The outer 3-attempt retry stays as the last-resort safety net for whole-runner flakes. --- .github/workflows/bdd-tests.yml | 41 +++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/.github/workflows/bdd-tests.yml b/.github/workflows/bdd-tests.yml index 9633e131e..638471731 100644 --- a/.github/workflows/bdd-tests.yml +++ b/.github/workflows/bdd-tests.yml @@ -273,23 +273,31 @@ jobs: command: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-tests.outputs.image-tag }} - name: Run BDD suite (${{ matrix.suite.name }}) - # Three attempts cover two distinct flake families that hit BDD - # jobs on GitHub-hosted runners: + # `--network=host` is the load-bearing flag. The previous run + # of this step used the default bridge network: the container + # inherits DNS from the docker daemon, which on a GitHub-hosted + # runner does not always re-export the host's systemd-resolved + # stub at 127.0.0.53. When that bridge DNS path is wedged, + # every cargo attempt inside the container fails with + # `Could not resolve host: index.crates.io` even though the + # host runner itself can reach crates.io fine. Sharing the + # host's network stack short-circuits the bridge resolver, + # which is the real root cause; `--network=host` is also safe + # here because each matrix entry runs on its own ephemeral + # runner, so no two BDD suites contend for the same loopback + # ports. # - # 1. Timing-sensitive scenarios (SCRAM passthrough reconnect - # after retain, sleep-based lifecycle waits) occasionally - # lose their margin under cross-job contention. - # 2. Transient DNS resolution failures on `index.crates.io` - # can take both attempts to clear when only two were - # allowed — cargo's own three-retry network loop runs over - # ~25 seconds, so an outage longer than that drains both - # attempts (see run #25734696524, Patroni proxy job). + # `CARGO_NET_RETRY=10` and `CARGO_HTTP_TIMEOUT=60` upgrade the + # cargo-internal retry loop for the residual case where DNS is + # fine but `index.crates.io` itself rate-limits / 5xx's mid- + # download. The defaults are 2 retries / 30 s timeout, which + # gives cargo about 25 s of attempts before it gives up — + # the new values triple that headroom. # - # A 30-second wait between attempts gives the DNS flake time to - # settle without doubling the wall-clock for the timing-flake - # case (the third attempt only runs when the first two failed). - # Each attempt builds and runs ~1-3 min, so worst-case wall is - # still inside the 30-minute timeout. + # `max_attempts: 3` plus a 30 s wait between them is the + # outer safety net for the rare case where the entire host + # runner is mid-flake. The third attempt only fires when the + # first two failed, so single-attempt successes pay nothing. uses: nick-fields/retry@v3 with: timeout_minutes: 30 @@ -297,6 +305,9 @@ jobs: retry_wait_seconds: 30 command: | docker run --rm \ + --network=host \ + -e CARGO_NET_RETRY=10 \ + -e CARGO_HTTP_TIMEOUT=60 \ -v ${{ github.workspace }}:/workspace \ -w /workspace \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-tests.outputs.image-tag }} \ From 19313b25b121f8266fc3a025640ecc6c31f74961 Mon Sep 17 00:00:00 2001 From: dmitrivasilyev Date: Tue, 12 May 2026 16:20:51 +0300 Subject: [PATCH 72/72] =?UTF-8?q?ci(bdd):=20revert=20outer=20retry=20bump?= =?UTF-8?q?=20=E2=80=94=20DNS=20isolation=20is=20the=20real=20fix,=20not?= =?UTF-8?q?=20waiting=20longer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous attempt bumped the outer step retry to 3×30 s on the assumption that the second attempt would land outside a transient DNS outage. Observation: when this fails, exactly one job in the matrix fails — the others on different ephemeral runners succeed at the same wall time. That rules out a fleet-wide DNS flake and points at a single runner whose bridge resolver is wedged for the whole job. A 30 s wait does not unwedge a dead resolver, so the extra attempts only stretch the failed wall time without changing the outcome. Restore the outer retry to the previous 2×5 s policy that exists only for the timing-sensitive BDD flake family (SCRAM passthrough reconnect, sleep-based lifecycle waits). The network defence now lives in two narrower places: `--network=host` short-circuits the bridge resolver so the container talks to the host's systemd- resolved stub directly, and `CARGO_NET_RETRY=10` / `CARGO_HTTP_TIMEOUT=60` widen cargo's own retry loop for residual crates.io flakes that arrive after DNS resolves cleanly. --- .github/workflows/bdd-tests.yml | 52 +++++++++++++++++---------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/.github/workflows/bdd-tests.yml b/.github/workflows/bdd-tests.yml index 638471731..f2298bc36 100644 --- a/.github/workflows/bdd-tests.yml +++ b/.github/workflows/bdd-tests.yml @@ -273,36 +273,38 @@ jobs: command: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-tests.outputs.image-tag }} - name: Run BDD suite (${{ matrix.suite.name }}) - # `--network=host` is the load-bearing flag. The previous run - # of this step used the default bridge network: the container - # inherits DNS from the docker daemon, which on a GitHub-hosted - # runner does not always re-export the host's systemd-resolved - # stub at 127.0.0.53. When that bridge DNS path is wedged, - # every cargo attempt inside the container fails with - # `Could not resolve host: index.crates.io` even though the - # host runner itself can reach crates.io fine. Sharing the - # host's network stack short-circuits the bridge resolver, - # which is the real root cause; `--network=host` is also safe - # here because each matrix entry runs on its own ephemeral - # runner, so no two BDD suites contend for the same loopback - # ports. + # `--network=host` is the load-bearing flag. With the default + # bridge network, the container inherits DNS from the docker + # daemon, which on a GitHub-hosted runner does not always + # re-export the host's systemd-resolved stub at 127.0.0.53. + # When the bridge resolver is wedged for a job, every cargo + # attempt inside the container fails with + # `Could not resolve host: index.crates.io` and stays wedged + # for minutes — both cargo's own retries and an outer step + # retry hit the same dead resolver. Outer retries do not + # rescue that case (we tried 3×30 s in a previous commit and + # the job still drained all three attempts on the wedged + # resolver). Sharing the host's network stack short-circuits + # the bridge resolver entirely; this is safe because each + # matrix entry runs on its own ephemeral runner, so no two + # BDD suites contend for the same loopback ports. # - # `CARGO_NET_RETRY=10` and `CARGO_HTTP_TIMEOUT=60` upgrade the - # cargo-internal retry loop for the residual case where DNS is - # fine but `index.crates.io` itself rate-limits / 5xx's mid- - # download. The defaults are 2 retries / 30 s timeout, which - # gives cargo about 25 s of attempts before it gives up — - # the new values triple that headroom. + # `CARGO_NET_RETRY=10` (default 2) and `CARGO_HTTP_TIMEOUT=60` + # (default 30 s) widen cargo's internal network retry loop for + # the residual case where the host's own resolver works but + # `index.crates.io` itself rate-limits or 5xx's mid-fetch. # - # `max_attempts: 3` plus a 30 s wait between them is the - # outer safety net for the rare case where the entire host - # runner is mid-flake. The third attempt only fires when the - # first two failed, so single-attempt successes pay nothing. + # The outer 2-attempt retry remains for the timing-sensitive + # BDD flake family (SCRAM passthrough reconnect after retain, + # sleep-based lifecycle waits) that occasionally loses its + # margin under cross-job contention. It is not a network + # safety net — that responsibility moved to `--network=host` + # plus the cargo env vars above. uses: nick-fields/retry@v3 with: timeout_minutes: 30 - max_attempts: 3 - retry_wait_seconds: 30 + max_attempts: 2 + retry_wait_seconds: 5 command: | docker run --rm \ --network=host \