Repository navigation
build: pin jemalloc's page size to 4 KiB on aarch64 Linux - #39549
Merged
Merged
Conversation
jemalloc 5.3.1, which arrived with tikv-jemalloc-sys 0.7.1 in MaterializeInc#39339, changed its default allocator page on aarch64 Linux from the build host's page (4 KiB) to 64 KiB. Our aarch64 nodes run 4 KiB kernel pages, and with a 64 KiB allocator page every small-size-class slab and every heap-profiling sample occupies at least 64 KiB. In v26.45.0-rc.3 that raised jemalloc_active minus jemalloc_allocated by 200 to 400 MiB per process in local runs, and by up to 1.6 GiB on the largest production canary clusterd pods. Set AARCH64_UNKNOWN_LINUX_GNU_JEMALLOC_SYS_WITH_LG_PAGE=12 so the aarch64 Linux build keeps the 4 KiB page it had with jemalloc 5.3.0. The target-prefixed variable leaves x86_64 and macOS builds untouched. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Member
|
Moving to 64kb page size was not a deliberate choice. It might be interesting for compute workloads, but our kernel currently only supports 4kb pages, so we still have to pay the kernel overhead, even if the allocator works in larger pages. This change wasn't obvious from the changelog, kudos to you for spotting it. |
bosconi
pushed a commit
that referenced
this pull request
Oct 5, 2026
The rc.4 increment is two items, both omitted, so this snapshot adds no entry to the assembled `## v26.45.0` section and the provisional dates are unchanged (Cloud 2026-10-08, Self-Managed 2026-10-09). - #39549 pins jemalloc's allocator page back to 4 KiB on aarch64-unknown-linux-gnu, repairing a 64 KiB-page regression that #39339 introduced earlier in this same unreleased train. No released version ever ran the 64 KiB page, so a user upgrading from v26.44.0 sees the same allocator page before and after; #39339 was itself omitted at rc.1 as a dependency bump, so nothing published describes the behavior and there is no bullet to amend. Recorded as borderline in the snapshot's omitted.md. - `ba9b30921` is the materialize-bot version bump for the candidate. The one agent-skills PR in the window, MaterializeInc/agent-skills#88, is an automated `mz-docs` regeneration and is omitted as it was at rc.1. This empty commit records rc.4 as applied; v26.39.0 rc.3 is the precedent for a zero-entry RC on this layout.
This was referenced Oct 5, 2026
val-materialize
added a commit
that referenced
this pull request
Oct 9, 2026
Release notes for the v26.45 train, published as **v26.45.1**. v26.45.0 did not ship, so (as with v26.44) its notes are folded in here and [#39477](#39477) is closed unmerged. - **v26.45.0 content**: 8 Improvements, 2 Agent Skills, 8 Bug Fixes, copied verbatim from #39477 with anchors renamed to `{#v26.45.1-…}`. - **v26.45.1 content**: Alerting for Self-Managed and the external dependency monitoring Improvement (editor-added). The release's only PR, [#39659](#39659), is omitted. - **Dates**: Cloud 2026-10-08, Self-Managed 2026-10-09. The operator compatibility row is `v26.45.1`. `v26.45.md` (`patch: 1`, no `rc`) is already on `main`.⚠️ **Open questions** - **Group-sync bullet**: `oidc_group_role_sync_enabled` defaults to `false` at the tag. Is it on in Cloud? If not, cut the "Quieter logins" bullet. - **Certificate SAN requirements** ([#39316](#39316), [#39415](https://github.com/MaterializeInc/materialize/pull/39415)/[#39459](https://github.com/MaterializeInc/materialize/pull/39459)) need action at upgrade time but sit in Improvements. Should they be called out more loudly? Also, does [#39351](#39351) (S3 on rustls) impose the same SAN rule on custom S3 endpoints? - **[#39659](#39659) (omitted)**: is `enable_upsert_paged_spill` on in production? A draft bullet is in [omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.45.1/final/omitted.md#borderline). - **`EXPLAIN ANALYZE ... CPU`**: [agent-skills#90](MaterializeInc/agent-skills#90) says it overstates joins, reductions, and TopK by 1.5x–3x before v26.46. Not published. Is that a known issue? - **Metric sinks**: [#39332](#39332), [#38910](#38910), and [#39337](#39337) are withheld because `enable_metric_sink` is `false`. #39337 fixes a panic any superuser can reach, so consider publishing it regardless. - **Possible cuts**: [#39362](#39362) was observed only in CI. The latency figures from [#39179](#39179) were measured on one noisy machine. - **Possible adds**: [#38894](https://github.com/MaterializeInc/materialize/pull/38894)–[#38896](https://github.com/MaterializeInc/materialize/pull/38896) (MV correction buffer) need an end-to-end number. [#39149](#39149) fixes the kind install guide, which crash-loops today. [#39549](#39549) (jemalloc page) is omitted because no released version had the regression. Snapshot drafts in mz-skills: [v26.45.0](https://github.com/MaterializeInc/mz-skills/tree/main/data/mz-release-notes/v26.45.0) (rc.1–rc.4, final), [v26.45.1](https://github.com/MaterializeInc/mz-skills/tree/main/data/mz-release-notes/v26.45.1/final). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Valerie Chiang <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Pin jemalloc's allocator page to 4 KiB for
aarch64-unknown-linux-gnubuilds by settingAARCH64_UNKNOWN_LINUX_GNU_JEMALLOC_SYS_WITH_LG_PAGE = "12"in.cargo/config.toml.#39339 bumped tikv-jemalloc-sys from 0.6.1 (jemalloc 5.3.0) to 0.7.1 (jemalloc 5.3.1). jemalloc 5.3.1 changed its default allocator page on aarch64 Linux from the build host's page to 64 KiB (facebook/jemalloc#34). Neither #39339 nor the tikv-jemallocator 0.7.0 release notes mention it, and I found no discussion of it in code, issues or Slack.
@antiguru confirmed on this PR that the move to 64 KiB was not a deliberate choice. Every production and staging environmentd and clusterd pod runs on arm64 nodes, so rc.3 runs with a 64 KiB allocator page on 4 KiB-page kernels.
The effect is a step in
jemalloc_activeminusjemalloc_allocatedin every Materialize process. This PR restores the 4 KiB page that v26.44.0 and earlier had on aarch64, and keeps everything else in jemalloc 5.3.1.What was observed
The v26.45.0-rc.3 sign-off sweeps on staging and the production canaries flagged
jemalloc_activerising whilejemalloc_allocatedstayed flat or fell, from process start, in environmentd and clusterd:How it was bisected
All of this ran locally on an Apple Silicon Mac under OrbStack: linux/arm64 natively, 4 KiB kernel page.
1. Page size per image. Running each image with
MALLOC_CONF=stats_print:true <binary> --versionprints jemalloc's compiled page size:environmentd,clusterd,materializedv26.44.0environmentd,clusterd,materializedv26.44.0environmentd,clusterd,materializedv26.45.0-rc.3environmentd,clusterdv26.45.0-rc.3materialized:v26.45.0-dev.0--main.geb032636b571a61c1016d2a13f7398b5edceb8ff(parent of #39339)materialized:v26.45.0-dev.0--main.g0d478e2dc6ba5361b948bc222c61968878508625(#39339)kube_pod_container_infoshows every rc.3 environmentd and clusterd container on the production canaries (us-east-1, eu-west-1, us-west-2) and on staging (us-east-1, eu-west-1) running the Docker Hub index digests that were tested.2. Workload bisection on per-commit images. 150 of the 232 main commits between the v26.44.0 and rc.3 branch points have
--main.g<sha>images on Docker Hub, including #39339 and its parent. A harness started one freshmaterializedcontainer per run on a fixed timeline: 40 s idle, then a fixed SQL workload onquickstart(a 1.5M-row table, an index, an aggregating and a joining materialized view, deletes, some catalog churn), then a 60 s settle and six samples 10 s apart. It recorded jemalloc stats from environmentd's/metricsand from each clusterd's internal HTTP socket, plus VmRSS. Each arm64 configuration ran twice, interleaved.Active minus allocated, after the workload, in MiB:
mz_catalog_serverquickstartv26.44.0v26.45.0-rc.3experimental_tcache_gc:falsedisable_large_size_classes:falsev26.44.0, amd64The step is in #39339, the only first-parent commit between the two main images. The two new jemalloc 5.3.1 defaults, set back through
MALLOC_CONF, do not move it. rc.3 on amd64 runs the same jemalloc 5.3.1 with a 4 KiB page and has v26.44.0's gap.3. Page size alone. rc.3 was rebuilt twice with
cargo build --profile optimized --bin materializedin the ci-builder: once unchanged and once withJEMALLOC_SYS_WITH_LG_PAGE=12. Each binary replaced/usr/local/bin/materializedin the released arm64 rc.3 image. The 4 KiB build keepsexperimental_tcache_gcanddisable_large_size_classeson.mz_catalog_servergapmz_catalog_serverVmRSSquickstartgapquickstartVmRSSValues in MiB, two runs each.
4. Where the extra active memory sits. Full
malloc_stats_printoutput was dumped from the live processes after the same workload, for both rebuilt images. The table splits the 64 KiB minus 4 KiB gap difference, in MiB:mz_catalog_serverquickstartprof:trueandlg_prof_sample:19, every sampled small allocation gets its own page-aligned extent. That extent counts as active and not as allocated, so a 64 KiB page makes each sample cost 64 KiB or more. Withprof_active:falsethat term is zero.How the cost scales
The two terms in step 4 scale differently, so the effect on small and large pods differs.
Partly filled slabs: roughly a fixed cost per process shape.
Each arena keeps about one partly used slab per size class in use, so this term follows arenas times size classes in use, not heap size.
jemalloc creates four arenas per CPU in the process's affinity mask. Swap-enabled replica sizes do not get exclusive CPUs, so a clusterd pod's arena count follows its node's vCPU count. That last step is inferred from the replica-size config and was not measured on a pod.
Locally, cutting the arena count from 48 to 16 cut this term by 48 to 61%.
On the production canaries, the clusterd gap as a share of
mz_memory_limiter_memory_limit_bytesfalls as pods grow. This is a snapshot at 2026-10-04 05:40 UTC across different workloads, not a controlled scaling test:mz_probeThe largest pod's gap, 1.58 GiB, is about 22 times the smallest's, for about 670 times the limit.
Not measured: frees in a different order from allocation also leave partly empty slabs behind, and a 64 KiB-page slab holds 16 times as many regions. A heap with heavy churn could therefore grow this part of the term with its size.
Heap-profiling samples: linear in live small-object bytes.
lg_prof_sample:19, about one small allocation is sampled per 512 KiB allocated. The live sampled allocations therefore number about live small-object bytes divided by 512 KiB, and each takes at least one page.jemalloc_allocatedon the three local processes.Resident memory, which the clusterd memory limiter counts, rises less than either term.
Why the target-prefixed variable
tikv-jemalloc-sys reads
<TARGET>_JEMALLOC_SYS_WITH_LG_PAGEbeforeJEMALLOC_SYS_WITH_LG_PAGE(tikv/jemallocator#91, used this way in tikv/jemallocator#122). The unprefixed variable would also apply to aarch64 macOS builds, where the kernel page is 16 KiB. There jemalloc aborts at startup with "Unsupported system page size" (tikv/jemallocator#80). The prefixed form changes only the aarch64 Linux build. x86_64 already uses a 4 KiB page.Upstream moved to 64 KiB so one aarch64 binary runs on 4, 16 and 64 KiB kernels. The thread it closed, jemalloc/jemalloc#2639, notes the costs, including heap profiling. With this change, a Materialize aarch64 binary again fails to start on a kernel with pages larger than 4 KiB. v26.44.0 and earlier had the same constraint. Their 4 KiB-page jemalloc runs on our arm64 nodes, so those kernels use 4 KiB pages.
Verification
cargo build -p tikv-jemalloc-sys -vvin the ci-builder on this branch passes--with-lg-page=12to jemalloc's configure, which reportsLG_PAGE : 12foraarch64-unknown-linux-gnu.Page size: 4096and gives the measurements above.No tests were added. Whether the published arm64 images report
Page size: 4096can be checked by running the next main image withMALLOC_CONF=stats_print:true clusterd --version.🤖 Generated with Claude Code