- ในเบนช์มาร์กของนักพัฒนาอิสระ Theo Browne พบว่า Cloudflare Workers ช้ากว่า Vercel Node.js ได้สูงสุด 3.5 เท่า
- สาเหตุของผลเบนช์มาร์กมาจาก ปัญหาด้านโครงสร้างพื้นฐาน การตั้งค่าไลบรารี และระเบียบวิธีการทดสอบ หลายประการ
- มีการปรับปรุงแพลตฟอร์มและเฟรมเวิร์กหลายด้าน เช่น การปรับอัลกอริทึมการจัดตารางงาน การจูน V8 garbage collector และการปรับแต่ง OpenNext
- จากแพตช์สำคัญเหล่านี้ ปัจจุบัน ช่องว่างด้านประสิทธิภาพระหว่าง Cloudflare กับ Vercel ในเบนช์มาร์กส่วนใหญ่ลดลงอย่างมาก
- ต่อจากนี้ Cloudflare มีแผนจะ มีส่วนร่วมในการปรับปรุงโครงสร้างพื้นฐานสาธารณะและเฟรมเวิร์ก พร้อมเดินหน้าปรับแต่งและตรวจสอบเบนช์มาร์กอย่างต่อเนื่อง
ภาพรวมและข้อถกเถียงเรื่องเบนช์มาร์ก
- ในเดือนตุลาคม 2023 นักพัฒนา Theo Browne ได้เผยแพร่เบนช์มาร์กเปรียบเทียบ ความเร็วในการรัน JavaScript ฝั่งเซิร์ฟเวอร์ของ Cloudflare Workers และ Vercel (ที่ทำงานบน AWS Lambda)
- แม้ Cloudflare Workers จะใช้เอนจิน JavaScript V8 เหมือนกับ Vercel แต่กลับพบว่าช้ากว่าได้สูงสุด 3.5 เท่า
- ช่องว่างด้านประสิทธิภาพที่ไม่สมเหตุสมผลนี้เกิดจากหลายปัจจัย เช่น การจูนโครงสร้างพื้นฐานแบบละเอียด ความแตกต่างของไลบรารี JavaScript และปัญหาในวิธีการทดสอบ
- ระหว่างการแก้ไขปัญหาเหล่านี้ ประสิทธิภาพโดยรวมของ Cloudflare Workers ก็ได้รับการปรับปรุงตามไปด้วย
- ในบรรดาการแก้ไขสำคัญ ยังมีการปรับปรุงที่อาจส่งผลต่อแพลตฟอร์มอื่นด้วย เช่น การเพิ่มความเร็วของการคำนวณฟังก์ชันตรีโกณมิติ
ระเบียบวิธีของเบนช์มาร์ก
- ไคลเอนต์ทดสอบชุดแรกของ Theo เข้าถึงผ่าน Webpass จากซานฟรานซิสโก และ Vercel รันอยู่ในรีเจียน sfo1
- ฝั่ง Cloudflare ลดผลกระทบจาก latency ของเครือข่ายให้เหลือน้อยที่สุด โดยสื่อสารกับอินสแตนซ์ iad1 ของ Vercel โดยตรงจากดาต้าเซ็นเตอร์ AWS us-east-1
- เบนช์มาร์กทั้งหมดรันในสภาพแวดล้อมแบบ single-thread (1 vCPU) และทำให้เทียบราคาได้ง่ายด้วย
- บั๊กที่พบระหว่างการทดสอบถูกส่งเป็น Pull Request ไปยัง upstream และได้รับการแก้ไข
การปรับปรุงประสิทธิภาพของแพลตฟอร์ม Cloudflare
ปรับปรุงการจัดตารางงานและการแยก execution ของ Workers Runtime
- ก่อนหน้านี้มีการใช้อัลกอริทึมที่ route ทราฟฟิกไปยัง isolation ที่ "อุ่นแล้ว" (อินสแตนซ์ที่พร้อมประมวลผลได้รวดเร็ว) เพื่อเพิ่มประสิทธิภาพด้าน latency และ throughput ของแอปขนาดใหญ่ แต่กลับไม่มีประสิทธิภาพสำหรับ workload ที่ใช้ CPU หนัก
- เมื่อมีคำขอที่กิน CPU สูงจำนวนมากเข้ามาพร้อมกัน คิวจะยาวขึ้นและเกิด latency สูง ซึ่งเห็นได้ชัดจากเบนช์มาร์ก
- Cloudflare Workers คิดค่าบริการตาม เวลาใช้งาน CPU ดังนั้นเวลาที่รออยู่ในคิว (รอให้ isolation พร้อม) จะไม่ถูกคิดค่าใช้จ่าย
- มีการ ปรับอัลกอริทึมใหม่ เพื่อให้ตรวจจับ workload ที่ใช้ CPU สูงได้เร็วขึ้น และสร้าง isolation ใหม่ได้รวดเร็วขึ้น
- จึงรองรับได้มีประสิทธิภาพทั้ง workload แบบ I/O-bound และ CPU-bound และได้ deploy ทั่วโลกโดยมีผลทันที
ปรับปรุงการตั้งค่า V8 garbage collector
- ระหว่างการทดสอบพบว่าปัญหาเรื่อง garbage collection และการจัดการหน่วยความจำ ของ JavaScript ส่งผลอย่างมากต่อประสิทธิภาพที่ลดลง
- เดิมทีขนาดของพื้นที่หน่วยความจำ "young generation" ในเอนจิน V8 ถูกตรึงไว้อย่างจำกัดเกินไป (ตั้งตามคำแนะนำในอดีตที่ 128MB)
- ใน V8 รุ่นใหม่ วิธีตั้งค่าเช่นนี้กลับทำให้เกิด GC ถี่เกินความจำเป็น
- จึงยกเลิกการจูนแบบแมนนวล และเปิดให้ V8 จัดสรรหน่วยความจำแบบไดนามิกตาม heuristic ของตัวเอง
- พบว่าช่วยเพิ่มประสิทธิภาพในเบนช์มาร์กได้ประมาณ 25% และนำไปใช้กับ Workers ทั้งหมดแล้ว
การปรับแต่งประสิทธิภาพ Next.js บน OpenNext
ตัดการจัดสรรและการคัดลอกหน่วยความจำที่ไม่จำเป็น
- จากการวิเคราะห์พบว่า 10–25% ของเวลาประมวลผลคำขอถูกใช้ไปกับการคืนหน่วยความจำ (GC)
- ใน OpenNext, Next.js และ React มีรูปแบบโค้ดจำนวนมากที่คัดลอก data buffer ภายในมากเกินไป
- มีทั้งการคัดลอกข้อมูล output ของสตรีมทั้งหมดโดยไม่จำเป็น และการคัดลอกข้อมูลจำนวนมากผ่าน
Buffer.concat เพียงเพื่อวัดความยาวอย่างง่าย
- ประเด็นที่เกี่ยวข้องกำลังถูกปรับปรุงผ่าน Pull Request ในรีโพซิทอรี OpenNext
- มีแผนจะพัฒนาต่อเพื่อให้เกิดการปรับปรุงประสิทธิภาพร่วมกันได้ทั้งแพลตฟอร์ม
ปรับปรุง stream adapter ที่ไม่มีประสิทธิภาพ
- Workers ออกแบบมาบน Web Streams API ขณะที่ Next.js อิงกับ stream API ของ Node.js เป็นหลัก จึงต้องมี adapter สำหรับแปลงระหว่างกัน
- เดิมมีการใช้ adapter ซ้อนกันโดยไม่จำเป็น ทำให้เกิด overhead จากการคัดลอกหน่วยความจำและการบัฟเฟอร์จำนวนมาก
- จึงย่อโค้ดให้เหลือเพียง
ReadableStream.from(chunks) เพื่อตัดการคัดลอกระหว่างทาง
- จากโครงสร้างสตรีมค่าเดี่ยวเป็นหลักเดิม (highWaterMark=1) ได้ปรับเป็น byte stream (เช่น highWaterMark=4096) เพื่อเพิ่มประสิทธิภาพการจัดการข้อมูลขนาดใหญ่
- ในอนาคต แพตช์ปรับปรุงการจัดการสตรีมของ Next.js และ React ก็จะถูกส่ง upstream ไปยังแพลตฟอร์มต้นทางด้วย
ปัญหาประสิทธิภาพของ JSON.parse() และแพตช์สำหรับ V8
- ทั่วทั้ง Next.js และ React มีการเรียกใช้ JSON.parse() พร้อมตัวเลือก reviver มากเกินไป (มากกว่า 100,000 ครั้ง)
- ในมาตรฐาน ECMAScript ล่าสุด reviver สามารถรับอาร์กิวเมนต์ตัวที่สาม (source context) ได้ ทำให้ประสิทธิภาพยิ่งแย่ลงเพิ่มเติม (เป็นปัญหาร่วมใน Firefox, Chrome ฯลฯ)
- ทีม Cloudflare Workers ได้ ส่งแพตช์ให้เอนจิน V8 (ปรับปรุงประสิทธิภาพ 33%) ซึ่งจะเป็นประโยชน์ต่อทั้ง Node.js, เบราว์เซอร์ Chrome, Deno และระบบนิเวศทั้งหมด
ปัญหาประสิทธิภาพของฟังก์ชันตรีโกณมิติใน Node.js
- นอกเหนือจากเบนช์มาร์กของ Theo ยังมีรายงานว่าในเบนช์มาร์กที่เรียกใช้ฟังก์ชันตรีโกณมิติทางคณิตศาสตร์ (เช่น sin, cos) ซ้ำ ๆ Cloudflare Workers เร็วกว่า 3 เท่า
- สาเหตุมาจาก Node.js ยังไม่ได้รองรับ เส้นทางการทำงานของฟังก์ชันตรีโกณมิติรุ่นใหม่/ความเร็วสูงที่ V8 มีให้ (compile-time flag)
- Cloudflare Workers เปิดใช้ flag นี้เป็นค่าเริ่มต้นโดยบังเอิญ และได้ส่งแพตช์เป็น Pull Request ให้กับ Node.js
- เนื่องจากปัญหานี้เป็นเรื่องร่วมของระบบนิเวศโอเพนซอร์ส จึงคาดว่าจะช่วยให้ความเร็วโดยรวมมีเสถียรภาพมากขึ้นเมื่อสะท้อนไปถึง AWS Lambda และ Vercel ด้วย
ข้อจำกัดของการออกแบบ/การวัดเบนช์มาร์ก และบทเรียนที่ได้
- เบนช์มาร์กส่วนใหญ่ใช้วิธี วัดเวลาในการตอบสนอง (latency) จากฝั่งไคลเอนต์ โดยไม่ได้วัดเวลาใช้งาน CPU ฝั่งเซิร์ฟเวอร์โดยตรง
- เส้นทางเครือข่าย ตำแหน่งดาต้าเซ็นเตอร์ รุ่นของฮาร์ดแวร์ และการใช้ทรัพยากรร่วมกันแบบ multi-tenancy ล้วนเป็น ตัวแปรที่เทียบกันตรง ๆ ไม่ได้ และอาจส่งผลต่อผลลัพธ์
- หากวัดเพียงเวลาไปถึงไบต์แรกของการตอบสนอง (TTFB) ก็อาจสะท้อนเวลาเรนเดอร์/ส่งข้อมูลทั้งหมดได้ไม่ครบ และถ้าเปลี่ยนเป็น TTFL ก็อาจไวต่อความต่างของความเร็วเครือข่ายมากขึ้น
- ยังมี noise แบบชั่วคราวหรือมีความเชื่อมโยงกัน จากความหลากหลายของฮาร์ดแวร์/ซอฟต์แวร์ฝั่งเซิร์ฟเวอร์ รวมถึงดวงในการจัดสรรอินสแตนซ์
- แต่กระบวนการทำให้สภาพแวดล้อมและ workflow สำหรับเบนช์มาร์กแม่นยำขึ้น รวมถึงการทำให้ตัวแปรต่าง ๆ ใกล้เคียงกัน ก็ช่วยค้นพบจุดปรับปรุงที่ใช้ได้จริง และเป็นประโยชน์ต่อทั้งแพลตฟอร์มของตนเองและของผู้อื่น
ปัญหาที่พบระหว่างการทดลองในเบนช์มาร์กและการตั้งค่าสภาพแวดล้อม
- มีความเสี่ยงต่อการตีความผลผิดจากปัจจัยอย่างการไม่ใช้การตั้งค่า force-dynamic ของ Next.js, logic การจัดการแคช และความแตกต่างของวิธีการสตรีมการตอบสนอง
- ในเบนช์มาร์ก React SSR มีการปล่อยให้ตัวแปรสภาพแวดล้อม NODE_ENV ไม่ถูกตั้งค่า จึงทำงานใน dev mode และให้ผลช้ากว่าความเป็นจริง
- ข้อผิดพลาดเหล่านี้ได้รับการแก้ไขด้วยการระบุตัวแปรสภาพแวดล้อมอย่างชัดเจน
แผนต่อจากนี้และบทสรุป
- การปรับปรุงประสิทธิภาพหลายด้านของ Cloudflare Workers Runtime ถูกนำไปใช้เต็มรูปแบบแล้ว ผู้ใช้จะได้รับประโยชน์โดยไม่ต้องทำอะไรเพิ่มเติม
- มีการส่ง Pull Request ให้ Theo เพื่อรวมโค้ดทดสอบและการปรับแต่ง OpenNext ที่เกี่ยวข้อง
- จะมีการปรับปรุงเพิ่มเติมเพื่อปิดช่องว่างระหว่าง OpenNext กับ Next.js บน Vercel
- มีนโยบายเดินหน้าปรับปรุงอัลกอริทึมการจัดตารางงาน รวมถึงอัปเกรดเอนจินโอเพนซอร์ส (V8, Node.js) และมีส่วนร่วมกับชุมชนอย่างต่อเนื่อง
- มีแผนใช้เบนช์มาร์กและการทำ profiling ที่ดีกว่าเดิมเพื่อค้นหาปัญหาแฝงได้เร็วขึ้น พร้อมรักษาวัฒนธรรมการปรับแต่งและแบ่งปันความรู้ต่อไป
เอกสารอ้างอิงและลิงก์เพิ่มเติม
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เป็นเรื่องดีที่ CF พยายามปรับปรุงผลิตภัณฑ์จริง ๆ แต่การเปลี่ยนแปลงเกิดขึ้นเร็วเกินไปจนตามไม่ทัน และหลายครั้งการเปิดตัวก็มาเร็วกว่าความสมบูรณ์ของตัวผลิตภัณฑ์ เช่น R2 Data Catalog ก็ยังขาดการรองรับ Iceberg v3, Wrangler ก็เปลี่ยนไปมากในเวลาแค่ไม่กี่เดือน, ส่วน Pages ก็ดูเหมือนจะหายไปในไม่ช้า ทำให้การย้ายไป Workers Assets ยุ่งยากมาก การตั้งค่าที่ใช้ได้ดีใน Wrangler 3 กลับใช้กับ Wrangler 4 ไม่ได้อย่างถูกต้อง และรู้สึกว่าใน Wrangler 5 ก็น่าจะมี interaction model ใหม่ออกมาอีก
สำหรับคำพูดที่ว่า "Pages ดูเหมือนจะหายไป" ที่จริง CF ได้พูดไว้ในโพสต์ชุมชนว่าจะไม่เลิก Pages จนกว่า Workers จะมีความสามารถเทียบเท่า Pages โพสต์ที่เกี่ยวข้อง หาข้อมูลอย่างเป็นทางการเรื่องการยกเลิก Pages ได้ยาก และทั้ง pages.cloudflare.com กับ developer.cloudflare.com/pages ก็ยังดำเนินการอย่างคึกคัก โพสต์หนึ่งบน Reddit อาจสื่อถึงการย้ายออกจาก Pages แต่ในลิงก์นั้นก็ไม่มีการกล่าวอย่างเป็นทางการว่าจะยุติบริการ เห็นด้วยกับความเห็นส่วนที่เหลือ และโดยเฉพาะประเด็นนี้ทำให้ประหลาดใจ ลิงก์อ้างอิง Reddit
ผมไม่เห็นด้วยกับคำพูดที่ว่าการตั้งค่าของ Wrangler 3 ใช้งานต่อใน Wrangler 4 ไม่ได้เลย รูปแบบการตั้งค่าใน Wrangler 4 ไม่ได้เปลี่ยนแปลงแม้แต่น้อย และเหตุผลของการขึ้นเมเจอร์เวอร์ชันก็แทบไม่มีผลกับผู้ใช้ 99.99% ดูรายการเปลี่ยนแปลงได้ที่นี่ แค่การอัปเมเจอร์เวอร์ชันอย่างเดียวก็สร้างความไม่สะดวกมากจนผมเองก็เคยคัดค้านภายใน แต่ทีมระมัดระวังเพราะมีเคสยกเว้นที่พบได้น้อยมาก ในอนาคตเราจะพัฒนาวิธีรับมือปัญหาแบบนี้โดยไม่ต้องอัปเมเจอร์เวอร์ชัน เช่น รองรับหลายเวอร์ชันของ esbuild ควบคู่กัน ในฝั่งรันไทม์เราให้ความสำคัญกับ backward compatibility อย่างมาก บล็อกเกี่ยวกับ backward compatibility Pages ไม่ได้หายไปไหน และ Workers Assets ก็เป็นเวอร์ชันที่ยืดหยุ่นกว่าของ Pages ถ้าไม่ได้ต้องการฟีเจอร์เพิ่มเติมก็ไม่จำเป็นต้องย้าย และในอนาคตจะมีการย้ายแบบอัตโนมัติด้วย
นี่เป็นการเตือนอีกครั้งว่าเวลาสร้างโปรเจกต์สำคัญหรือระบบที่จะต้องดูแลต่อเนื่องหลายปี ก็ควรเลือกใช้ 'เทคโนโลยีที่ธรรมดาแต่น่าเชื่อถือ (boring tech)'
อยากรู้ว่าเห็นข้อมูลจากไหนว่าคิดว่า "Pages กำลังจะหายไป" เพราะผมเองก็ใช้งาน Pages กับหลายโปรเจกต์ได้ดีมาก
เรื่องที่น่าสนใจคือประเด็นถกเถียงนี้เริ่มจากคำกล่าวอ้างว่า Cloudflare เร็วกว่า Vercel จากนั้นมีคนที่รู้จริงทำ benchmark แล้วพบว่าผลจริงกลับตรงกันข้าม สุดท้าย Cloudflare ก็ลงมือปรับปรุงจริงเพื่อเพิ่มประสิทธิภาพ
ชอบมากที่บทความนี้ไม่ได้ตำหนิคู่แข่ง แต่กลับค้นหาและเน้นจุดที่ควรปรับปรุง อีกทั้งยังมีการพัฒนาในส่วนของ OpenNext ที่ผู้ให้บริการรายอื่นก็นำกลับไปใช้ต่อได้ด้วย ซึ่งน่าประทับใจมาก
กำลังย้าย NextJS ที่โฮสต์บน Vercel ไปเป็น Astro/React บน Cloudflare อยู่ เรื่องที่น่าทึ่งคือแม้จะเรนเดอร์เว็บแอปทุกคำขอที่ “edge” เวลาตอบสนองก็อยู่ราว 100-200ms ซึ่งเร็วแทบไม่ต่างจากหน้าแบบสแตติกเลย ช่วงไม่กี่สัปดาห์ที่ผ่านมา การปรับปรุงของ Cloudflare Worker ก็รู้สึกได้ชัดมาก ทั้ง cold start ที่แทบหายไป และความเร็วตอบสนองที่เสถียรกว่ามาก ลิงก์เว็บแอประหว่างพอร์ต
น่าสนใจที่วิดีโอจากยูทูบเบอร์ที่ไม่ได้มีขนาดใหญ่มาก สามารถกระจายต่อได้อย่างมีประสิทธิภาพในลักษณะนี้ และนำไปสู่การปรับปรุงที่มีความหมายจริงจากฝั่ง Cloudflare รวมถึงการแก้ปัญหาบนแพลตฟอร์ม
เป็น PR ที่ทำออกมาได้ดีมาก ขอชื่นชมคนที่เตรียมโพสต์นี้
ในฐานะลูกค้า cf มานาน ต้องบอกว่า cf ไม่ได้เก่งแค่การเขียนบล็อกหรือทำโอเพนซอร์ส แต่ยังเป็นบริษัทอินฟราที่ให้การซัพพอร์ตดีที่สุดด้วย สมาชิกทีม (รวมถึง kenton) มักเข้ามาช่วยผู้ใช้โดยตรงใน Discord หรือรับฟังฟีดแบ็กอยู่บ่อย ๆ และบั๊กหรือปัญหาต่าง ๆ ก็สามารถคุยกับวิศวกรที่รับผิดชอบได้โดยตรงจริง ๆ แม้แต่ PR หรือคำขอฟีเจอร์ที่ผมเสนอเองก็เคยถูกนำไปใช้เร็วมากโดยแทบไม่มีขั้นตอนอะไรเลย ทั้งที่จ่ายในราคาถูกกว่าบริษัทใหญ่รายอื่นมาก แต่กลับได้การซัพพอร์ตที่ดีกว่ามาก
ขอบคุณ! PR และโพสต์นี้ถูกวางแผนและจัดทำโดยวิศวกรจากทีม Workers แบบ 100% (ผมเองก็มีส่วนร่วมด้วย)
ชอบมากทั้งวิธีเขียนโพสต์นี้ การแยกย่อยข้อมูล และการพูดคุยกันอย่างเปิดเผย ทำให้ความเชื่อมั่นที่มีต่อทีม Cloudflare workers เพิ่มขึ้น
ผมคิดว่า SvelteKit เร็วมาก ส่วน Next.js ค่อนข้างช้าเมื่อเทียบกัน
เป็นข้อสรุปที่ฟังดูสมเหตุสมผล
หวังว่าเฟรมเวิร์กที่ใช้งานได้จริงอย่าง SvelteKit, Astro และ TanStack จะมาแทนที่ความซับซ้อนของ NextJS ในไม่ช้า
กรณีแบบนี้แสดงให้เห็นว่าทำไมเราจึงต้องมีการแข่งขันและ benchmark ที่เป็นอิสระ เพราะมันทำให้บริการที่ประสิทธิภาพยังไม่ดีพอต้องพยายามปรับปรุง
แต่ความพยายามแบบนั้นก็จะได้ผลก็ต่อเมื่อใส่ใจตัวผลิตภัณฑ์จริง ๆ
benchmark อิสระมีอยู่แล้ว
ประทับใจที่ Cloudflare ยอมรับผลลัพธ์อย่างถ่อมตัวและนำไปปรับปรุงอย่างสร้างสรรค์
เป็นบทความที่ดีมาก เพราะโฟกัสที่เนื้อหาและไม่กล่าวโทษใคร แต่ก็แปลกใจที่ Cloudflare ไม่ได้มอนิเตอร์และปรับค่าอย่างพวก generation size ล่วงหน้าให้ดีกว่านี้ เพราะในโลกของการจูนประสิทธิภาพ JVM การตั้งค่า generation size ถือเป็นพื้นฐาน