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..4c5d13188 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) } @@ -1737,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"); 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(