Skip to content

zlib deflate results in a memory leak #8871

Description

@jasonmcaffee
  • Version:6.7.0
  • Platform:Darwin ITs-MacBook-Pro.local 15.6.0 Darwin Kernel Version 15.6.0: Thu Jun 23 18:25:34 PDT 2016; root:xnu-3248.60.10~1/RELEASE_X86_64 x86_64
  • Subsystem:zlib

I'm using the graylog2 package for logging, and we ran into significant memory leak issues.
After tracking it down, I found zlib.deflate is the source of the issue.
The issue is magnified when running code inside of docker with the latest node distribution.

Running the below code on my macbook pro results in the memory spiking to ~3GB, then released down to 600MB.
Running the code in the latest node docker distro results in memory spiking to ~3GB, and it is never released.

let zlib = require('zlib');

let message = {
  some:"data"
};
let payload = new Buffer(JSON.stringify(message));

for(var i =0; i < 30000; ++i){
  zlib.deflate(payload, function (err, buffer) {
  });
}

setTimeout(()=>{}, 2000000);

This has resulted in our docker containers crashing due to memory exhaustion.

Activity

  1. added
    zlibIssues and PRs related to the zlib module and its compression dependencies.
    memoryIssues and PRs related to Node.js memory management or memory footprint.
    on Sep 30, 2016
  2. mscdex commented on Sep 30, 2016

    @mscdex
    Contributor

    Are you sure the node version is the same between the two?

  3. jasonmcaffee commented on Sep 30, 2016

    @jasonmcaffee
    Author

    Yes. I've also recreated the issue on v4.5 and 5.12.

  4. smyt1 commented on Sep 30, 2016

    @smyt1

    @jasonmcaffee have you experimented with the sync version zlib.deflateSync? I see the same thing with the async version but the sync version seems more tame

  5. bnoordhuis commented on Oct 1, 2016

    @bnoordhuis
    Member

    The loop is creating 30,000 concurrent zlib.deflate requests that are handed off to a threadpool; i.e., it's creating a giant backlog. They are dispatched eventually and that is why memory goes down again on OS X.

    On Linux (or more precisely, with glibc), something else happens. When you run it through strace -cfe mmap,munmap,mprotect,brk you can see what is going on:

    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     99.63    3.803199          32    119985           brk
      0.20    0.007680          12       632           mmap
      0.12    0.004748           7       675           munmap
      0.05    0.001882          30        62           mprotect
    ------ ----------- ----------- --------- --------- ----------------
    100.00    3.817509                121354           total
    

    Node.js uses malloc/new and glibc translates those overwhelmingly to brk system calls. The problem is that the "program break" goes up but a few allocations with longer lifetimes make it impossible for the break to go down again. There is no real memory leak, it's ordinary - but rather catastrophic - memory fragmentation.

    IOW, can confirm but node.js is not the root cause. Maybe we can sidestep it by using a custom allocator like jemalloc but that has a lot of ramifications, it's not something we can do lightly.

  6. jasonmcaffee commented on Oct 3, 2016

    @jasonmcaffee
    Author

    Thanks @bnoordhuis for the breakdown and explanation.

  7. kevinburke commented on Jan 30, 2017

    @kevinburke

    FWIW, we just fixed a problem that had similar symptoms, and our solution was to disable transparent huge pages. I'd encourage you to investigate and see if a) they are turned on, and b) if disabling them fixes your problem.

  8. jorangreef commented on Jun 30, 2017

    @jorangreef
    Contributor

    Could this be an issue if multiple clients are triggering async deflate calls within a few ms of each other? Should people just avoid deflate() all together?

  9. jorangreef commented on Jun 30, 2017

    @jorangreef
    Contributor

    Does this go much further than just deflate()? Could this be affecting most async calls which allocate memory on Linux?

  10. TylerBrock commented on Jul 19, 2017

    @TylerBrock

    Does anyone know which versions of node do not have this bug? We are seeing this on latest Node8 but also on all versions of 6 that I sampled.

    I run the code snippet above in node invoked with --expose-gc and call global.gc() to free the memory but 3gb is still resident no matter what I do. This is killing us in production as we have to restart our containers every couple of hours.

  11. bnoordhuis commented on Jul 19, 2017

    @bnoordhuis
    Member

    @TylerBrock Did you read through this issue and the linked issues? If you did, then you already know the answer.

  12. TylerBrock commented on Jul 20, 2017

    @TylerBrock

    I did but I'm still not sure so I apologize if I missed something.

    People have suggested it could be new zlib, new v8, or something in crypto (possibly related to zlib) but going back to versions of node having older zlib and v8 (checked via process.versions) yielded similar results.

    Would you mind summarizing where we are at?

  13. TimothyGu commented on Jul 20, 2017

    @TimothyGu
    Member

    @TylerBrock

    From #8871 (comment):

    There is no real memory leak, it's ordinary - but rather catastrophic - memory fragmentation.

    IOW, can confirm but node.js is not the root cause. Maybe we can sidestep it by using a custom allocator like jemalloc but that has a lot of ramifications, it's not something we can do lightly.

    In other words, this is not a Node.js, or zlib, or V8 issue, but rather caused by how the system memory allocator works.

  14. 62 remaining items

  15. alestor123 commented on Jul 30, 2020

    @alestor123

    I should try with jemalloc on Linux.

    @lpinca I've tried that with jemalloc 3.6.0-11 and here is the output:

    $ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ./node zlib.js
    testLeak: 13.778s
    Memory: {
      rss: 334114816,
      heapTotal: 131715072,
      heapUsed: 103527424,
      external: 472453668,
      arrayBuffers: 471975702
    }
    

    Try using - zlib-bug-inflate

  16. lpinca commented on Jul 30, 2020

    @lpinca
    Member

    @puzpuzpuz

    That's without the mitigation patch right? I also tried with jemalloc and there is an order of magnitude difference (~40 MB with the patch and ~400 MB without it).

  17. puzpuzpuz commented on Jul 30, 2020

    @puzpuzpuz
    Member

    @lpinca no, that's with the patch. I may be doing something wrong, as I don't see an order of magnitude difference.

  18. lpinca commented on Jul 30, 2020

    @lpinca
    Member

    Nvm it seems it was me that did something wrong:

    luigi@ubuntu:~/leak$ node -v
    v14.7.0
    luigi@ubuntu:~/leak$ env LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 node index.js
    testLeak: 57.830s
    Memory: {
      rss: 46489600,
      heapTotal: 4251648,
      heapUsed: 2202536,
      external: 998285,
      arrayBuffers: 17590
    }
    luigi@ubuntu:~/leak$ nvm use 14.6.0
    Now using node v14.6.0 (npm v6.14.6)
    luigi@ubuntu:~/leak$ node -v
    v14.6.0
    luigi@ubuntu:~/leak$ env LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 node index.js
    testLeak: 1:24.327 (m:ss.mmm)
    Memory: {
      rss: 49336320,
      heapTotal: 3989504,
      heapUsed: 2189736,
      external: 997652,
      arrayBuffers: 17590
    }
    

    It seems jemalloc is a working workaround.

  19. puzpuzpuz commented on Jul 30, 2020

    @puzpuzpuz
    Member

    It seems jemalloc is a working workaround.

    @lpinca you probably mean that jemalloc works more or less like #34048 when used with pre-14.7.0 versions of node, right? At least, that's how it looks like.

  20. lpinca commented on Jul 30, 2020

    @lpinca
    Member

    Better than that, I mean that with jemalloc there is no leak at all.

    luigi@ubuntu:~/leak$ node -v
    v14.7.0
    luigi@ubuntu:~/leak$ node index.js
    testLeak: 1:11.205 (m:ss.mmm)
    Memory: {
      rss: 700317696,
      heapTotal: 132943872,
      heapUsed: 101497112,
      external: 493479181,
      arrayBuffers: 492498486
    }
    luigi@ubuntu:~/leak$ env LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 node index.js
    testLeak: 57.279s
    Memory: {
      rss: 49631232,
      heapTotal: 9494528,
      heapUsed: 2323328,
      external: 998285,
      arrayBuffers: 17590
    }
    luigi@ubuntu:~/leak$ nvm use 14.6
    Now using node v14.6.0 (npm v6.14.6)
    luigi@ubuntu:~/leak$ node -v
    v14.6.0
    $ env LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 node index.js
    testLeak: 1:24.564 (m:ss.mmm)
    Memory: {
      rss: 51523584,
      heapTotal: 4513792,
      heapUsed: 2273040,
      external: 997652,
      arrayBuffers: 17590
    }
    
  21. puzpuzpuz commented on Jul 30, 2020

    @puzpuzpuz
    Member

    Better than that, I mean that with jemalloc there is no leak at all.

    Strictly speaking memory fragmentation is not a leak and the allocator should be able to get rid of it when the allocation rate gets lower and fewer allocated memory chunks remain. If jemalloc doesn't have a noticeable fragmentation in the discussed scenario, that's great.

    As a summary, it would be great to get some feedback from the community on v14.7.0 (which includes #34048) and v14.7.0 (or any previous version) with jemalloc.

  22. lpinca commented on Jul 30, 2020

    @lpinca
    Member

    Yes "leak" is not the correct term in this case. My point is that with jemalloc memory usage remains pretty much stable regardless of how many times the testLeak() function in the above example is run. This is not true with glibc even with #34048 applied.

  23. intellix commented on Oct 23, 2023

    @intellix

    How does this look in 2023 with Node 18+ ? There's no comments on this issue for 3 years so I'm wondering if it's safe to use permessage-deflate to gzip websockets and was pointed to this issue.

  24. STRML commented on Oct 23, 2023

    @STRML
    Contributor
  25. cyantree commented on Dec 3, 2023

    @cyantree

    I've run the test from #8871 (comment)
    on my machine with the following results:

    • Win 10
    • Ryzen 7 1800X
    • 32GB RAM
    testLeak: 4:14.889 (m:ss.mmm)
    Memory: {
      rss: 95129600,
      heapTotal: 5390336,
      heapUsed: 3328040,
      external: 1524293,
      arrayBuffers: 18659
    }
    

    During the test the process memory usage consistently oscillated between around 600 MB and 6,4 GB.

  26. davidje13 commented on Nov 5, 2025

    @davidje13
    Contributor

    Since this was originally reported on mac, and reported to be worse on Linux, I've run an adapted version of the updated reproduction on a MacBook (48GB RAM) and a Raspberry Pi (8GB RAM), using standard Node.js (i.e. not using jemalloc):

    import { promisify } from 'util';
    import { deflate } from 'zlib';
    
    const deflatePromise = promisify(deflate);
    const payload = Buffer.from(JSON.stringify({ some: 'data' }));
    
    async function testLeak() {
      const promises = [];
      for (let i = 0; i < 30000; i++) {
        promises.push(deflatePromise(payload));
      }
      await Promise.all(promises);
    }
    
    for (let i = 0; i < 50; i++) {
      await testLeak();
      await new Promise((resolve) => setTimeout(resolve, 2000));
      console.log((process.memoryUsage().rss / 1000000).toFixed() + ' MB');
    }

    Results:

    Image

    Memory useage clearly increases for the first few runs, but it's not unbounded on either system, so it does appear the original issue of fragmentation has been resolved (somewhere; not necessarily in Node.js itself).

    I think it would be safe to close this issue.

  27. gurgunday commented on Nov 5, 2025

    @gurgunday
    Member

    Closing

    Feel free to comment if anyone still has this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-bugIssues and PRs for confirmed bugs.memoryIssues and PRs related to Node.js memory management or memory footprint.zlibIssues and PRs related to the zlib module and its compression dependencies.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions