diff --git a/eleventy.config.js b/eleventy.config.js index b8431ae..615871e 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -1123,7 +1123,7 @@ export default function (eleventyConfig) { const siteName = process.env.SITE_NAME || "My IndieWeb Blog"; try { execFileSync(process.execPath, [ - "--max-old-space-size=768", + "--max-old-space-size=512", "--expose-gc", resolve(__dirname, "lib", "og-cli.js"), contentDir, diff --git a/lib/og.js b/lib/og.js index 4feb24b..7b9d394 100644 --- a/lib/og.js +++ b/lib/og.js @@ -298,8 +298,13 @@ export async function generateOgImages(contentDir, cacheDir, siteName) { let skipped = 0; const newManifest = {}; const SAVE_INTERVAL = 10; - const GC_INTERVAL = 50; + // GC every 5 images to keep WASM native memory bounded. + // Satori (Yoga WASM) + Resvg (Rust WASM) allocate ~50-100 MB native memory + // per image that V8 doesn't track. Without aggressive GC, native memory + // grows unbounded and OOM-kills the process in constrained containers. + const GC_INTERVAL = 5; const hasGC = typeof global.gc === "function"; + let peakRss = 0; for (const filePath of mdFiles) { const raw = readFileSync(filePath, "utf8"); @@ -333,25 +338,28 @@ export async function generateOgImages(contentDir, cacheDir, siteName) { newManifest[slug] = { title: slug, hash }; generated++; - // Save manifest periodically to preserve progress + // Save manifest periodically to preserve progress on OOM kill if (generated % SAVE_INTERVAL === 0) { writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2)); } // Force GC to reclaim Satori/Resvg WASM native memory. // V8 doesn't track native heap (Satori Yoga WASM + Resvg Rust WASM), - // so without periodic GC the JS wrappers accumulate and native memory - // grows unbounded. With --expose-gc this keeps peak RSS under control. + // so without frequent GC the JS wrappers accumulate and native memory + // grows unbounded. Every 5 images keeps peak RSS under ~400 MB. if (hasGC && generated % GC_INTERVAL === 0) { global.gc(); + const rss = process.memoryUsage().rss; + if (rss > peakRss) peakRss = rss; } } if (hasGC) global.gc(); writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2)); const mem = process.memoryUsage(); + if (mem.rss > peakRss) peakRss = mem.rss; console.log( `[og] Generated ${generated} images, skipped ${skipped} (cached or have photos)` + - ` | RSS: ${(mem.rss / 1024 / 1024).toFixed(0)} MB, heap: ${(mem.heapUsed / 1024 / 1024).toFixed(0)} MB`, + ` | RSS: ${(mem.rss / 1024 / 1024).toFixed(0)} MB, peak: ${(peakRss / 1024 / 1024).toFixed(0)} MB, heap: ${(mem.heapUsed / 1024 / 1024).toFixed(0)} MB`, ); }