Chromium มีขนาดประมาณ 280MB แต่แพ็กเกจที่ AWS Lambda unzip แล้วรับได้สูงสุดมีแค่ 250MB ถ้าเคยลอง npm install puppeteer แล้ว deploy ขึ้น Lambda ตรง ๆ ก็น่าจะรู้ดีว่าเลขสองตัวนี้มันไปด้วยกันไม่ได้เลย
ผมใช้เวลาอยู่พอสมควรกับการไล่แก้ปัญหา "Failed to launch the browser process" ตอนตีสอง เลยรู้ว่าหัวข้อนี้ควรมีการเทียบกันแบบจริงจัง ไม่ใช่บทความที่สอนทีละวิธีแล้วปล่อยให้คุณเดาเอาเองว่าวิธีอื่นต่างกันยังไง ดังนั้นบทความนี้จะพาไปดูทั้ง Layer, Container Image และการอัปโหลด ZIP ตรง ๆ พร้อมตารางความเข้ากันได้ของเวอร์ชันที่อัปเดตสำหรับปี 2026 และส่วนแก้ปัญหากับ 5 error ที่มีโอกาสเจอบ่อยที่สุด
Puppeteer บน AWS Lambda คืออะไร และทำไมถึงน่าใช้
Puppeteer คือไลบรารี Node.js ที่ใช้ควบคุม headless Chromium ผ่าน Chrome DevTools Protocol ส่วน Lambda คือบริการประมวลผลแบบ serverless ของ AWS ที่คิดค่าบริการตามการเรียกใช้งาน ระบบจะสเกลให้อัตโนมัติ และคุณไม่ต้องดูแลเซิร์ฟเวอร์เอง พอเอาสองอย่างนี้มารวมกัน คุณจะได้ชุด automation สำหรับเบราว์เซอร์ที่รองรับงานพร้อมกันได้หลายร้อยงาน โดยไม่ต้องเตรียม EC2 สักเครื่อง
เคสที่เจอบ่อย ๆ มักคล้ายกันในแต่ละทีม ไม่ว่าจะเป็น web scraping, สร้าง screenshot และ PDF, synthetic monitoring, pre-render หน้าเว็บแบบ single-page app เพื่อ SEO และการทดสอบ UI แบบอัตโนมัติ ปัญหาหลักก็ยังเป็นเรื่องเดิมที่พูดไปตอนต้น คือขนาดของ Chromium เทียบกับข้อจำกัดของ Lambda นี่แหละ เลยไม่มีใคร deploy puppeteer แบบเต็มชุดลง Lambda ตรง ๆ เพราะมันจะพา Chromium ของตัวเองมาด้วย ทางเลือกที่ใช้จริงคือ puppeteer-core ซึ่งไม่มี browser แถมมา แล้วจับคู่กับ Chromium binary ที่ปรับแต่งมาสำหรับ Lambda โดยเฉพาะ ซึ่งตัวที่นิยมสุดคือ @sparticuz/chromium
แค่เปลี่ยนจาก puppeteer มาเป็น puppeteer-core ก็ช่วยแก้ปัญหาด้านขนาดได้ราว 80% ตั้งแต่ยังไม่ต้องไปเขียน config สำหรับ deploy ด้วยซ้ำ
Layer, Container Image หรือ ZIP: เลือกทางให้ชัดก่อน
ตอนหาข้อมูลเรื่องนี้มีอย่างหนึ่งที่ทำให้ผมหงุดหงิดมาก นั่นคือแทบทุกคู่มือจะสอนแค่วิธี deploy แบบเดียว เช่น tutorial ของ AWS SAM จะเน้น Layers ตัวอย่างของ CDK จะใช้ Docker ส่วนโพสต์ใน Substack บางอันก็อัปโหลด ZIP ตรง ๆ พร้อม Chromium binary บน S3 แต่ไม่มีใครเอามาเทียบกันแบบชัด ๆ ทำให้คำถามสำคัญที่สุด — แล้ววิธีไหนเหมาะกับสถานการณ์ของฉัน — ถูกข้ามไปเฉย ๆ
งั้นเรามาแก้ตรงนี้กัน
| เกณฑ์ | Lambda Layers | Container Image (Docker) | อัปโหลด ZIP ตรง ๆ |
|---|---|---|---|
| ขนาดแพ็กเกจสูงสุด | 250 MB หลัง unzip (รวมทุก layer) | อิมเมจได้ถึง 10 GB | 250 MB หลัง unzip |
| ความซับซ้อนในการ deploy | ปานกลาง (ต้องจัดการ layer ARN) | สูงกว่า (Dockerfile + push ไป ECR) | ต่ำสุด (zip แล้วอัปโหลด) |
| ผลต่อ cold start | ปานกลาง | สูงขึ้นเล็กน้อย (ต้องดึงอิมเมจขนาดใหญ่กว่า) | ปานกลาง |
| วิธีอัปเดต Chromium | publish layer ใหม่ | build อิมเมจใหม่ | อัปโหลด zip ใหม่ |
| เหมาะกับ | โปรโตไทป์เร็ว ๆ, คนใช้ Serverless Framework | งาน production, ทีมที่มี Docker CI อยู่แล้ว | ฟังก์ชันเดี่ยว ๆ แบบง่าย ๆ |
| รองรับ IaC | SAM, Serverless Framework | CDK, SAM, Terraform | Console, IaC ทั่วไป |
ทั้งข้อจำกัด 250MB และ 10GB มาจาก เอกสาร Lambda quotas ของ AWS เอง โดยตรง — ตัวเลขนี้ไม่ได้เปลี่ยนบ่อย และเป็นข้อจำกัดที่ตัดสินกลยุทธ์การ deploy ของคุณตั้งแต่ต้น
ถ้าจะให้แนะนำแบบใช้งานจริง: ถ้าคุณกำลังทำ prototype หรือใช้งาน Serverless Framework อยู่แล้ว ให้เริ่มที่ Layers ถ้าจะเอาขึ้น production และทีมของคุณมี Docker CI/CD อยู่แล้ว ให้เลือก Container Image เพราะเพดาน 10GB ช่วยให้จัดการได้สบายกว่า ถ้าต้องการแค่ฟังก์ชันเดียวสำหรับแคปหน้าจอเป็นครั้งคราว ZIP ตรง ๆ คือวิธีที่ยุ่งยากน้อยที่สุด
ทั้งสามวิธีใช้ dependency หลักเหมือนกัน คือ puppeteer-core + @sparticuz/chromium ความต่างอยู่ที่วิธีแพ็กของสองตัวนี้ ไม่ใช่ตัว dependency เอง

ตารางความเข้ากันได้ของเวอร์ชันปี 2026 (เลิกเดาสุ่มได้แล้ว)
นี่คือส่วนที่ทำให้คนเสียเวลาหลายเดือน ไม่ใช่แค่ไม่กี่ชั่วโมง ข้อร้องเรียนที่ดังที่สุดใน Stack Overflow และ GitHub issue ไม่ใช่ "deploy ยังไง" แต่เป็น "ทำไมของที่เคยใช้ได้อยู่ดี ๆ พังหลังอัปเดต npm" สาเหตุส่วนใหญ่คือเวอร์ชันของ @sparticuz/chromium, puppeteer-core และ runtime ของ Node.js ไม่ตรงกัน
ข้อสำคัญที่ต้องรู้ก่อน: chrome-aws-lambda (แพ็กเกจต้นฉบับของ alixaxel) เลิกพัฒนาแล้ว มันใช้กับ Node 18+ ไม่ได้ดี และตาม Chromium เวอร์ชันไม่ทัน ถ้าเจอบทความไหนยังอ้างถึงแพ็กเกจนี้ ให้ปิดแท็บนั้นได้เลย เพราะมันล้าสมัยแล้ว คู่มือที่อัปเดตตอนนี้ควรพาไปใช้ @sparticuz/chromium แทน
ให้ใช้กฎความเข้ากันได้ด้านล่างนี้ แทนการจับคู่เลข major แบบดูด้วยตา:
| องค์ประกอบ | กฎเวอร์ชัน | สิ่งที่ต้องตรวจสอบก่อน deploy |
|---|---|---|
puppeteer-core | เลือกเวอร์ชันของ Puppeteer ที่แอปคุณต้องใช้ | ดูว่า Puppeteer เวอร์ชันนั้นรองรับ Chromium build ไหน |
@sparticuz/chromium | เลข major ของแพ็กเกจนี้ตาม Chromium major ไม่ใช่ Puppeteer major | จับคู่กับ Chromium build จากตารางที่ Puppeteer รองรับ และอ่าน release notes ของ Sparticuz |
| AWS Lambda Node.js runtime | ใช้ runtime ของ Lambda ที่ยังรองรับอยู่ในปัจจุบัน | ทดสอบ invocation ทุกครั้งหลังอัปเดตรันไทม์หรือแพ็กเกจ |
| Architecture | แพ็กเกจ npm มี binary x64 เป็นหลัก ส่วน arm64 เริ่มรองรับตั้งแต่ Chromium v135 ผ่าน arm64 layer หรือ remote pack | ต้องให้ architecture ของ Lambda, artifact ของ layer/pack และเวอร์ชัน Chromium ตรงกันเป๊ะ |
ผมตั้งใจไม่ล็อกคู่เวอร์ชันแบบตายตัวตรงนี้ เพราะ @sparticuz/chromium เดินตาม release cycle ของ Chromium และไม่ได้ใช้ semantic versioning แบบปกติ เริ่มจากดู หน้า Chromium Support ของ Puppeteer ก่อน จดเลข Chromium major ที่ Puppeteer เวอร์ชันที่คุณเลือกใช้รองรับ แล้วค่อยเลือก @sparticuz/chromium major เดียวกัน จากนั้นอ่าน release notes ของ Sparticuz เพื่อเช็ก breaking changes ระดับ patch และรายละเอียดเรื่อง architecture อย่าเพิ่งติดตั้งแพ็กเกจทั้งสองโดยใช้เลข major เดียวกันแบบเดา ๆ เว้นแต่ว่าแหล่งข้อมูลสองที่นั้นยืนยันไว้แล้ว

วิธี deploy Puppeteer บน AWS Lambda ด้วย Lambda Layers
Lambda Layer ช่วยให้คุณแยก Chromium ออกจากโค้ดฟังก์ชันได้ ทำให้ตัว handler ของคุณเล็กลง และสามารถใช้ Chromium layer เดิมร่วมกันกับหลายฟังก์ชันได้ นี่คือวิธีที่ใกล้เคียงคำว่า "เริ่มต้นง่าย ๆ" ที่สุดในพื้นที่นี้
ขั้นตอนที่ 1: ติดตั้ง puppeteer-core และแพ็กเกจ -min
เมื่อไฟล์ Chromium อยู่ใน Lambda Layer ให้ใช้ @sparticuz/chromium-min เพื่อให้แพ็กเกจของฟังก์ชันเล็กที่สุด แทนค่า placeholder ด้วยเวอร์ชันที่คุณเช็กความเข้ากันได้ไว้ด้านบน:
npm install puppeteer-core@$PUPPETEER_VERSION \
@sparticuz/chromium-min@$CHROMIUM_VERSION
ที่ต้องใช้ puppeteer-core ไม่ใช่ puppeteer เพราะมันจะไม่ดาวน์โหลด browser อัตโนมัติ ส่วนแพ็กเกจ -min จะให้ helper สำหรับ launch ขณะที่ layer จะเป็นตัวเก็บไฟล์ Chromium ที่บีบอัดด้วย Brotli ไว้ใต้ /opt/chromium
ขั้นตอนที่ 2: สร้างหรืออ้างอิง Chromium Lambda Layer
ใช้ archive ของ layer ที่แยกตาม architecture ซึ่งแนบมากับ Sparticuz release อย่างเป็นทางการ หรือจะ build archive จาก repo ทางการก็ได้ สำหรับ Lambda ที่เป็น x86_64 วิธี build ที่เอกสารแนะนำคือ:
git clone --depth=1 https://github.com/sparticuz/chromium.git
cd chromium
make chromium.x64.zip
คำสั่งนี้จะสร้าง chromium.x64.zip ให้นำไปอัปโหลดที่ S3 แล้ว publish เป็น Lambda Layer ด้วย runtime และ architecture ที่คุณใช้งานจริง ถ้าเป็น arm64 ก็ต้องใช้ artifact หรือ build target ที่ตรงกับ arm64 เท่านั้น ห้ามเอา x64 archive ไปผูกกับฟังก์ชัน arm64
ถ้าใช้ SAM ก็ผูก layer ARN ตรงใน template.yaml ได้เลย:
Resources:
PuppeteerFunction:
Type: AWS::Serverless::Function
Properties:
Layers:
- arn:aws:lambda:us-east-1:XXXXXXXXXXXX:layer:chromium-layer:1
ขั้นตอนที่ 3: เขียน Lambda Handler
นี่คือตัวอย่าง handler ที่ใช้งานได้จริงสำหรับเปิด URL และส่งกลับ title ของหน้า:
import puppeteer from "puppeteer-core";
import chromium from "@sparticuz/chromium-min";
export const handler = async () => {
const browser = await puppeteer.launch({
args: puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
executablePath: await chromium.executablePath("/opt/chromium"),
headless: "shell",
});
try {
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
return { title: await page.title() };
} finally {
await browser.close();
}
};
สังเกตบล็อก finally ให้ดี ควรปิด browser ตรงนี้เสมอ เพราะถ้าไม่ปิด environment ของ Lambda ที่ถูก warm ไว้จะสะสม process ของ browser ที่ค้างอยู่จากแต่ละ invocation และสุดท้ายคุณจะเจอปัญหา memory แปลก ๆ ที่ไม่ได้เกิดจากโค้ดจริงของคุณ
ขั้นตอนที่ 4: ตั้งค่า Memory, Timeout และ Architecture
ตั้ง memory อย่างน้อย 1024 MB — ถ้าเป็นงานที่หนักกว่าการแคปหน้าจอทั่วไป ผมแนะนำ 1536–2048 MB ตั้ง timeout อย่างน้อย 60 วินาที และล็อก architecture เป็น x86_64 เว้นแต่คุณยืนยันแล้วว่า Chromium เวอร์ชันที่ใช้รองรับ arm64 จริง ๆ (เรื่องนี้เปลี่ยนไปตาม release)
ขั้นตอนที่ 5: Deploy และทดสอบ
sam build && sam deploy --guided
จากนั้นเรียกด้วย test event แล้วเช็ก CloudWatch Logs ทันทีถ้ามีอะไรผิดปกติ — 90% ของ error ที่อยู่ในส่วน troubleshooting ด้านล่างจะเห็นชัดใน log เหล่านี้
วิธี deploy Puppeteer บน AWS Lambda ด้วย Container Images (Docker)
Container image แก้ปัญหา 250MB ไปเลยด้วยเพดาน 10GB แทน ซึ่งโดยทั่วไปเป็นตัวเลือกที่ดีกว่าสำหรับงาน production โดยเฉพาะถ้าทีมของคุณใช้ Docker อยู่ใน pipeline CI แล้ว
ขั้นตอนที่ 1: สร้าง Dockerfile
เริ่มจาก official AWS Lambda base image สำหรับ Node.js ติดตั้ง dependencies แล้วกำหนด handler:
FROM public.ecr.aws/lambda/nodejs:20
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["index.handler"]
ขึ้นอยู่กับแพ็กเกจ Chromium ที่คุณใช้ คุณอาจต้อง yum install shared library บางตัวเพิ่มเติมด้วย (จะอธิบายในส่วน troubleshooting) — @sparticuz/chromium รวมของที่จำเป็นไว้ค่อนข้างมาก จึงลดความยุ่งยากไปเยอะเมื่อเทียบกับการติดตั้ง Chrome เต็มชุดด้วยตัวเอง
ขั้นตอนที่ 2: Build และ push ไป Amazon ECR
aws ecr create-repository --repository-name puppeteer-lambda
docker build -t puppeteer-lambda .
docker tag puppeteer-lambda:latest <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
aws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
ควรวางอิมเมจไว้ region เดียวกับ Lambda function ของคุณ เพราะการดึงอิมเมจข้าม region จะเพิ่ม latency โดยไม่จำเป็น
ขั้นตอนที่ 3: สร้าง Lambda Function จาก Container Image
ชี้ฟังก์ชันไปยัง ECR image URI ผ่าน CLI หรือ CDK แล้วตั้ง memory (1536–2048 MB) และ timeout (60–120 วินาที) เหมือนกับการ deploy แบบ layer
ขั้นตอนที่ 4: Deploy และทดสอบ
เรียกด้วย test event แล้วตรวจผลลัพธ์ จุดต่างหลักเมื่อเทียบกับ Layers คือ cold start จะช้ากว่าเล็กน้อยเพราะต้องดึงอิมเมจที่ใหญ่กว่า แต่คุณได้พื้นที่สำหรับ dependencies มากขึ้นเยอะ
วิธี deploy Puppeteer บน AWS Lambda ด้วยการอัปโหลด ZIP ตรง ๆ
นี่คือตัวเลือกแบบไม่ต้องแต่งเยอะ — ไม่ต้องจัดการ layers และไม่ต้อง build Docker เหมาะกับ prototype หรือฟังก์ชันเดี่ยวที่ไม่ได้ต้องขยายไปเป็น browser automation platform เต็มรูปแบบ
ขั้นตอนที่ 1: ติดตั้ง dependencies แบบ local
ถ้าจะทำ ZIP แบบ self-contained ให้ใช้ puppeteer-core + @sparticuz/chromium และล็อกเวอร์ชันทั้งสองไว้ให้ชัด แพ็กเกจแบบเต็มจะมีไฟล์ Chromium ที่บีบอัดมาด้วย และจะแตกไฟล์ไปที่ /tmp ตอนรัน ใช้ @sparticuz/chromium-min เฉพาะกรณีที่คุณเตรียมไฟล์เหล่านั้นแยกไว้ผ่าน Lambda Layer หรือ remote pack URL ที่โหลดได้เร็วเท่านั้น เพราะ -min จะไม่มีไฟล์ Brotli มาให้ในตัว
ขั้นตอนที่ 2: bundle และ zip ฟังก์ชัน
npm install --production
zip -r function.zip . -x "*.git*"
ตัว --production สำคัญมาก เพราะ dev dependencies จะกินงบ 250MB ของคุณไปฟรี ๆ
ขั้นตอนที่ 3: อัปโหลดและตั้งค่า Lambda Function
aws lambda update-function-code --function-name my-puppeteer-fn --zip-file fileb://function.zip
ถ้า ZIP ของคุณเกิน 50MB จะอัปโหลดผ่าน console หรือ CLI แบบง่าย ๆ ไม่ได้ ต้องอัปโหลดขึ้น S3 ก่อนแล้วค่อยอ้างอิง S3 URI แทน ตั้งค่า memory, timeout และ architecture เหมือนอีกสองวิธีก่อนหน้า
ขั้นตอนที่ 4: Deploy และทดสอบ
ใช้ flow เดิมคือ invoke แล้วเช็ก log แบบเดิม ถ้าใช้แพ็กเกจเต็ม chromium.executablePath() ไม่ต้องใส่ argument ส่วน chromium-min ต้องส่งพาธของ layer หรือ remote pack URL ที่ตรงเป๊ะ เช่น chromium.executablePath("/opt/chromium") สำหรับ layout ของ layer ด้านบน ถ้าใช้ remote pack ก็จะเพิ่มงานดาวน์โหลดใน cold start ครั้งแรก ดังนั้นควรวางไฟล์ไว้ใกล้กับฟังก์ชันและตรวจให้แน่ใจว่าเวอร์ชัน artifact กับ architecture ตรงกัน
args ของ puppeteer.launch() ที่ใช้บน Lambda ได้จริง
นี่คือ snippet ที่ทุกคนชอบก็อปไปใช้ มาทำให้มันถูกต้องกันก่อนดีกว่า environment ของ Lambda ไม่มี /dev/shm, ไม่มี GPU และมี permission จำกัด — หมายความว่า puppeteer.launch() แบบ default ที่ใช้ได้บน laptop ของคุณ จะใช้งานไม่ได้ตรงนี้
const viewport = {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: true,
};
const browser = await puppeteer.launch({
args: await puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
executablePath: await chromium.executablePath(),
headless: "shell",
defaultViewport: viewport,
});
อาร์เรย์ chromium.args จาก @sparticuz/chromium ใส่ flag ที่จำเป็นสำหรับ serverless environment มาให้แล้ว เช่น --no-sandbox, --disable-gpu, --disable-dev-shm-usage และอื่น ๆ นี่คือเหตุผลที่ใช้แพ็กเกจนี้แทนการเขียนรายการ flag เอง เพราะมันตาม requirement ของ Chromium ให้แล้ว คุณจะได้ไม่ต้องไล่แก้เอง

Troubleshooting: 5 error ที่นักพัฒนาส่วนใหญ่ต้องเจอ
คู่มือส่วนใหญ่ที่ผมเจอไม่มี troubleshooting แบบจริงจังเลย ซึ่งแปลกมาก เพราะ error นี่แหละน่าจะเป็นเหตุผลที่คุณกำลังอ่านบทความนี้อยู่
"Failed to launch the browser process"
สาเหตุหลัก: shared library หายไป (libnss3.so, libatk ฯลฯ) หรือ executablePath ไม่ถูกต้อง
วิธีแก้: @sparticuz/chromium แพ็ก dependency ที่จำเป็นส่วนใหญ่มาให้แล้ว นี่จึงเป็นแพ็กเกจที่แนะนำมากกว่าการ build Chromium binary เอง ถ้าใช้ Docker แล้วเจอปัญหานี้อยู่ ให้ yum install library ที่ขาดให้ชัดเจนใน Dockerfile
"Unzipped size must be smaller than 262144000 bytes"
สาเหตุหลัก: คุณติดตั้ง puppeteer แบบเต็ม ซึ่งพา Chromium ของมันมาเองด้วย (~400MB)
วิธีแก้: เปลี่ยนไปใช้ puppeteer-core + @sparticuz/chromium ถ้าจำเป็นต้องมีพื้นที่เพิ่มจริง ๆ ให้ย้ายไปใช้แนวทาง Container Image ที่มีเพดาน 10GB
"Browser disconnected" หรือ timeout ตอน browser.newPage()
สาเหตุหลัก: memory ของ Lambda ไม่พอ หรือคุณลืมใส่ flag อย่าง --disable-gpu ใน launch args
วิธีแก้: ตั้ง memory อย่างน้อย 1024MB (ถ้าเป็นไปได้ให้สูงกว่านั้น — ดู benchmark ด้านล่าง) และตรวจให้แน่ใจว่าคุณส่ง chromium.args เข้าไปจริง ไม่ใช่ลดทอน list เองแบบ custom
โค้ดที่เคยใช้ได้กลับพังหลังอัปเดต Lambda runtime
สาเหตุหลัก: AWS มีการ patch runtime อยู่เรื่อย ๆ ซึ่งอาจเปลี่ยน shared library version หรือ Node.js patch version ใต้เท้าคุณโดยไม่ตั้งใจ
วิธีแก้: ล็อกเวอร์ชัน @sparticuz/chromium ให้ชัด ล็อก Node runtime เวอร์ชันใน config ของฟังก์ชัน และที่คนมักข้ามไป — ให้ทดสอบใหม่ทุกครั้งหลัง AWS ประกาศอัปเดต runtime ไม่ใช่รอให้พังแล้วค่อยแก้
"Protocol error: Connection closed" หลังผ่านไปประมาณ 30 วินาที
สาเหตุหลัก: timeout ของ Lambda สั้นกว่าระยะเวลาที่หน้าเว็บต้องใช้ในการโหลดและ render จริง
วิธีแก้: เพิ่ม timeout เป็น 60–120 วินาที ตั้ง page.setDefaultNavigationTimeout() ให้ชัดเจน และเปลี่ยน waitUntil: 'networkidle0' เป็น waitUntil: 'domcontentloaded' ถ้าคุณไม่ได้ต้องการให้ทุก network request เงียบสนิทก่อนค่อยทำขั้นตอนถัดไป
ทำให้พร้อม production: Memory, cold start และต้นทุน
คู่มือส่วนใหญ่มักบอกแค่ว่า "เพิ่ม memory" แล้วจบ ซึ่งไม่ค่อยช่วยอะไรเท่าไร — มาดูกันว่าการเพิ่ม memory ส่งผลอะไรจริงบ้างเมื่อสเกลขึ้น
Memory เทียบกับประสิทธิภาพ
Lambda แบ่ง CPU ตาม memory ที่ตั้งไว้ ซึ่งเป็นรายละเอียดที่ทำให้หลายคนสับสน การเพิ่ม memory ไม่ใช่แค่ได้ RAM มากขึ้น แต่ยังได้ CPU มากขึ้นด้วย ซึ่งช่วยให้ Chromium render ได้เร็วขึ้นโดยตรง ในการใช้งานจริง ทีมที่รัน benchmark ของ Puppeteer มักจะเห็นเวลาการทำงานดีขึ้นชัดเจนเมื่อขยับจาก 512MB ขึ้นไปแถว 1536–2048MB แต่ตัวเลขจริงจะขึ้นอยู่กับหน้าเว็บที่คุณ render มาก ๆ แทนที่จะยกตาราง benchmark แบบตายตัวที่เดี๋ยวอ่านไปก็เก่า ให้คุณลองทดสอบที่ 512MB, 1024MB, 1536MB และ 2048MB กับหน้าเป้าหมายจริงของคุณเองดีกว่า — ใช้เวลาแค่ราว 10 นาที แต่จะบอกได้ชัดเลยว่าจุดคุ้มค่าระหว่างต้นทุนกับประสิทธิภาพอยู่ตรงไหน
ใช้ Provisioned Concurrency เพื่อลด cold start
ถ้างานของคุณอ่อนไหวกับ latency มาก เช่น synthetic monitoring หรือ screenshot API แบบเรียลไทม์ cold start คือศัตรูตัวฉกาจ Provisioned Concurrency จะคง execution environment จำนวนหนึ่งให้พร้อมใช้งานอยู่เสมอ ช่วยตัด penalty จาก cold start ออกไป แต่ต้องแลกกับการจ่ายเงินสำหรับ capacity ที่ว่างอยู่ เหมาะมากเมื่อความเร็วสำคัญกว่าความคุ้มค่าล้วน ๆ
ใช้ arm64 (Graviton) เพื่อลดต้นทุน
Lambda บน Graviton มีค่าใช้จ่ายถูกกว่ารุ่น x86_64 ประมาณ 20% แต่ข้อควรระวังคือ support ของ arm64 ใน @sparticuz/chromium เคยมีข้อจำกัดมากกว่า x86_64 อยู่พอสมควร ดังนั้นก่อนจะย้ายไปใช้ Graviton ใน production ต้องตรวจสอบเวอร์ชันที่ล็อกไว้ให้แน่ชัดก่อน
VPC กับไม่ใช้ VPC
การวางฟังก์ชันไว้ใน VPC เคยทำให้ cold start ช้าลงอย่างเห็นได้ชัด แต่ AWS ลดช่องว่างนี้ไปมากในช่วงหลายปีที่ผ่านมา ทว่ายังไม่ถึงกับเป็นศูนย์ ถ้าฟังก์ชันของคุณต้องเข้าถึง resource ภายใน เช่น RDS หรือ ElastiCache ค่อยวางใน VPC ถ้าไม่จำเป็นก็ไม่ต้องใส่
เมื่อไรควรย้ายออกจาก Lambda ไปเลย
ถ้างานเบราว์เซอร์ของคุณใช้เวลานานเกิน 15 นาที ต้องการ memory มากกว่า 10GB หรือจำเป็นต้องมี browser session ค้างอยู่ระหว่าง request Lambda จะเริ่มฝืนงานของคุณแล้ว ในจุดนี้ ECS Fargate เหมาะกว่าเพราะออกแบบมาสำหรับงานที่รันยาว ปรับ resource ได้ และคิดเงินตามวินาที Lambda เหมาะมากกับงานเบราว์เซอร์ที่สั้น เป็นช่วง ๆ และแตกงานขนานได้ดี แต่ไม่ใช่เครื่องมือที่ถูกทางเมื่อ workload ของคุณเริ่มมีลักษณะเป็น service ถาวร
เมื่อการ deploy Puppeteer บน Lambda อาจไม่ใช่คำตอบที่ถูก
มีเรื่องหนึ่งที่ควรคิดให้ตรงไปตรงมา: นักพัฒนาจำนวนมากที่มาเจอคู่มือ "Puppeteer + Lambda" จริง ๆ แล้วกำลังแก้ปัญหาการดึงข้อมูล ไม่ใช่ปัญหาการควบคุมเบราว์เซอร์ ถ้าสิ่งที่คุณต้องการจริง ๆ คือข้อมูลแบบมีโครงสร้างจากหน้าเว็บ เช่น รายการสินค้า ข้อมูลติดต่อ หรือเนื้อหาบนหน้าเว็บ ทั้งการแพ็ก Chromium, การล็อกเวอร์ชัน และการจัดการ layer ที่พูดมาข้างต้นอาจเป็นภาระที่ไม่จำเป็น
ควรใช้ Lambda + Puppeteer ต่อ ถ้าคุณต้องควบคุมเบราว์เซอร์จริง ๆ เช่น กรอกฟอร์มแบบกำหนดเอง สร้าง pipeline สำหรับ screenshot/PDF, ทำ synthetic monitoring หรือทดสอบผ่าน browser ที่ต้อง manipulate DOM ด้วยโค้ด
ควรพิจารณา scraping API ถ้าปลายทางของคุณคือ JSON ที่มีโครงสร้างจากหน้าเว็บ ไม่ใช่ session ของ browser ที่คุณต้องขับเอง Thunderbit's Open API จัดการการ render JS, anti-bot และ CAPTCHA ให้หลังบ้านผ่าน HTTP call เดียว — POST /extract พร้อม JSON Schema จะได้ข้อมูลแบบมีโครงสร้างกลับมา ส่วน POST /distill จะช่วยแปลงเป็น Markdown ที่สะอาด นอกจากนี้ยังมี MCP server (thunderbit_extract, thunderbit_distill) สำหรับคนที่กำลังสร้าง AI agent และต้องดึงข้อมูลระหว่าง workflow โดยไม่ต้องเปิด browser ของตัวเองขึ้นมา
| ปัจจัย | Lambda + Puppeteer (ทำเอง) | Extraction API (เช่น Thunderbit) |
|---|---|---|
| เวลาในการเริ่มต้น | หลายชั่วโมง (แพ็กเกจ, layer, debug) | ไม่กี่นาที (API key + HTTP call) |
| การดูแลรักษา | ต้องดูแลต่อเนื่อง (ล็อกเวอร์ชัน, อัปเดตรันไทม์) | ผู้ให้บริการจัดการให้ |
| การรับมือ anti-bot | ทำเอง (stealth plugin, proxy) | มีมาให้ในตัว |
| รูปแบบผลลัพธ์ | HTML/ screenshot ดิบ ๆ ที่คุณต้อง parse เอง | JSON แบบมีโครงสร้างผ่าน schema |
| เหมาะกับ | browser automation เต็มรูปแบบ, testing, flow แบบ custom | การดึงข้อมูล, scraping, content ingestion |
พูดตรง ๆ เลยนะ: ถ้าคุณใช้เวลาหลายชั่วโมงไล่แก้ Chromium binary เพื่อแค่จะดึง JSON จากหน้าสินค้า นั่นเป็นสัญญาณว่าคุณอาจกำลังแก้ปัญหาผิดโจทย์ เก็บแนวทาง DIY Lambda ไว้ใช้ตอนที่คุณต้องควบคุม browser จริง ๆ ส่วนงาน extraction มีทางที่ตรงกว่า ถ้าคุณกำลังชั่งใจเรื่องนี้สำหรับโปรเจกต์หนึ่งโดยเฉพาะ คู่มือ AI web scraper ของเราจะช่วยให้เห็นภาพตลาดได้ลึกขึ้น และ Thunderbit Chrome Extension ก็น่าสนใจถ้าคุณอยากลองแนวทาง extraction-first ก่อนจะตัดสินใจใช้ทางไหนจริง
สรุป
มีอยู่ 3 วิธีในการ deploy แต่มีแนวคิดร่วมกันอยู่ข้อเดียว: ล็อกเวอร์ชันให้ดี, ให้ Chromium มี memory มากพอจะทำงาน และเลือกวิธี deploy ให้ตรงกับข้อจำกัดจริงของคุณ ไม่ใช่เลือกจาก tutorial แรกที่เจอ Layers เหมาะกับการ iterate เร็ว ๆ, Container Images เหมาะกับ production scale และ ZIP เหมาะกับงานเดี่ยวที่เรียบง่าย และถ้าสิ่งที่คุณทำจริง ๆ คือการดึงข้อมูล ไม่ใช่ browser automation ก็ควรถามตัวเองว่าใช้ extraction API โดยเฉพาะจะช่วยตัดความยุ่งยากเรื่อง packaging ออกไปได้เลยหรือไม่
คำถามที่พบบ่อย
ปี 2026 ยังรัน Puppeteer บน AWS Lambda ได้ไหม?
ได้ — ใช้ puppeteer-core คู่กับ @sparticuz/chromium แล้ว deploy ผ่าน Layers, Container Image หรือ ZIP ตรง ๆ ส่วน puppeteer แบบเต็มและ chrome-aws-lambda รุ่นเก่าไม่ค่อยเชื่อถือได้บน Lambda runtime ปัจจุบันแล้ว
ขนาดแพ็กเกจสูงสุดของ AWS Lambda คือเท่าไร? 250MB หลัง unzip สำหรับการ deploy แบบ Layer และ ZIP ส่วน Container Image ได้ถึง 10GB ตาม Lambda quotas ของ AWS
chrome-aws-lambda ยังมีคนดูแลอยู่ไหม?
ไม่มีแล้ว แพ็กเกจ chrome-aws-lambda ต้นฉบับ (ของ alixaxel) เลิกพัฒนาแล้ว และใช้กับ Node 18+ ได้ไม่ดี ควรใช้ @sparticuz/chromium แทน เพราะเป็นมาตรฐานที่ยังมีการดูแลอยู่ในตอนนี้
Puppeteer ต้องใช้ memory เท่าไรบน AWS Lambda? 1024MB คือขั้นต่ำที่ใช้งานได้จริง ส่วน 1536–2048MB คือช่วงที่ประสิทธิภาพเริ่มสบายขึ้น ถ้าต่ำกว่า 1024MB ให้คาดได้เลยว่าการทำงานจะช้าอย่างเห็นได้ชัด เพราะ Lambda ผูก CPU กับ memory
จะลด cold start ของ Puppeteer บน Lambda ได้ยังไง? เพิ่ม memory เพื่อได้ CPU เพิ่มด้วย, พิจารณาใช้ Provisioned Concurrency ถ้า latency เป็นเรื่องสำคัญ, และพยายามทำให้แพ็กเกจ deploy เบาที่สุด — dependency เพิ่มมาแต่ละตัวคือเวลาที่เพิ่มใน cold start


