Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .schema/pgdog.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"resharding_parallel_copies": 1,
"rollback_timeout": 5000,
"server_lifetime": 86400000,
"server_lifetime_jitter": 0,
"shutdown_termination_timeout": null,
"shutdown_timeout": 60000,
"stats_period": 15000,
Expand Down Expand Up @@ -476,6 +477,15 @@
"format": "uint64",
"minimum": 0
},
"server_lifetime_jitter": {
"description": "Overrides the `server_lifetime_jitter` setting for this database.\n\nhttps://docs.pgdog.dev/configuration/pgdog.toml/databases/#server_lifetime_jitter",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"shard": {
"description": "The shard number for this database. Only required if your database contains more than one shard. Shard numbers start at 0.\n\nhttps://docs.pgdog.dev/configuration/pgdog.toml/databases/#shard",
"type": "integer",
Expand Down Expand Up @@ -1016,6 +1026,13 @@
"default": 86400000,
"minimum": 0
},
"server_lifetime_jitter": {
"description": "Maximum random adjustment applied to `server_lifetime` per backend\nconnection, in milliseconds. Each connection's effective lifetime\nis sampled uniformly from `[server_lifetime - jitter,\nserver_lifetime + jitter]` once at creation time, breaking up\nsynchronized cohorts that would otherwise expire together.\n\n_Default:_ `0` (no jitter; existing behavior).\n\nhttps://docs.pgdog.dev/configuration/pgdog.toml/general/#server_lifetime_jitter",
"type": "integer",
"format": "uint64",
"default": 0,
"minimum": 0
},
"shutdown_termination_timeout": {
"description": "How long to wait for active connections to be forcibly terminated after `shutdown_timeout` expires.\n\n**Note:** If set, PgDog will send `CANCEL` requests to PostgreSQL for any remaining active queries before tearing down connection pools.\n\nhttps://docs.pgdog.dev/configuration/pgdog.toml/general/#shutdown_termination_timeout",
"type": [
Expand Down
9 changes: 9 additions & 0 deletions .schema/users.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,15 @@
"format": "uint64",
"minimum": 0
},
"server_lifetime_jitter": {
"description": "Maximum random adjustment applied to `server_lifetime` per backend connection (milliseconds).\nOverrides the database-level and general-level `server_lifetime_jitter` setting for this user.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"server_password": {
"description": "Which password to connect with when creating backend connections from PgDog to PostgreSQL. By default, the password configured in `password` is used. This setting allows you to override this configuration and use a different password, decoupling server passwords from user passwords given to clients.\n\n**Note:** Values specified in `pgdog.toml` take priority over this configuration.\n\nhttps://docs.pgdog.dev/configuration/users.toml/users/#server_password",
"type": [
Expand Down
4 changes: 4 additions & 0 deletions pgdog-config/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ pub struct Database {
///
/// https://docs.pgdog.dev/configuration/pgdog.toml/databases/#server_lifetime
pub server_lifetime: Option<u64>,
/// Overrides the `server_lifetime_jitter` setting for this database.
///
/// https://docs.pgdog.dev/configuration/pgdog.toml/databases/#server_lifetime_jitter
pub server_lifetime_jitter: Option<u64>,
/// Used for resharding only; this database will not serve regular traffic.
#[serde(default)]
pub resharding_only: bool,
Expand Down
17 changes: 17 additions & 0 deletions pgdog-config/src/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,18 @@ pub struct General {
#[serde(default = "General::server_lifetime")]
pub server_lifetime: u64,

/// Maximum random adjustment applied to `server_lifetime` per backend
/// connection, in milliseconds. Each connection's effective lifetime
/// is sampled uniformly from `[server_lifetime - jitter,
/// server_lifetime + jitter]` once at creation time, breaking up
/// synchronized cohorts that would otherwise expire together.
///
/// _Default:_ `0` (no jitter; existing behavior).
///
/// https://docs.pgdog.dev/configuration/pgdog.toml/general/#server_lifetime_jitter
#[serde(default = "General::server_lifetime_jitter")]
pub server_lifetime_jitter: u64,

/// How many transactions can wait while the mirror database processes previous requests.
///
/// _Default:_ `128`
Expand Down Expand Up @@ -787,6 +799,7 @@ impl Default for General {
Self::two_phase_commit_wal_checkpoint_interval(),
expanded_explain: Self::expanded_explain(),
server_lifetime: Self::server_lifetime(),
server_lifetime_jitter: Self::server_lifetime_jitter(),
stats_period: Self::stats_period(),
connection_recovery: Self::connection_recovery(),
client_connection_recovery: Self::client_connection_recovery(),
Expand Down Expand Up @@ -1209,6 +1222,10 @@ impl General {
)
}

pub fn server_lifetime_jitter() -> u64 {
Self::env_or_default("PGDOG_SERVER_LIFETIME_JITTER", 0)
}

pub fn connection_recovery() -> ConnectionRecovery {
Self::env_enum_or_default("PGDOG_CONNECTION_RECOVERY")
}
Expand Down
8 changes: 7 additions & 1 deletion pgdog-config/src/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ impl From<&Url> for Database {
database.server_lifetime = Some(lifetime);
}
}
"server_lifetime_jitter" => {
if let Ok(jitter) = val.parse::<u64>() {
database.server_lifetime_jitter = Some(jitter);
}
}
_ => {}
}
}
Expand Down Expand Up @@ -183,14 +188,15 @@ mod test {

#[test]
fn test_numeric_fields_from_query_params() {
let url = Url::parse("postgres://user:password@host:5432/name?pool_size=10&min_pool_size=2&statement_timeout=5000&idle_timeout=300&server_lifetime=3600").unwrap();
let url = Url::parse("postgres://user:password@host:5432/name?pool_size=10&min_pool_size=2&statement_timeout=5000&idle_timeout=300&server_lifetime=3600&server_lifetime_jitter=600").unwrap();
let database = Database::from(&url);

assert_eq!(database.pool_size, Some(10));
assert_eq!(database.min_pool_size, Some(2));
assert_eq!(database.statement_timeout, Some(5000));
assert_eq!(database.idle_timeout, Some(300));
assert_eq!(database.server_lifetime, Some(3600));
assert_eq!(database.server_lifetime_jitter, Some(600));
}

#[test]
Expand Down
3 changes: 3 additions & 0 deletions pgdog-config/src/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ pub struct User {
pub two_phase_commit_auto: Option<bool>,
/// Server connections older than this (in milliseconds) will be closed when returned to the pool.
pub server_lifetime: Option<u64>,
/// Maximum random adjustment applied to `server_lifetime` per backend connection (milliseconds).
/// Overrides the database-level and general-level `server_lifetime_jitter` setting for this user.
pub server_lifetime_jitter: Option<u64>,
}

impl User {
Expand Down
6 changes: 6 additions & 0 deletions pgdog-stats/src/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,11 @@ pub struct Config {
pub connect_attempt_delay: Duration,
/// How long a connection can be open.
pub max_age: Duration,
/// Maximum random adjustment applied to `max_age` per connection.
/// Each connection samples a per-connection offset uniformly from
/// `[-max_age_jitter, +max_age_jitter]` once at creation, breaking
/// up synchronized retirement of cohorts that connect together.
pub max_age_jitter: Duration,
/// Can this pool be banned from serving traffic?
pub bannable: bool,
/// Healtheck timeout.
Expand Down Expand Up @@ -349,6 +354,7 @@ impl Default for Config {
connect_attempts: 1,
connect_attempt_delay: Duration::from_millis(10),
max_age: Duration::from_millis(24 * 3600 * 1000),
max_age_jitter: Duration::ZERO,
bannable: true,
healthcheck_timeout: Duration::from_millis(5_000),
healthcheck_interval: Duration::from_millis(30_000),
Expand Down
45 changes: 45 additions & 0 deletions pgdog/src/backend/pool/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ impl Config {
user.server_lifetime
.unwrap_or(database.server_lifetime.unwrap_or(general.server_lifetime)),
),
max_age_jitter: Duration::from_millis(
user.server_lifetime_jitter.unwrap_or(
database
.server_lifetime_jitter
.unwrap_or(general.server_lifetime_jitter),
),
),
healthcheck_interval: Duration::from_millis(general.healthcheck_interval),
idle_healthcheck_interval: Duration::from_millis(general.idle_healthcheck_interval),
idle_healthcheck_delay: Duration::from_millis(general.idle_healthcheck_delay),
Expand Down Expand Up @@ -185,6 +192,7 @@ mod test {
pool_size: Some(5),
min_pool_size: Some(5),
server_lifetime: Some(5),
server_lifetime_jitter: Some(1),
statement_timeout: Some(5),
pooler_mode: Some(PoolerMode::Session),
idle_timeout: Some(5),
Expand All @@ -196,6 +204,7 @@ mod test {
pool_size: Some(10),
min_pool_size: Some(10),
server_lifetime: Some(10),
server_lifetime_jitter: Some(2),
statement_timeout: Some(10),
pooler_mode: Some(PoolerMode::Transaction),
idle_timeout: Some(10),
Expand All @@ -208,12 +217,48 @@ mod test {
assert_eq!(5, config.max);
assert_eq!(5, config.min);
assert_eq!(Duration::from_millis(5), config.max_age);
assert_eq!(Duration::from_millis(1), config.max_age_jitter);
assert_eq!(Some(Duration::from_millis(5)), config.statement_timeout);
assert_eq!(PoolerMode::Session, config.pooler_mode);
assert_eq!(Duration::from_millis(5), config.idle_timeout);
assert!(config.read_only);
}

#[test]
fn test_jitter_falls_through_general_to_database_to_user() {
let general = General {
server_lifetime_jitter: 100,
..General::default()
};

// Only general set: pool inherits the general value.
let cfg = Config::new(&general, &Database::default(), &User::default(), false);
assert_eq!(Duration::from_millis(100), cfg.max_age_jitter);

// Database overrides general.
let database = Database {
server_lifetime_jitter: Some(200),
..Default::default()
};
let cfg = Config::new(&general, &database, &User::default(), false);
assert_eq!(Duration::from_millis(200), cfg.max_age_jitter);

// User overrides both.
let user = User {
server_lifetime_jitter: Some(300),
..Default::default()
};
let cfg = Config::new(&general, &database, &user, false);
assert_eq!(Duration::from_millis(300), cfg.max_age_jitter);
}

#[test]
fn test_jitter_default_is_zero() {
let general = General::default();
let cfg = Config::new(&general, &Database::default(), &User::default(), false);
assert_eq!(Duration::ZERO, cfg.max_age_jitter);
}

#[test]
fn test_role_primary_disables_role_detection() {
let general = General::default();
Expand Down
6 changes: 3 additions & 3 deletions pgdog/src/backend/pool/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,12 @@ impl Inner {
/// Close connections that have exceeded the max age.
#[inline]
pub(crate) fn close_old(&mut self, now: Instant) -> usize {
let max_age = self.config.max_age;
let base_max_age = self.config.max_age;
let mut removed = 0;

self.idle_connections.retain_mut(|c| {
let age = c.age(now);
let keep = age < max_age;
let keep = age < c.effective_max_age(base_max_age);
Comment thread
levkk marked this conversation as resolved.
if !keep {
removed += 1;
}
Expand Down Expand Up @@ -354,7 +354,7 @@ impl Inner {
}

// Close connections exceeding max age.
if server.age(now) >= self.config.max_age {
if server.age(now) >= server.effective_max_age(self.config.max_age) {
server.disconnect_reason(DisconnectReason::Old);
return Ok(result);
}
Expand Down
6 changes: 5 additions & 1 deletion pgdog/src/backend/pool/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,21 +337,25 @@ impl Monitor {
let mut error = Error::ServerError;
let now = Instant::now();

let max_age = pool.config().max_age;
let max_age_jitter = pool.config().max_age_jitter;

for attempt in 0..connect_attempts {
match timeout(
connect_timeout,
Server::connect(pool.addr(), options.clone(), reason),
)
.await
{
Ok(Ok(conn)) => {
Ok(Ok(mut conn)) => {
let elapsed = now.elapsed();
{
let mut guard = pool.lock();
guard.stats.counts.connect_count += 1;
guard.stats.counts.connect_time += elapsed;
guard.stats.counts.auth_attempts += conn.password_attempts();
}
conn.apply_lifetime_jitter(max_age, max_age_jitter);
return Ok(conn);
}

Expand Down
Loading
Loading