Abhinav◗
← All notes

Oct 13, 2026

My Discord bot's memory climbed every day, and the JavaScript heap never moved.

RSS up. Heap flat.

When the heap doesn't move, the leak isn't in your JavaScript.

Annie is a discord.js bot, about 125,000 lines and 417 commands, running sharded in 480+ servers on a 2-core container, and every shard renders images and charts with canvas and sharp, so large buffers come and go all day.

The leak was in glibc.

Its mmap threshold is dynamic. Every time a large buffer is freed, the threshold ratchets up, so after the first few image buffers, large allocations stop coming from mmap and start coming from the arena heap via sbrk. Arena memory is never handed back to the OS.

Then the container. It reports 16 cores while limited to 2, so glibc opened around 128 malloc arenas that fragmented and never gave memory back.

Two settings fixed it.

I pinned MALLOC_MMAP_THRESHOLD_ at 128 KB, so every large allocation uses mmap, which is returned on free. Then I capped the arenas with MALLOC_ARENA_MAX=2.

Both must be set in code before manager.spawn(). glibc reads them at process start, before dotenv ever runs. Put them in.env and nothing happens.

My rule from it: when the heap is flat and RSS isn't, stop reading your code and start reading the allocator's.

What's the lowest layer a bug has ever sent you?

Discuss on LinkedIn → Newer: The loudest part of the waveform had zero words in the transcript. →