From d22dce2e31093205d99cdc4aaff68499f8b6b749 Mon Sep 17 00:00:00 2001 From: John Spray Date: Sun, 19 Nov 2023 15:21:16 +0100 Subject: [PATCH] pageserver: shut down idle walredo processes (#5877) The longer a pageserver runs, the more walredo processes it accumulates from tenants that are touched intermittently (e.g. by availability checks). This can lead to getting OOM killed. Changes: - Add an Instant recording the last use of the walredo process for a tenant - After compaction iteration in the background task, check for idleness and stop the walredo process if idle for more than 10x compaction period. Cc: #3620 Co-authored-by: Joonas Koivunen Co-authored-by: Shany Pozin --- pageserver/src/tenant.rs | 10 ++++++++++ pageserver/src/tenant/tasks.rs | 4 ++++ pageserver/src/walredo.rs | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/pageserver/src/tenant.rs b/pageserver/src/tenant.rs index 4025e93f66..04fe9db76a 100644 --- a/pageserver/src/tenant.rs +++ b/pageserver/src/tenant.rs @@ -291,6 +291,16 @@ impl From for WalRedoManager { } impl WalRedoManager { + pub(crate) fn maybe_quiesce(&self, idle_timeout: Duration) { + match self { + Self::Prod(mgr) => mgr.maybe_quiesce(idle_timeout), + #[cfg(test)] + Self::Test(_) => { + // Not applicable to test redo manager + } + } + } + pub async fn request_redo( &self, key: crate::repository::Key, diff --git a/pageserver/src/tenant/tasks.rs b/pageserver/src/tenant/tasks.rs index 381d731b79..e59001297c 100644 --- a/pageserver/src/tenant/tasks.rs +++ b/pageserver/src/tenant/tasks.rs @@ -198,6 +198,10 @@ async fn compaction_loop(tenant: Arc, cancel: CancellationToken) { warn_when_period_overrun(started_at.elapsed(), period, BackgroundLoopKind::Compaction); + // Perhaps we did no work and the walredo process has been idle for some time: + // give it a chance to shut down to avoid leaving walredo process running indefinitely. + tenant.walredo_mgr.maybe_quiesce(period * 10); + // Sleep if tokio::time::timeout(sleep_duration, cancel.cancelled()) .await diff --git a/pageserver/src/walredo.rs b/pageserver/src/walredo.rs index ccdf621c30..9290940acf 100644 --- a/pageserver/src/walredo.rs +++ b/pageserver/src/walredo.rs @@ -91,6 +91,7 @@ struct ProcessOutput { pub struct PostgresRedoManager { tenant_id: TenantId, conf: &'static PageServerConf, + last_redo_at: std::sync::Mutex>, redo_process: RwLock>>, } @@ -187,10 +188,26 @@ impl PostgresRedoManager { PostgresRedoManager { tenant_id, conf, + last_redo_at: std::sync::Mutex::default(), redo_process: RwLock::new(None), } } + /// This type doesn't have its own background task to check for idleness: we + /// rely on our owner calling this function periodically in its own housekeeping + /// loops. + pub(crate) fn maybe_quiesce(&self, idle_timeout: Duration) { + if let Ok(g) = self.last_redo_at.try_lock() { + if let Some(last_redo_at) = *g { + if last_redo_at.elapsed() >= idle_timeout { + drop(g); + let mut guard = self.redo_process.write().unwrap(); + *guard = None; + } + } + } + } + /// /// Process one request for WAL redo using wal-redo postgres /// @@ -205,6 +222,8 @@ impl PostgresRedoManager { wal_redo_timeout: Duration, pg_version: u32, ) -> anyhow::Result { + *(self.last_redo_at.lock().unwrap()) = Some(Instant::now()); + let (rel, blknum) = key_to_rel_block(key).context("invalid record")?; const MAX_RETRY_ATTEMPTS: u32 = 1; let mut n_attempts = 0u32;