Skip to content

Leak on Linux? #955

Closed
Closed

Description

@asilvas

Been troubleshooting a leak in https://github.com/asilvas/node-image-steam (processes millions of images every day) and originally thought it was in my project but after a number of heap dump checks I determined it wasn't a leak in V8.

In order to break it down the simplest parts I recorded the traffic in a serial form so it can be replayed in a pure sharp script.

https://gist.github.com/asilvas/474112440535051f2608223c8dc2fcdf

npm i sharp request

curl https://gist.githubusercontent.com/asilvas/474112440535051f2608223c8dc2fcdf/raw/be4e593c6820c0246acf2dc9604012653d71c353/sharp.js > sharp.js
curl https://gist.githubusercontent.com/asilvas/474112440535051f2608223c8dc2fcdf/raw/be4e593c6820c0246acf2dc9604012653d71c353/sharp.log > sharp.log

node sharp.js http://img1.wsimg.com/isteam sharp.log

It's downloading these files on the fly, which avoids any FS caching which will bloat memory usage, and forwards the instructions (in sharp.log) directly to sharp, one at a time.

Memory usage gets into 500MB+ within a few mins (at least on Docker+CentOS), and seems to eventually peak. On some systems I've seen over 2GB usage. Only processing a single image at a time should be pretty flat in memory usage. Have you seen this before? Any ideas? I wasn't aware of anything sharp/vips was doing that should be triggering Linux's file caching.

Edit: While memory usage on Mac is still higher than I expect for a single image processed at a time (~160MB) after a couple hundred images, it's nowhere near as high as on Linux.. And it seems to peak quickly. So it appears to be a linux only issue. Docker is also involved, so not ruling that out either.

Activity

  1. lovell commented on Sep 21, 2017

    @lovell
    Owner

    Hello, how is memory usage being measured? If RSS, please remember this includes free memory that has not (yet) been returned to the OS, which explains why different OSs report different RSS for the same task.

    If you've not seen then, there have been quite a few related questions previously:
    https://github.com/lovell/sharp/search?utf8=%E2%9C%93&q=rss+%22returned+to+the+OS%22&type=Issues

  2. asilvas commented on Sep 21, 2017

    @asilvas
    Author

    Yes, RSS being the main indicator. But I also rely on buff/cache and avail memory to better understand "free-able" memory, and this indicates that this memory is never being released back. This memory doesn't seem to reside in the V8 memory space, as indicated from dozens of heap dump tests.

    Thanks for links to the other issues that seem connected. I'm not entirely sure the issue is fully understood though. I'm not convinced it's an issue with sharp either, but I'm hoping we can work around the problem as the impact is quite significant. We're using ~5x the memory we should be, which becomes a big deal when you're serving (many) millions of requests/day.

    I'll continue investigating the related cases. So far I've found no workaround to the problem that doesn't involve not using toBuffer.

  3. lovell commented on Sep 21, 2017

    @lovell
    Owner

    It's worth subscribing to nodejs/node#1671 for V8 updates that will improve GC of Buffer objects.

    If you're not already doing so, you might want to experiment with a different memory allocator such as jemalloc. You'll probably see less fragmentation, but that's still dealing with the effect rather than the cause.

  4. asilvas commented on Sep 21, 2017

    @asilvas
    Author

    Will do, thanks.

    Probably no surprise, but I was at least able to correlate usage with the concurrency setting.

    In my isolated (sharp-only) test these were the findings:

    0 concurrency: 276MB (should be detecting 4 in my local setup)
    1 concurrency: 190MB
    4 concurrency: 276MB
    8 concurrency: 398MB (our prod env)
    16 concurrency: 500MB
    
  5. lovell commented on Sep 22, 2017

    @lovell
    Owner

    Does the prod environment use 8 real CPU cores or is this "vCPU" hyper-threading? If the latter, perhaps also experiment halving concurrency to improve throughput (and reduce the memory effects).

  6. asilvas commented on Sep 22, 2017

    @asilvas
    Author

    I am artificially limiting cores to keep memory in check, but at the cost of up to 30% slower response times. Temporary test.

  7. asilvas commented on Sep 25, 2017

    @asilvas
    Author

    Feel free to close this as a duplicate of others. But from everything I've learned of the problem thus far there doesn't seem to be any conclusive evidence that this is a Node and/or V8 issue. From the symptoms of the isolated tests I've run (as well as others) it does seem to be any issue with sharp or vips as this isn't a common problem in the node community to see run-away memory increases of this nature. I was able to verify this is not a case of GC'd memory not being released back to the OS, confirming that this memory was in-use and any reduction in available memory resulting in memory allocation failure. But as I said, nothing conclusive either way.

    I tried to investigate ways to resolve/workaround the problem within Sharp but was unsuccessful -- hopefully someone with more expertise with V8 native modules will have better luck.

  8. lovell commented on Sep 25, 2017

    @lovell
    Owner

    "I was able to verify this is not a case of GC'd memory not being released back to the OS"

    Could memory fragmentation explain this?

  9. asilvas commented on Sep 25, 2017

    @asilvas
    Author

    I haven't proven/disproven that theory. But with the modest number of objects being processed to achieve such high memory usage, it'd seem to require some pretty severe fragmentation to justify this.

    Is your thoughts that it's fragmentation in V8 or native space?

  10. lovell commented on Sep 26, 2017

    @lovell
    Owner

    Are you using Node 8? If not, do you see the same RSS levels with it?

    Were you able to try jemalloc? It provides useful debugging via malloc_stats_print.

    Given you're using CentOS, have you tried disabling transparent huge pages?

  11. asilvas commented on Sep 26, 2017

    @asilvas
    Author

    Not using 8 in prod, but yes was able to reproduce similar RSS levels (with 8.5.0) in the isolated test.

    Might be a bit before I can look into the other options but will keep in mind, thanks.

  12. lovell commented on Oct 15, 2017

    @lovell
    Owner

    If sharpen or blur operations are being used then the small leak fixed in https://github.com/jcupitt/libvips/issues/771 may be related here.

  13. asilvas commented on Oct 15, 2017

    @asilvas
    Author

    The test sample for this topic doesn't use those two operations so probably unrelated. But we do use them on occasion, thanks!

  14. trev-dev commented on Nov 23, 2017

    @trev-dev

    I've found that running my sharp modules in a child_process spawn that exits once it's completed works really well for me. It keeps the memory load down.

  15. 38 remaining items

  16. cablespaghetti commented on Oct 24, 2019

    @cablespaghetti

    I switched from Debian to Alpine (and admittedly Node 11 to 12):
    image

    Thanks to everyone who suggested this as a fix!

  17. eugenelight16 commented on Jul 23, 2020

    @eugenelight16

    Sharp is very fast but there's an obvious memory problem. As I send items to be processed to a worker, the memory never gets released at end of processing.

    On a C5.x4large, I spawn 30 child processes. CPU ranges from 10%-30%. Memory however keeps growing until I run out of 32GB of ram in about 2 minutes.

    I'll investigate and see if I can come back with a solution.

  18. vinerz commented on Jul 23, 2020

    @vinerz

    @egekhter I can confirm that using jemalloc as allocator solves the problem.

  19. eugenelight16 commented on Jul 23, 2020

    @eugenelight16

    @egekhter I can confirm that using jemalloc as allocator solves the problem.

    Thanks, it does seem to help a bit, but the problem is not solved on my end. I installed jemalloc and set the "LD_PRELOAD" environment variable and it kept memory usage lower for longer, however it still keeps climbing. If I stop sending new items for processing to my workers, the memory usage of the entire server does not decrease.

  20. eugenelight16 commented on Jul 23, 2020

    @eugenelight16

    Another update.

    I originally switched from Jimp to Sharp as CPU usage was too high.

    I noticed CPU usage went down but memory usage went up.

    I switched from Sharp to GM.

    With the only change being switching out the modules and changing the function calls (i.e from extract to crop), my used memory decreased from 32 GB to a stable 3GB.

    There's not much we can do as a user from here.

  21. lovell commented on Jul 23, 2020

    @lovell
    Owner

    @egekhter Whilst Node.js Worker Threads will probably help the single-threaded world of jimp, they won't offer much to help the multi-threaded world of sharp/libvips and can cause greater heap fragmentation faster.

    30x worker threads each spawning 4x libuv threads each spawning 16x (c5.4xlarge vCPU) libvips threads is a concurrency of 1920 threads all allocating/freeing memory from the same pool.

    If you'd like to "manage" concurrency via Worker Threads, then try setting sharp.concurrency to 1 so libvips doesn't also try to do so.

  22. eugenelight16 commented on Jul 23, 2020

    @eugenelight16

    @egekhter Whilst Node.js Worker Threads will probably help the single-threaded world of jimp, they won't offer much to help the multi-threaded world of sharp/libvips and can cause greater heap fragmentation faster.

    30x worker threads each spawning 4x libuv threads each spawning 16x (c5.4xlarge vCPU) libvips threads is a concurrency of 1920 threads all allocating/freeing memory from the same pool.

    If you'd like to "manage" concurrency via Worker Threads, then try setting sharp.concurrency to 1 so libvips doesn't also try to do so.

    Solved my problem with your help.

    sharp.concurrency(1);

    Up to 70x child processes and still have plenty of memory left.

    Thanks for all your work!

    Screen Shot 2020-07-23 at 4 07 50 PM

  23. lovell commented on Mar 10, 2021

    @lovell
    Owner

    Please see #2607 for a change in the default concurrency for glibc-based Linux users that will be in v0.28.0.

  24. FoxxMD commented on May 5, 2022

    @FoxxMD

    not related...but @vinerz what application were you using to monitor memory in this comment? Is that heroku's dashboard?

  25. vinerz commented on May 5, 2022

    @vinerz

    not related...but @vinerz what application were you using to monitor memory in this comment? Is that heroku's dashboard?

    Hey @FoxxMD, that's New Relic's application monitor for node 😄

  26. alcidesbsilvaneto commented on Feb 18, 2023

    @alcidesbsilvaneto

    I switched from Debian to Alpine (and admittedly Node 11 to 12): image

    Thanks to everyone who suggested this as a fix!

    Still the solution for me in 2023.

  27. nandi95 commented on Sep 3, 2023

    @nandi95

    Went from:
    Screenshot_2023-09-03_at_21 33 26
    to:
    Screenshot_2023-09-03_at_23 48 59

    It's a definite improvement.

  28. rambo-panda commented on Dec 28, 2023

    @rambo-panda
    const sharp = require("sharp");
    (async () => {
        await Promise.all([...Array(100)].map(() => sharp("./test.webp").rotate(90).toBuffer()));
    })();     
    • use libjemalloc.so cache:true concurrency : 1
      image

    • use libjemalloc.so cache:false concurrency : 1
      image

    • use libjemalloc.so cache:true concurrency : 16
      image

    • use libjemalloc.so cache:false concurrency : 16
      image


    • use glibc cache: false concurrency:1
      image

    • use glibc cache:true concurrency:1
      image

    • use glibc cache:false concurrency : 16
      image

    • use glibc cache: true concurrency: 16
      image

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions