From 65b4f4a70828af15bf7f0db493f00658c5d59e11 Mon Sep 17 00:00:00 2001 From: Igor Ohrimenko Date: Fri, 24 Jul 2026 13:06:19 +0300 Subject: [PATCH 1/3] feat(config): add jemalloc_background_thread option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `[general]` option (also PGDOG_JEMALLOC_BACKGROUND_THREAD env), default off, that enables jemalloc's background purge threads at startup via tikv-jemalloc-ctl — equivalent to _RJEM_MALLOC_CONF=background_thread:true, so freed memory is returned to the OS after allocation bursts without requiring the env var. No-op on non-jemalloc builds (test/msvc). Refs #1230. --- .schema/pgdog.schema.json | 6 ++++++ Cargo.lock | 18 ++++++++++++++++++ pgdog-config/src/general.rs | 13 +++++++++++++ pgdog/Cargo.toml | 1 + pgdog/src/lib.rs | 17 +++++++++++++++++ pgdog/src/main.rs | 2 ++ 6 files changed, 57 insertions(+) diff --git a/.schema/pgdog.schema.json b/.schema/pgdog.schema.json index 3900e97cd..3a3ab7f9c 100644 --- a/.schema/pgdog.schema.json +++ b/.schema/pgdog.schema.json @@ -58,6 +58,7 @@ "idle_healthcheck_delay": 5000, "idle_healthcheck_interval": 30000, "idle_timeout": 60000, + "jemalloc_background_thread": false, "load_balancing_strategy": "random", "load_schema": "auto", "log_connections": true, @@ -811,6 +812,11 @@ "default": 60000, "minimum": 0 }, + "jemalloc_background_thread": { + "description": "Enable jemalloc's background purge threads so freed memory is returned to the OS after allocation bursts. Equivalent to `_RJEM_MALLOC_CONF=background_thread:true`.\n\n_Default:_ `false`\n\n", + "type": "boolean", + "default": false + }, "load_balancing_strategy": { "description": "Which strategy to use for load balancing read queries.\n\n_Default:_ `random`\n\n", "$ref": "#/$defs/LoadBalancingStrategy", diff --git a/Cargo.lock b/Cargo.lock index 662b1f8ee..b48888766 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3225,6 +3225,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3396,6 +3402,7 @@ dependencies = [ "stats_alloc", "tempfile", "thiserror 2.0.18", + "tikv-jemalloc-ctl", "tikv-jemallocator", "tokio", "tokio-rustls", @@ -5284,6 +5291,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tikv-jemalloc-ctl" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "661f1f6a57b3a36dc9174a2c10f19513b4866816e13425d3e418b11cc37bc24c" +dependencies = [ + "libc", + "paste", + "tikv-jemalloc-sys", +] + [[package]] name = "tikv-jemalloc-sys" version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" diff --git a/pgdog-config/src/general.rs b/pgdog-config/src/general.rs index 5ad4cc018..eaa01333b 100644 --- a/pgdog-config/src/general.rs +++ b/pgdog-config/src/general.rs @@ -806,6 +806,14 @@ pub struct General { /// #[serde(default)] pub cutover_save_config: bool, + + /// Enable jemalloc's background purge threads so freed memory is returned to the OS after allocation bursts. Equivalent to `_RJEM_MALLOC_CONF=background_thread:true`. + /// + /// _Default:_ `false` + /// + /// + #[serde(default = "General::jemalloc_background_thread")] + pub jemalloc_background_thread: bool, } impl Default for General { @@ -911,6 +919,7 @@ impl Default for General { cutover_timeout: Self::cutover_timeout(), cutover_timeout_action: Self::cutover_timeout_action(), cutover_save_config: bool::default(), + jemalloc_background_thread: Self::jemalloc_background_thread(), unique_id_function: Self::unique_id_function(), } } @@ -982,6 +991,10 @@ impl General { Self::env_bool_or_default("PGDOG_SCHEMA_RELOAD_ON_DDL", true) } + fn jemalloc_background_thread() -> bool { + Self::env_bool_or_default("PGDOG_JEMALLOC_BACKGROUND_THREAD", false) + } + fn idle_healthcheck_interval() -> u64 { Self::env_or_default("PGDOG_IDLE_HEALTHCHECK_INTERVAL", 30_000) } diff --git a/pgdog/Cargo.toml b/pgdog/Cargo.toml index 725aa94f3..20bc624e2 100644 --- a/pgdog/Cargo.toml +++ b/pgdog/Cargo.toml @@ -92,6 +92,7 @@ libc = "0.2" [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = "0.6" +tikv-jemalloc-ctl = "0.6" [build-dependencies] diff --git a/pgdog/src/lib.rs b/pgdog/src/lib.rs index 3e02cf07f..87eef5948 100644 --- a/pgdog/src/lib.rs +++ b/pgdog/src/lib.rs @@ -52,6 +52,23 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; #[global_allocator] static GLOBAL: &stats_alloc::StatsAlloc = &stats_alloc::INSTRUMENTED_SYSTEM; +/// Enable jemalloc's background purge threads so freed memory is returned to +/// the OS after allocation bursts. No-op when disabled. +#[cfg(all(not(test), not(target_env = "msvc")))] +pub fn set_jemalloc_background_thread(enabled: bool) { + if !enabled { + return; + } + match tikv_jemalloc_ctl::background_thread::write(true) { + Ok(()) => tracing::info!("jemalloc background_thread enabled"), + Err(err) => tracing::warn!("could not enable jemalloc background_thread: {}", err), + } +} + +/// No-op fallback where jemalloc is not the active allocator. +#[cfg(any(test, target_env = "msvc"))] +pub fn set_jemalloc_background_thread(_enabled: bool) {} + /// Filter that dynamically installs or removes an inner /// [`TracingRateLimitLayer`] at runtime. /// diff --git a/pgdog/src/main.rs b/pgdog/src/main.rs index 684c87e55..8aef4baef 100644 --- a/pgdog/src/main.rs +++ b/pgdog/src/main.rs @@ -82,6 +82,8 @@ fn main() -> Result<(), Box> { config::overrides(overrides); + pgdog::set_jemalloc_background_thread(config.config.general.jemalloc_background_thread); + plugin::load_from_config()?; let runtime = build_runtime( From 0ef1a02a35e96b5846575f04a21b5049bd0db8ce Mon Sep 17 00:00:00 2001 From: Igor Ohrimenko Date: Fri, 24 Jul 2026 19:38:13 +0300 Subject: [PATCH 2/3] test(config): cover jemalloc_background_thread parsing Assert the default (false) and env-var (PGDOG_JEMALLOC_BACKGROUND_THREAD) handling of the new option. --- pgdog-config/src/general.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pgdog-config/src/general.rs b/pgdog-config/src/general.rs index eaa01333b..4c5d13188 100644 --- a/pgdog-config/src/general.rs +++ b/pgdog-config/src/general.rs @@ -1750,6 +1750,15 @@ mod tests { assert!(!General::query_log_stdout()); } + #[test] + fn test_jemalloc_background_thread_env() { + let _guard = set_env_var("PGDOG_JEMALLOC_BACKGROUND_THREAD", "true"); + assert!(General::jemalloc_background_thread()); + + let _guard = remove_env_var("PGDOG_JEMALLOC_BACKGROUND_THREAD"); + assert!(!General::jemalloc_background_thread()); + } + #[test] fn test_env_numeric_fields() { let _guard = set_env_var("PGDOG_BROADCAST_PORT", "7432"); From b826b952ed5de95d0c6828eda3cdc9b11dbf9863 Mon Sep 17 00:00:00 2001 From: Igor Ohrimenko Date: Mon, 27 Jul 2026 20:22:54 +0300 Subject: [PATCH 3/3] refactor: enable jemalloc background_thread unconditionally Drop the jemalloc_background_thread option, its env var and schema entry; turn background purge threads on at startup instead. Background threads are universally desirable, and mallctl only fails on platforms that don't support them, so the error is ignored rather than logged. --- .schema/pgdog.schema.json | 6 ------ pgdog-config/src/general.rs | 22 ---------------------- pgdog/src/lib.rs | 14 ++++---------- pgdog/src/main.rs | 4 ++-- 4 files changed, 6 insertions(+), 40 deletions(-) diff --git a/.schema/pgdog.schema.json b/.schema/pgdog.schema.json index 3a3ab7f9c..3900e97cd 100644 --- a/.schema/pgdog.schema.json +++ b/.schema/pgdog.schema.json @@ -58,7 +58,6 @@ "idle_healthcheck_delay": 5000, "idle_healthcheck_interval": 30000, "idle_timeout": 60000, - "jemalloc_background_thread": false, "load_balancing_strategy": "random", "load_schema": "auto", "log_connections": true, @@ -812,11 +811,6 @@ "default": 60000, "minimum": 0 }, - "jemalloc_background_thread": { - "description": "Enable jemalloc's background purge threads so freed memory is returned to the OS after allocation bursts. Equivalent to `_RJEM_MALLOC_CONF=background_thread:true`.\n\n_Default:_ `false`\n\n", - "type": "boolean", - "default": false - }, "load_balancing_strategy": { "description": "Which strategy to use for load balancing read queries.\n\n_Default:_ `random`\n\n", "$ref": "#/$defs/LoadBalancingStrategy", diff --git a/pgdog-config/src/general.rs b/pgdog-config/src/general.rs index 4c5d13188..5ad4cc018 100644 --- a/pgdog-config/src/general.rs +++ b/pgdog-config/src/general.rs @@ -806,14 +806,6 @@ pub struct General { /// #[serde(default)] pub cutover_save_config: bool, - - /// Enable jemalloc's background purge threads so freed memory is returned to the OS after allocation bursts. Equivalent to `_RJEM_MALLOC_CONF=background_thread:true`. - /// - /// _Default:_ `false` - /// - /// - #[serde(default = "General::jemalloc_background_thread")] - pub jemalloc_background_thread: bool, } impl Default for General { @@ -919,7 +911,6 @@ impl Default for General { cutover_timeout: Self::cutover_timeout(), cutover_timeout_action: Self::cutover_timeout_action(), cutover_save_config: bool::default(), - jemalloc_background_thread: Self::jemalloc_background_thread(), unique_id_function: Self::unique_id_function(), } } @@ -991,10 +982,6 @@ impl General { Self::env_bool_or_default("PGDOG_SCHEMA_RELOAD_ON_DDL", true) } - fn jemalloc_background_thread() -> bool { - Self::env_bool_or_default("PGDOG_JEMALLOC_BACKGROUND_THREAD", false) - } - fn idle_healthcheck_interval() -> u64 { Self::env_or_default("PGDOG_IDLE_HEALTHCHECK_INTERVAL", 30_000) } @@ -1750,15 +1737,6 @@ mod tests { assert!(!General::query_log_stdout()); } - #[test] - fn test_jemalloc_background_thread_env() { - let _guard = set_env_var("PGDOG_JEMALLOC_BACKGROUND_THREAD", "true"); - assert!(General::jemalloc_background_thread()); - - let _guard = remove_env_var("PGDOG_JEMALLOC_BACKGROUND_THREAD"); - assert!(!General::jemalloc_background_thread()); - } - #[test] fn test_env_numeric_fields() { let _guard = set_env_var("PGDOG_BROADCAST_PORT", "7432"); diff --git a/pgdog/src/lib.rs b/pgdog/src/lib.rs index 87eef5948..c1011ac13 100644 --- a/pgdog/src/lib.rs +++ b/pgdog/src/lib.rs @@ -53,21 +53,15 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; static GLOBAL: &stats_alloc::StatsAlloc = &stats_alloc::INSTRUMENTED_SYSTEM; /// Enable jemalloc's background purge threads so freed memory is returned to -/// the OS after allocation bursts. No-op when disabled. +/// the OS after allocation bursts. #[cfg(all(not(test), not(target_env = "msvc")))] -pub fn set_jemalloc_background_thread(enabled: bool) { - if !enabled { - return; - } - match tikv_jemalloc_ctl::background_thread::write(true) { - Ok(()) => tracing::info!("jemalloc background_thread enabled"), - Err(err) => tracing::warn!("could not enable jemalloc background_thread: {}", err), - } +pub fn enable_jemalloc_background_thread() { + let _ = tikv_jemalloc_ctl::background_thread::write(true); } /// No-op fallback where jemalloc is not the active allocator. #[cfg(any(test, target_env = "msvc"))] -pub fn set_jemalloc_background_thread(_enabled: bool) {} +pub fn enable_jemalloc_background_thread() {} /// Filter that dynamically installs or removes an inner /// [`TracingRateLimitLayer`] at runtime. diff --git a/pgdog/src/main.rs b/pgdog/src/main.rs index 8aef4baef..e185551d2 100644 --- a/pgdog/src/main.rs +++ b/pgdog/src/main.rs @@ -19,6 +19,8 @@ use tokio::runtime::Builder; use tracing::{error, info, warn}; fn main() -> Result<(), Box> { + pgdog::enable_jemalloc_background_thread(); + let args = cli::Cli::parse(); let command = args.command.clone(); let mut overrides = pgdog::config::Overrides::default(); @@ -82,8 +84,6 @@ fn main() -> Result<(), Box> { config::overrides(overrides); - pgdog::set_jemalloc_background_thread(config.config.general.jemalloc_background_thread); - plugin::load_from_config()?; let runtime = build_runtime(