Firebase云函数用Puppeteer截图触发256MB内存超限问题排查
Firebase Cloud Function + Puppeteer 内存超限与请求数异常排查
问题背景
创建了监听Firestore websites集合新增事件的Cloud Function,触发Puppeteer对目标网站截图,但频繁触发256MB内存限制错误。Puppeteer启动耗时极久(数分钟级),日志显示启动完成后出现内存超限,且存在处理1次或2次请求就触发错误的情况,怀疑和异步函数特性有关。
相关代码
云函数完整代码
exports.onWebsiteCreateTakeScreenshot = functions.firestore .document('websites/{id}') .onCreate(async (snap, context) =>{ const webpageDocRef = admin.firestore().collection("websites").doc(context.params.id); webpageDocRef.get().then(async (snapshot) => { if (!snapshot.empty) { const url = snapshot.data()["url"]; try { console.log("Launching puppeteer"); const browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox"], timeout: 0, }); console.log("puppeteer launch complete"); const page = await browser.newPage(); await page.setViewport({width: 640, height: 480}); await page.goto(url, {waitUntil: 'networkidle2'}); const bucket = admin.storage().bucket("thumbnails"); const file = bucket.file(context.params.id + ".png"); const screenshotBuffer = await page.screenshot(); await file.save(screenshotBuffer); await browser.close(); } catch (e) { console.error("Error: " + e); } } else { console.log("Error: No documents found"); } }); });
Puppeteer启动代码
const browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox"], timeout: 0, });
错误信息
Container worker exceeded memory limit of 256 MiB with 256 MiB used after servicing 2 requests total. Consider setting a larger instance class.
依赖信息(package.json片段)
"engines": { "node": "16" }, "main": "index.js", "dependencies": { "firebase-admin": "^10.0.2", "firebase-functions": "^3.18.0", "puppeteer" : "^18.2.1", "chromium" : "^3.0.3"
问题排查与解决方案
1. 内存超限原因
- Chromium基础内存占用:无头模式下的Chromium本身启动和加载网页的内存开销较大,256MB默认实例内存对多数网页来说都偏紧张,尤其是包含大量图片、脚本的页面。
- 异步逻辑错误导致内存泄漏:外层
async函数内部使用.then()而非await,导致云函数主线程提前结束,Puppeteer进程可能未被正确关闭,残留内存占用无法释放。 - 冗余依赖:同时安装
puppeteer和chromium,Puppeteer已内置Chromium,额外的chromium包会造成资源重复加载,增加内存负担。 - 资源释放不及时:仅关闭浏览器而未显式关闭页面,虽然理论上浏览器关闭会销毁页面,但提前关闭页面能更早释放内存。
2. 请求数异常与异步函数的关系
请求数显示处理2次但实际仅触发1次,核心原因是异步嵌套错误:
- 云函数
onCreate回调为async函数,但内部调用webpageDocRef.get().then(...)时未加await,导致函数主线程直接结束,Firebase判定执行完成,但.then()内的Puppeteer任务仍在后台运行。 - 当函数实例被复用时,新触发的任务会和之前未完成的任务同时运行,同一个容器内叠加处理多个请求,内存占用瞬间超限。
- 额外调用
webpageDocRef.get()属于冗余操作,snap参数已包含新增文档数据,这一步不仅浪费资源,还加剧了异步嵌套的复杂度。
3. 具体优化方案
修复异步处理逻辑
去掉冗余的Firestore查询,用await替代.then(),确保函数等待所有任务完成后再结束:
exports.onWebsiteCreateTakeScreenshot = functions.firestore .document('websites/{id}') .onCreate(async (snap, context) => { let browser; try { const url = snap.data().url; console.log("Launching puppeteer"); browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox", "--disable-dev-shm-usage", "--single-process"], timeout: 0, }); console.log("puppeteer launch complete"); const page = await browser.newPage(); await page.setViewport({width: 640, height: 480}); await page.goto(url, {waitUntil: 'networkidle2'}); const bucket = admin.storage().bucket("thumbnails"); const file = bucket.file(context.params.id + ".png"); const screenshotBuffer = await page.screenshot(); await file.save(screenshotBuffer); // 先关闭页面再关闭浏览器,尽早释放内存 await page.close(); await browser.close(); } catch (e) { console.error("Error: " + e); // 出错时强制关闭浏览器,避免内存泄漏 if (browser) await browser.close(); } });
优化Puppeteer启动参数
--disable-dev-shm-usage:避免使用容器内有限的/dev/shm空间,改用临时文件,减少内存压力。--single-process:以单进程模式运行Chromium,降低内存占用(注意:部分复杂页面可能存在兼容性问题)。
调整云函数实例配置
在firebase.json中配置更大的实例内存,比如512MB:
{ "functions": { "runtimeOptions": { "memory": "512MB" } } }
清理冗余依赖
卸载chromium包,仅保留puppeteer:
npm uninstall chromium
内容的提问来源于stack exchange,提问作者Greg
相关产品推荐
相关产品推荐

