From ffd95fd86e52a66932803bf14b5bd9076fb4e549 Mon Sep 17 00:00:00 2001 From: Panos Vekris Date: Thu, 29 Oct 2026 18:09:32 -0700 Subject: [PATCH] [flow] Use jemalloc in Cargo builds on Linux Summary: Release binaries are built with Cargo, which uses the system allocator: glibc malloc on Linux. glibc keeps most of the memory a recheck frees instead of returning it to the OS, so a long-running server's resident memory ratchets up with every large recheck. On a large codebase it settled at about twice what the same code uses with jemalloc, and `malloc_trim(0)` gave the difference back, so the growth is retained free memory, not live data. Install jemalloc as the global allocator in Cargo builds on Linux, with background threads so that memory freed by a recheck goes back to the OS while the server is idle. Buck builds pick their allocator themselves and are unchanged. jemalloc fixes its page size when it is built, so the arm64 release build uses 64 KiB pages, which also work on kernels with 4 or 16 KiB pages. macOS keeps the system allocator, which already returns memory after a recheck. With jemalloc (which has no background threads on macOS) the server settled about 1 GiB higher and peaked about 4 GiB lower, which is not a clear win. Changelog: [fix] Linux release builds use jemalloc, which keeps the memory of long-running servers from growing after large rechecks. Differential Revision: D128303865 --- .github/workflows/build_and_test.yml | 2 ++ rust_port/Cargo.lock | 21 +++++++++++++++++++++ rust_port/crates/flow_cli/Cargo.toml | 3 +++ rust_port/crates/flow_cli/src/main.rs | 6 ++++++ 4 files changed, 32 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index fb330eab760..7558f76ff3d 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -80,6 +80,8 @@ jobs: env: CARGO_PROFILE_RELEASE_STRIP: 'true' CARGO_PROFILE_RELEASE_CODEGEN_UNITS: '1' + # jemalloc fixes its page size at build time; 64 KiB pages also run on 4 and 16 KiB kernels. + JEMALLOC_SYS_WITH_LG_PAGE: '16' run: cargo build --release --bin flow_cli - name: Create artifacts run: |- diff --git a/rust_port/Cargo.lock b/rust_port/Cargo.lock index d13d8fca737..0a98c3aa1b2 100644 --- a/rust_port/Cargo.lock +++ b/rust_port/Cargo.lock @@ -559,6 +559,7 @@ dependencies = [ "lsp-types", "serde", "serde_json", + "tikv-jemallocator", ] [[package]] @@ -3472,6 +3473,26 @@ dependencies = [ "syn 3.0.9", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.7.1+5.3.1-0-g81034ce1f1373e37dc865038e1bc8eeecf559ce8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2825c78386b4ae0314074867860ba9577875de945f05992c38815cbec327f0" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249f09e49ab1609436f34c776e84231bead18d6a955f119f939bdc1d847561bd" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "tokio" version = "1.53.2" diff --git a/rust_port/crates/flow_cli/Cargo.toml b/rust_port/crates/flow_cli/Cargo.toml index bc730b14c08..925e9c6a962 100644 --- a/rust_port/crates/flow_cli/Cargo.toml +++ b/rust_port/crates/flow_cli/Cargo.toml @@ -72,5 +72,8 @@ lsp-types = "0.97" serde = { version = "1.0.229", features = ["derive", "rc"] } serde_json = { version = "1.0.154", features = ["alloc", "float_roundtrip", "raw_value", "unbounded_depth"] } +[target.'cfg(target_os = "linux")'.dependencies] +tikv-jemallocator = { version = "0.7.0", features = ["background_threads"] } + [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ["cfg(fbcode_build)"]} diff --git a/rust_port/crates/flow_cli/src/main.rs b/rust_port/crates/flow_cli/src/main.rs index d47165140ac..17dcfebe50b 100644 --- a/rust_port/crates/flow_cli/src/main.rs +++ b/rust_port/crates/flow_cli/src/main.rs @@ -10,6 +10,12 @@ #[used] static malloc_conf: &str = "metadata_thp:always\0"; +// Buck builds pick their allocator with `fbcode.default_allocator`, which replaces `malloc` for the +// whole binary, so a Rust global allocator there could link a second copy of jemalloc. +#[cfg(all(not(fbcode_build), target_os = "linux"))] +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + fn main() { #[cfg(windows)] {