วิธีที่ Roblox ขยายแพลตฟอร์ม Kafka ของตนให้รองรับข้อความมากกว่า 18 ล้านล้านข้อความต่อวัน
การเพิ่มประสิทธิภาพแบบครบวงจรเพื่อระบบที่มีประสิทธิภาพ

ในสองปีที่ผ่านมา เราได้ขยายแพลตฟอร์มคิวข้อความภายในให้เติบโตมากกว่า 2.5 เท่าทุกปี จนถึงกว่า 18 ล้านล้านข้อความต่อวัน ปัจจุบันเราประมวลผลข้อความมากกว่า 300 ล้านข้อความต่อวินาที พร้อมรักษาความพร้อมใช้งานที่ 99.995% แพลตฟอร์มคิวข้อความนี้ ซึ่งสร้างขึ้นบน Apache Kafka ได้กลายเป็นส่วนสำคัญในกระบวนการส่งข้อมูลภายในบริษัท มันรองรับงานต่าง ๆ เช่น เหตุการณ์วิเคราะห์ข้อมูล (analytics events), กระบวนการตรวจสอบเนื้อหา (moderation pipelines), ระบบจับคู่ (matchmaking systems) และอีกมากมาย ตัวเลขเหล่านี้น่าตื่นเต้น แต่เรื่องราวที่สำคัญกว่าคือวิธีที่เราไปถึงจุดนี้
ผลกระทบจากการเพิ่มประสิทธิภาพแบบครบวงจร
ความเข้าใจผิดที่พบบ่อยเกี่ยวกับระบบขนาดใหญ่คือว่า ความจุเป็นปัญหาด้านฮาร์ดแวร์เป็นหลัก แต่จากประสบการณ์ของเรา การปรับปรุงที่มีผลกระทบมากที่สุดเกิดจากการมองความสามารถในการขยายตัว (scalability) เป็นปัญหาทางวิศวกรรมแบบ end-to-end
ทีมต่าง ๆ ใน Roblox ได้สร้างแพลตฟอร์มต่าง ๆ บนคิวการส่งข้อความ รวมถึงกระบวนการฝึกอบรม AI, การสกัดข้อมูลแบบสตรีม และระบบเทเลเมทรี การร่วมมือกับเจ้าของแพลตฟอร์มเหล่านี้ช่วยให้เราสามารถรับรองการปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุด เช่น การเปิดใช้งานการประมวลผลแบบกลุ่ม (batching) และการบีบอัดข้อมูล ซึ่งส่งผลให้เกิดระบบที่มีประสิทธิภาพ โดยไม่ต้องให้ผู้ใช้ปลายทางต้องมีส่วนร่วมในการตั้งค่าการกำหนดค่าอย่างละเอียด

การประสานงานกับทีมต่าง ๆ ที่ใช้แพลตฟอร์มนี้เกี่ยวกับลำดับความสำคัญทางธุรกิจ การวัดผล และเกณฑ์ความสำเร็จ เป็นเรื่องสำคัญอย่างยิ่ง และช่วยส่งเสริมการร่วมมือระหว่างทีมต่าง ๆ ตัวชี้วัดหลักจะแตกต่างกันไปตามชั้นต่าง ๆ ของระบบ: ในขณะที่เราอาจปรับแต่งเซิร์ฟเวอร์ Kafka เพื่อจัดการปริมาณข้อมูลขนาดใหญ่ได้อย่างมีประสิทธิภาพด้วยเครื่องน้อยลง ผู้ใช้ปลายทางอาจต้องการปรับแต่งเพื่อลดความล่าช้าแบบ end-to-end ลงถึงระดับมิลลิวินาที หรือเพื่อลดเวลาทั้งหมดที่งาน batch ใช้ในการเขียนข้อมูลให้เสร็จสิ้น
ไม่ทุกการรับส่งข้อมูลมีค่าเท่ากัน
บทเรียนสำคัญหนึ่งที่เราได้เรียนรู้คือ แพลตฟอร์มขนาดใหญ่จะมีความน่าเชื่อถือมากขึ้นเมื่อไม่ปฏิบัติต่อทุกการรับส่งข้อมูลอย่างเท่าเทียมกัน บางงานโหลดต้องการความหน่วงต่ำมากและการป้องกันที่แข็งแกร่งในช่วงที่มีการรับส่งข้อมูลเพิ่มขึ้นอย่างรวดเร็ว เช่น ระบบจับคู่ของเรา ซึ่งทำงานด้วยความหน่วงจากต้นทางถึงปลายทางต่ำกว่า 10 มิลลิวินาที ส่วนอื่นมีปริมาณสูงมากแต่ทนต่อความล่าช้าได้ดีกว่า เช่น ท่อการรับข้อมูลเหตุการณ์วิเคราะห์ของเรา ซึ่งสามารถทำงานได้อย่างราบรื่นแม้มีความล่าช้าจากต้นถึงปลายหลายวินาที หากจัดการทั้งสองประเภทนี้อย่างเท่ากัน การจราจรที่มีปริมาณสูงที่สุดอาจทำให้การจราจรที่สำคัญต่อธุรกิจถูกเบียดออกไป โดยเฉพาะในช่วงโหลดสูงสุด
เพื่อแก้ไขปัญหานี้ เราจัดกลุ่มการรับส่งข้อมูลตามความต้องการของกรณีการใช้งาน เพื่อให้มั่นใจว่าแต่ละกรณีได้รับการสนับสนุนอย่างเหมาะสม การแลกเปลี่ยนระหว่างความล่าช้าจากต้นทางถึงปลายทาง (end-to-end latency) และปริมาณการรับส่งข้อมูล (throughput) ใน Kafka เป็นหนึ่งในปัจจัยหลักที่ต้องพิจารณาเมื่อตั้งค่ากรณีการใช้งานใหม่ สิ่งนี้เป็นจริงตลอดเส้นทางข้อมูลจากผู้ผลิต (producer) ไปยังเซิร์ฟเวอร์ (server) และผู้บริโภค (consumer): แต่ละส่วนมีการตั้งค่าที่สามารถปรับแต่งเพื่อเพิ่มประสิทธิภาพให้เหมาะสมกับปริมาณการรับส่งข้อมูลหรือความล่าช้า การตั้งค่าด้านไคลเอนต์สามารถปรับแต่งได้สำหรับกรณีการใช้งานแต่ละกรณี แต่การตั้งค่าด้านเซิร์ฟเวอร์มีความยืดหยุ่นน้อยกว่า เนื่องจากไม่สามารถรักษาคลัสเตอร์เฉพาะสำหรับกรณีการใช้งานแต่ละกรณีที่แตกต่างกันได้
เรานำแนวทางนี้ในการปรับแต่งคลัสเตอร์แต่ละตัวให้เหมาะสมกับกรณีการใช้งานที่มันให้บริการ ไปใช้จนถึงชั้นฮาร์ดแวร์
ฮาร์ดแวร์ยังคงเป็นปัจจัยสำคัญ
เราเริ่มต้นด้วยการรัน Kafka บนเครื่องที่ใช้ HDD RAID10 เมื่อปริมาณการรับส่งข้อมูลเพิ่มขึ้น ความจุ I/O ของดิสก์บนเครื่องของเราจึงกลายเป็นจุดคอขวดหลักที่จำกัดปริมาณข้อมูลผ่าน Kafka ต่อเซิร์ฟเวอร์ เพื่อแก้ไขปัญหานี้ เราได้ดำเนินการปรับแต่งสองประการ คือ เปลี่ยนให้โบรกเกอร์ใช้ SSD ให้มากที่สุดเท่าที่จะทำได้ ซึ่งช่วยย้ายจุดคอขวดจากความจุ I/O ของดิสก์ไปยังปริมาณข้อมูลผ่านเครือข่ายหรือพื้นที่ดิสก์ (เนื่องจาก SSD ที่เราจัดหามามีพื้นที่ดิสก์น้อยกว่า) การปรับเปลี่ยนนี้ทำให้ปริมาณการอ่าน/เขียนดิสก์เพิ่มขึ้น 10 เท่า นอกจากนี้ เรายังเริ่มเปลี่ยน HDD ที่เหลือจาก RAID10 เป็น RAID0 ซึ่งทำให้ความจุการอ่าน/เขียนดิสก์เพิ่มขึ้นประมาณสองเท่า เนื่องจากการเขียนข้อมูลใน RAID0 ต้องการการเขียนลงดิสก์เพียงครั้งเดียว แทนที่จะเป็นสองครั้ง
ข้อแลกเปลี่ยนคือ เนื่องจากการเขียนสำเนาข้อความแต่ละฉบับจะถูกเขียนลงดิสก์เพียงหนึ่งแผ่นต่อบрокเกอร์ แทนที่จะเป็นสองแผ่น การเปลี่ยนจาก RAID10 เป็น RAID0 จึงอาจมาพร้อมกับความเสี่ยงด้านความน่าเชื่อถือ เพื่อรักษาความน่าเชื่อถือ เราได้ทำงานกับกรณีการใช้งานสำคัญเพื่อเพิ่มการยืนยัน (acks) และการทำสำเนาข้อมูล รวมถึงการทำสำเนาไปยังหลายภูมิภาค
การใช้ประโยชน์จากความสามารถในการจัดเก็บข้อมูลและอัตราผ่านของเครื่องแต่ละเครื่องอย่างเต็มที่นั้นต้องการงานเพิ่มเติม เราได้ปรับแต่งการจัดสรรหน่วยความจำและการจัดการแคชหน้าเพจ เพื่อให้ Kafka สามารถบัฟเฟอร์ข้อความและให้บริการการอ่านหรือข้อมูลล่าสุดจากหน่วยความจำ โดยจะเข้าถึงดิสก์และใช้ IOPS ที่มีค่าเฉพาะเมื่อจำเป็น เช่น เมื่ออ่านข้อมูลในอดีต
เรายังดำเนินการทดสอบประสิทธิภาพเพื่อทำความเข้าใจขีดจำกัดของฮาร์ดแวร์และกำหนดขีดจำกัดระดับซอฟต์แวร์ที่เราจำเป็นต้องตั้งขึ้น ในทางปฏิบัติ นี่หมายถึงการตั้งค่าการจำกัดความเร็วการอ่านและเขียนของ Kafka broker ซึ่งแตกต่างกันไปตามฮาร์ดแวร์ที่คลัสเตอร์กำลังทำงานอยู่ เครื่องที่ใช้ SSD ของเราสามารถจัดการปริมาณข้อมูลผ่านได้สูงต่อ broker ในขณะที่เครื่องที่ใช้ HDD เหมาะสมกว่าสำหรับกรณีการใช้งานที่ต้องการเก็บรักษาข้อมูลประวัติศาสตร์ในระยะยาว
การพัฒนาเพื่อความน่าเชื่อถือ
แพลตฟอร์มคิวเป็นส่วนสำคัญของโครงสร้างพื้นฐานของ Roblox และการรักษาความน่าเชื่อถือจึงเป็นสิ่งสำคัญยิ่ง การทดสอบก่อนเปิดตัว รวมถึงการทดสอบสถานการณ์ความล้มเหลวอย่างต่อเนื่อง (บางครั้งเรียกว่าการฝึกซ้อมในวันเกม) มีบทบาทสำคัญในการยืนยันว่าโครงสร้างพื้นฐานของเราสามารถทนต่อรูปแบบความล้มเหลวต่าง ๆ ได้ เช่น ความล้มเหลวของโฮสต์เดียว ความล้มเหลวของแร็ก ปัญหาเครือข่าย และอื่น ๆ อีกมากมาย แพลตฟอร์มการทดสอบความวุ่นวายภายในของทีมความน่าเชื่อถือของเราได้ช่วยให้กระบวนการจำลองสถานการณ์ความล้มเหลวต่าง ๆ เป็นไปได้อย่างมีประสิทธิภาพ การทดสอบเหล่านี้ช่วยระบุจุดที่ระบบสามารถปรับปรุงได้หรือจุดที่ระบบถดถอย และสร้างความเป็นเลิศในการดำเนินงาน โดยการตรวจสอบการแจ้งเตือนอย่างสม่ำเสมอ และรับรองว่าผู้ปฏิบัติงานคุ้นเคยกับขั้นตอนการจัดการปัญหาต่าง ๆ — แม้แต่ปัญหาที่เกิดขึ้นได้ยาก
เรายังได้ออกแบบและนำกระบวนการ failover มาใช้ ซึ่งสามารถกู้คืนฟังก์ชันการทำงานของคิวได้อย่างรวดเร็วสำหรับกรณีการใช้งานที่ขึ้นอยู่กับระบบดังกล่าว แม้ในกรณีที่คลัสเตอร์เดี่ยวเกิดความล้มเหลวอย่างสมบูรณ์ เราก็สามารถเปลี่ยนลูกค้าไปยังคลัสเตอร์ที่ทำงานปกติและมีความจุเพียงพอได้ เพื่อรับประกันว่าเรามีความจุที่ยังไม่ได้ใช้งานเพียงพอ เราจึงรักษาคลัสเตอร์ Kafka สำรองไว้ ซึ่งช่วยลดความเสี่ยงจากปัญหา "noisy neighbor" เมื่อย้ายการรับส่งข้อมูลระหว่างคลัสเตอร์ที่ทำงานอยู่
แผนภาพด้านล่างแสดงขั้นตอนระดับสูงสำหรับการดำเนินการ failover ก่อนอื่น ผู้ดำเนินการจะกำหนดขอบเขตของ failover จากนั้น จะสร้างหัวข้อ (topics) และรายการควบคุมการเข้าถึง (ACLs) บนคลัสเตอร์สแตนด์บายผ่านกระบวนการอัตโนมัติแบบกลุ่ม สุดท้าย ผู้ดำเนินการจะอัปเดตการตั้งค่าบริการ control plane ซึ่งลูกค้าจะตรวจสอบเพื่อกำหนดว่าจำเป็นต้องเชื่อมต่อใหม่หรือไม่ ภาพประกอบด้านล่างนี้ใช้สำหรับกรณีการใช้งานแบบเรียลไทม์ทั่วไป เรามีกลยุทธ์และเครื่องมือเพิ่มเติมสำหรับกรณีการใช้งานและสถานการณ์อื่น ๆ รวมถึงการสะท้อนหัวข้อ (topic mirroring) โดยใช้ Mirror Maker 2 เครื่องมือเติมข้อมูลย้อนหลัง (data backfill) และอื่น ๆ อีกมากมาย

Roblox มีข้อกำหนดด้านความปลอดภัยที่เข้มงวด ซึ่งถือเป็นส่วนสำคัญของความน่าเชื่อถือด้วย เราได้นำคุณสมบัติความปลอดภัยมาตรฐานมาใช้ เช่น การเข้ารหัสข้อมูลระหว่างการส่ง (ด้วย mTLS), การเข้ารหัสข้อมูลที่เก็บไว้ (ด้วยดิสก์ที่เข้ารหัส), ข้อกำหนดเกี่ยวกับระยะเวลาการเก็บรักษาข้อมูล, และการตรวจสอบสิทธิ์/การอนุญาตด้วย Kafka principals และ ACLs นอกจากนี้ เรายังหมุนเวียนข้อมูลรับรองของผู้ใช้โดยอัตโนมัติเพื่อเพิ่มความปลอดภัยให้ดียิ่งขึ้น
นอกจากนี้ เรายังพัฒนาระบบจัดการแบบกำหนดเองใน control plane เพื่ออำนวยความสะดวกในการอัปเดตแพตช์ระบบปฏิบัติการ (OS patching) และการถอดเครื่องออกจากแร็ค (rack drains) อย่างรวดเร็ว ระหว่างการอัปเดตแพตช์ระบบปฏิบัติการ เราจะเก็บรักษาข้อมูลไว้บนดิสก์ เพื่อให้เครื่องที่ได้รับการอัปเดตสามารถกลับสู่สภาพปกติได้อย่างรวดเร็วหลังจากกลับเข้าร่วมคลัสเตอร์ Kafka อีกครั้ง ด้วยวิธีนี้ เราสามารถอัปเดตแพตช์ระบบปฏิบัติการให้กับเครื่องทั้งหมดภายใน 30 วัน แม้ระบบจะยังคงขยายตัวต่อไป ด้วยเครื่องมืออย่าง Cruise Control เราทำการคัดลอกข้อมูลโดยคำนึงถึงการจัดวางแร็ก (rack-aware) เพื่อให้สามารถนำแร็กทั้งชุดออกจากระบบเพื่อดำเนินการบำรุงรักษาและอัปเดตแพตช์เครือข่ายได้โดยไม่ส่งผลกระทบต่อความพร้อมใช้งานของข้อมูล
ทางข้างหน้า
บทต่อไปของแพลตฟอร์มคิวของเราคือการทำให้แพลตฟอร์มนี้ง่ายขึ้นสำหรับนักพัฒนา พร้อมทั้งยังคงปรับปรุงความยืดหยุ่นและการแยกส่วนต่อไป เรากำลังลงทุนในแนวคิดเชิงนามธรรมใหม่ ๆ รวมถึงพร็อกซีผู้ผลิตและผู้บริโภค รวมถึงคอนเน็กเตอร์ที่ได้รับการจัดการอย่างเต็มรูปแบบ ระบบเหล่านี้จะช่วยแยกทีมพัฒนาแอปพลิเคชันออกจากความซับซ้อนของโครงสร้างพื้นฐานได้มากขึ้น และทำให้แพลตฟอร์มของเราง่ายต่อการนำไปใช้และจัดการในระดับใหญ่ ตัวอย่างเช่น โปรกซีผู้บริโภคจะเปิดใช้งานรูปแบบผู้บริโภคแบบแข่งขัน (competing consumer pattern) ซึ่งช่วยให้แพลตฟอร์มรองรับกรณีการใช้งานที่หลากหลายยิ่งขึ้น และแยกการขยายขนาดของโปรเซสเซอร์ออกจากกระบวนการขยายขนาดของพาร์ติชัน Kafka สิ่งนี้มีประโยชน์สำหรับกรณีการใช้งาน เช่น การประมวลผลคลื่นการแจ้งเตือนจำนวนมาก ซึ่งไม่จำเป็นต้องจัดลำดับ และสามารถได้รับประโยชน์จากการขยายจำนวนโปรเซสเซอร์ชั่วคราว โดยไม่ต้องผูกมัดกับเป้าหมายการส่งข้อมูลจากต้นทางถึงปลายทางที่ต้องบรรลุ
การขยายขนาด Kafka ไม่ใช่เพียงการขยายจำนวนและขนาดของคลัสเตอร์เท่านั้น แต่ยังเกี่ยวข้องกับการสร้างวินัยทางวิศวกรรม การสร้างนามธรรม และแบบจำลองการดำเนินงาน ที่ช่วยให้ Roblox สามารถเดินหน้าต่อไปในภารกิจของเรา เพื่อเชื่อมต่อผู้คนหนึ่งพันล้านคนทุกวัน ด้วยความหวังและความสุภาพเรียบร้อย
คำขอบคุณ: ขอขอบคุณ Danny Yuan และ George Li สำหรับการมีส่วนร่วมที่สำคัญต่อแพลตฟอร์มคิวของ Roblox


