นักพัฒนาคนหนึ่งค้นพบว่าการเชื่อมต่อ Chrome DevTools debugger ผ่าน Playwright หรือ Puppeteer ทำให้การอัปโหลดผ่าน fetch ช้าลงกว่า 20 เท่า ซึ่งเปลี่ยนการทดสอบประสิทธิภาพ (performance benchmarks) ในชีวิตประจำวันให้กลายเป็นข้อมูลที่บิดเบือน

ปริศนาที่เป็นจุดเริ่มต้นของการสืบสวน

Coffer ซึ่งเป็นที่เก็บไฟล์บนเบราว์เซอร์ที่ทำการเข้ารหัสข้อมูลก่อนส่งไปยังเซิร์ฟเวอร์ มักจะทำการอัปโหลดข้อมูลระดับ gigabit ผ่านเครือข่ายท้องถิ่นเป็นประจำ เมื่อทีมงานวัดความเร็วในการอัปโหลด พวกเขาพบความแตกต่าง: การดาวน์โหลดนั้นใช้แบนด์วิดท์จนเต็มประสิทธิภาพ แต่การอัปโหลดกลับช้าลงเหลือเพียงประมาณ 1 ใน 8 ของแบนด์วิดท์ที่มีอยู่ ความคลาดเคลื่อนนี้ทำให้เกิดการแก้ไขโค้ดถึงสามรอบแต่ก็ไม่เห็นความเปลี่ยนแปลง จนกระทั่ง "การแก้ไข" ครั้งที่สี่ดูเหมือนจะช่วยให้ความเร็วเพิ่มขึ้นอย่างมหาศาล—แต่กลับหายไปทันทีเมื่อถอด debugger ออก

สิ่งที่ทีมงานลองทำเป็นอันดับแรก

เหล่าวิศวกรได้ไล่ตรวจสอบสาเหตุที่น่าสงสัยตามปกติ:

  • Chunk size – การเพิ่มขนาดบล็อกจาก 16 MiB เป็น 32 MiB ไม่ได้ทำให้ throughput เปลี่ยนแปลง
  • Pipelining – การทำ encryption ของบล็อกถัดไปควบคู่ไปกับการอัปโหลดบล็อกปัจจุบัน ช่วยให้ความเร็วเพิ่มขึ้นเพียง 13% ซึ่งน้อยจนแยกไม่ออกจากค่าความคลาดเคลื่อนในการวัด (measurement noise)
  • Concurrency – การรันการอัปโหลดหลายรายการพร้อมกัน (parallel) ก็ยังติดเพดานความเร็วรวมเท่าเดิม ซึ่งบ่งชี้ว่ามีข้อจำกัดบางอย่างในระดับ global

การปรับเปลี่ยนเหล่านี้ไม่มีวิธีใดเลยที่อธิบายถึงความล่าช้าที่ลดลงถึง 8 เท่าได้

การเปรียบเทียบแบบตัวต่อตัวที่น่าประหลาดใจ

เพื่อแยกปัญหาให้ชัดเจน ทีมงานได้เปลี่ยนการ implementation ของฝั่ง client โดยการใช้ .NET HttpClient พวกเขาสามารถทำความเร็วได้ถึง 700 Mbps บนเครือข่ายเดียวกัน แต่คำขอ (request) เดียวกันที่ส่งผ่าน fetch() API ของ Chromium กลับหยุดชะงักอยู่ที่ 140 Mbps ความแตกต่างที่ชัดเจนนี้ชี้ให้เห็นว่า network stack ของเบราว์เซอร์คือตัวการ—จนกระทั่งการทดลองถัดไปพิสูจน์ให้เห็นเป็นอย่างอื่น

ต้นทุนที่ซ่อนอยู่ของ debugger

Playwright และ Puppeteer ควบคุม Chrome ผ่าน Chrome DevTools Protocol (CDP) ซึ่งโปรโตคอลนี้จะเชื่อมต่อ debugger เข้ากับกระบวนการของเบราว์เซอร์ ทำให้สามารถเข้าถึง network events, DOM snapshots และ console logs ได้ ทีมงานจึงทำการทดสอบที่เจาะจง: การเรียกใช้ fetch() เพื่อส่ง payload แบบ Uint8Array โดยครั้งหนึ่งเชื่อมต่อกับ CDP debugger และอีกครั้งหนึ่งไม่ได้เชื่อมต่อ

  • เมื่อเชื่อมต่อ debugger: 113 Mbps

การมี debugger อยู่ทำให้ความเร็วในการอัปโหลดลดลงมากกว่า 20 เท่า การทดสอบด้วยตนเองในหน้าต่าง Edge ปกติ—โดยไม่ได้เชื่อมต่อ debugger—สามารถทำความเร็วได้ถึง 600+ Mbps ซึ่งยืนยันว่าตัวเบราว์เซอร์เองสามารถจัดการกับปริมาณข้อมูลได้เมื่อไม่มีสิ่งกีดขวาง

การปรับปรุงที่เกิดขึ้นจริงแต่เพียงเล็กน้อย

แม้ว่า debugger จะเป็นสาเหตุหลักของความล่าช้า แต่ทีมงานก็ยังค้นพบการปรับแต่ง (optimization) ที่ใช้งานได้จริง นั่นคือการเปลี่ยน request body จาก Uint8Array เป็น Blob ซึ่งช่วยเพิ่มความเร็วของ Chromium ได้ประมาณ 30% แม้จะเป็นการปรับแต่งที่มีประโยชน์ แต่ก็ยังห่างไกลจากความเร็วที่เพิ่มขึ้นแบบ "ปาฏิหาริย์" ตามที่คาดหวังไว้ในตอนแรก

ทำไมเรื่องนี้จึงสำคัญต่อวิศวกร

  • เครื่องมือวัดผลอาจหลอกเราได้: เครื่องมือวัดประสิทธิภาพที่ใช้ควบคุมเบราว์เซอร์แบบอัตโนมัติ ตัวมันเองก็เป็นส่วนหนึ่งของห่วงโซ่การวัดผลเช่นกัน
  • Benchmark ที่ดูเป็นไปไม่ได้ควรได้รับการตรวจสอบความสมเหตุสมผล (sanity check): หากตัวเลขแตกต่างจากขีดความสามารถของเครือข่ายอย่างมาก สภาพแวดล้อมในการวัดผลควรเป็นผู้ต้องสงสัยรายแรก
  • ผลลัพธ์ที่เป็นศูนย์ (null result) ก็มีค่า: การยืนยันว่าการเปลี่ยนแปลงบางอย่างไม่ได้ส่งผลใดๆ ช่วยป้องกันการเสียเวลาไปกับการไล่ตามบั๊กที่ไม่มีอยู่จริง (phantom bugs)
  • การควบคุมด้วยตนเองคือการประกันความเสี่ยงที่ราคาถูก: การรันการทำงานแบบเดียวกันในหน้าต่างเบราว์เซอร์ปกติสามารถช่วยเผยให้เห็น overhead ของเครื่องมือวัดผลที่ซ่อนอยู่ได้

มุมมองที่ต่างออกไป: เมื่อ debugger เป็นสิ่งที่ขาดไม่ได้

Debugger ช่วยให้เรามองเห็นพฤติกรรมของหน้าเว็บ, error traces และ network timelines ซึ่งหากไม่มี debugger ก็จะไม่สามารถเข้าถึงข้อมูลเหล่านี้ได้ สำหรับการทดสอบ regression, การตรวจสอบความปลอดภัย (