kleisauke / wasm-vips

libvips for the browser and Node.js, compiled to WebAssembly with Emscripten.
https://kleisauke.github.io/wasm-vips/
MIT License
519 stars 26 forks source link

High Memory Usage with wasm-vips Image Resizing #75

Closed jimzer closed 2 months ago

jimzer commented 2 months ago

High Memory Usage with wasm-vips Image Resizing

Praise for wasm-vips

First, I want to express my appreciation for the wasm-vips library. It's been a game-changer for my project. The ease of installation and setup in Docker is remarkable, especially compared to other image processing libraries I've tried. The performance and functionality are excellent, and it's made image processing in my web application significantly smoother. Thank you for creating such a powerful and user-friendly tool!

Description

While using wasm-vips in my project, I've encountered an issue with high memory usage when resizing images in my Node.js server. Despite the overall great performance, the server's memory consumption increases significantly and doesn't fully recover after processing images.

Current Behavior

Code Sample

import Vips from "wasm-vips";
// ... (other imports)

const vips = await Vips();
vips.config();

// ... (rest of the server setup)

.get(
    "/images/static/:uuid",
    zValidator(
        "query",
        z.object({
            w: z.coerce.number().int(),
            q: z.coerce.number().int().min(1).max(100).default(90),
        }),
    ),
    cache({
        cacheName: "my-app",
        cacheControl: "max-age=3600",
    }),
    async (c) => {
        const { w, q } = c.req.valid("query");
        const uuid = c.req.param("uuid");
        const media = (
            await db.select().from(medias).where(eq(medias.uuid, uuid))
        )?.[0];
        if (!media) {
            throw new HTTPException(404, { message: "Media not found" });
        }
        const blob = (
            await downloadMultipleFiles(SETTINGS.r2.bucket, [media.uuid])
        )?.[0];
        const processedImage = vips.Image.newFromBuffer(blob.content)
            .thumbnailImage(w)
            .writeToBuffer(".webp", { Q: q });

        c.header("Content-Type", "image/webp");
        return c.body(processedImage);
    },
)

Questions

  1. Am I using wasm-vips correctly for image resizing?
  2. Are there any settings or best practices I can apply to mitigate this high memory usage?
  3. Is this level of memory consumption expected when using wasm-vips for image processing?

Additional Information

Possible Solutions Tried

I would greatly appreciate any insights or suggestions on how to reduce memory consumption while still leveraging wasm-vips for image processing. Thank you again for this fantastic library, and for any help you can provide with this issue!

kleisauke commented 2 months ago

Did you see https://github.com/kleisauke/wasm-vips/issues/13#issuecomment-1073246828? Also, please avoid using thumbnailImage, if possible. It can't do any of the shrink-on-load tricks, so it's significantly slower.

@@ -31,9 +31,9 @@ vips.config();
     const blob = (
       await downloadMultipleFiles(SETTINGS.r2.bucket, [media.uuid])
     )?.[0];
-    const processedImage = vips.Image.newFromBuffer(blob.content)
-      .thumbnailImage(w)
-      .writeToBuffer(".webp", { Q: q });
+    const im = vips.Image.thumbnailBuffer(blob.content, w);
+    const processedImage = im.writeToBuffer(".webp", { Q: q });
+    im.delete();

     c.header("Content-Type", "image/webp");
     return c.body(processedImage);

Are you processing many different images? If so, you could consider disabling the operation cache via vips.Cache.max(0), see: https://github.com/libvips/libvips/blob/master/doc/Developer-checklist.md#disable-the-libvips-operation-cache-if-you-dont-need-it

kleisauke commented 2 months ago

Note that I would recommend just using sharp if you only want to target Node.js. It also comes with a WebAssembly build these days, see: https://github.com/kleisauke/wasm-vips/issues/43.

jimzer commented 2 months ago

Thank you very much for the help, I will try these improvements.

Indeed I'm processing many images but in separate HTTP requests. I use it as an image proxy so my frontend can request arbitrary sizes and quality.