- Discord ต้องการก้าวข้ามข้อจำกัดของโครงสร้างพื้นฐานการค้นหาเดิมที่อิงกับ Elasticsearch จึง ออกแบบสถาปัตยกรรมใหม่ทั้งหมดบน Kubernetes และปรับปรุงทั้งประสิทธิภาพและความเสถียรของการทำดัชนีข้อความอย่างก้าวกระโดด
- คิว Redis แบบเดิมมีความเสี่ยงต่อการสูญหายของข้อความ แต่เมื่อ เปลี่ยนมาใช้ PubSub จึงรับประกันการส่งต่อข้อความได้อย่างเสถียร พร้อมทั้งแยกจัดการข้อความตามคลัสเตอร์/ดัชนีเพื่อประมวลผลได้อย่างมีประสิทธิภาพ
- มีการนำสถาปัตยกรรม "cell" มาใช้ เพื่อกระจายงานไปยัง คลัสเตอร์ Elasticsearch ขนาดเล็กจำนวนมาก แก้ปัญหาโหนดโอเวอร์โหลดและปัญหาที่ไม่สามารถอัปเดตได้
- ข้อความ DM ส่วนตัวและข้อความในเซิร์ฟเวอร์ (guild) ถูก ทำดัชนีแยกกันคนละ cell และกลายเป็นรากฐานของ ฟีเจอร์ค้นหา DM ทั้งหมด ที่เพิ่งเปิดตัว
- สำหรับคอมมูนิตี้ขนาดใหญ่มาก (BFGs) ใช้ cell เฉพาะทางและดัชนีแบบหลาย shard เพื่อให้ ขยายระบบได้เกินข้อจำกัดจำนวนข้อความสูงสุดของ Lucene
ข้อจำกัดของโครงสร้างพื้นฐานเดิม
- คิวข้อความที่ใช้ Redis มีคอขวดเมื่อโหนด Elasticsearch ขัดข้อง และมีความเป็นไปได้ที่ข้อความจะสูญหาย
- คลัสเตอร์ขนาดใหญ่ (200+ โหนด) มีอัตราความล้มเหลวของการทำดัชนีทั้งระบบสูงถึง 40% จากความขัดข้องของโหนดเพียงตัวเดียว
- ดัชนีที่ชนข้อจำกัด
MAX_DOCS ของ Lucene (2 พันล้านข้อความ) จะ ทำให้การทำดัชนีหยุดลงโดยสมบูรณ์
- ด้วยความที่ระบบเก่ามาก แม้แต่การแพตช์ log4shell ก็ทำได้ก็ต่อเมื่อต้องปิดระบบทั้งหมดก่อน
กลยุทธ์ในการแก้ปัญหา
สร้างใหม่บน Kubernetes
- ใช้ Elastic Kubernetes Operator (ECK) เพื่อทำงานอัตโนมัติในการดูแลคลัสเตอร์ Elasticsearch
- ทำให้ rolling restart รวมถึงการอัปเกรด OS และซอฟต์แวร์สามารถทำได้อย่างปลอดภัย
กระจายคลัสเตอร์ด้วยสถาปัตยกรรม “cell”
- แทนที่จะใช้คลัสเตอร์เดี่ยวขนาดใหญ่แบบเดิม ก็เปลี่ยนมาเป็น หลายคลัสเตอร์ขนาดเล็กที่รวมกันเป็นหนึ่ง cell
- ภายในแต่ละ cell จะ จำกัดจำนวนดัชนี และคุมขนาด shard ไว้ที่ 50GB และไม่เกิน 200 ล้านข้อความ
- ช่วย เพิ่มประสิทธิภาพทั้งการทำดัชนีและการ query และลดภาระในการรักษาสถานะของคลัสเตอร์
คิวข้อความบน PubSub
- การเปลี่ยนจาก Redis → PubSub ทำให้ สามารถคงคิวไว้ได้โดยไม่สูญหายของข้อความ
- กำลังขยายการใช้ PubSub ไปยังฟังก์ชันอื่น ๆ ด้วย (เช่น การตั้งเวลา job)
การทำ batch indexing แยกตามคลัสเตอร์
- ข้อความที่รับจาก PubSub จะถูก แยกตามคลัสเตอร์และดัชนีเป้าหมาย ก่อนส่งเป็น task แยกเพื่อประมวลผลแบบขนาน
- สร้างโครงสร้างกระจายการประมวลผลข้อความด้วย tokio task + channel ของ Rust
การปรับปรุงความสามารถในการค้นหา
การค้นหา DM ตามผู้ใช้
- เดิมที DM ถูกทำดัชนีในระดับช่องทาง จึง ค้นหา DM ทั้งหมดได้อย่างไม่มีประสิทธิภาพ
- ตอนนี้ ทำดัชนีข้อความ DM แบบซ้ำลงในดัชนีรายผู้ใช้ ทำให้ค้นหา DM ทั้งหมดได้ในครั้งเดียว
รองรับ BFG (Big Freaking Guilds)
- นำดัชนีแบบหลาย shard มาใช้สำหรับคอมมูนิตี้ขนาดใหญ่มากที่เกินข้อจำกัดจำนวนข้อความของ Lucene
- BFG จะถูก ประมวลผลด้วยโครงสร้างหลาย primary shard บน Elasticsearch cell เฉพาะทาง
- ทำดัชนีซ้ำทั้งในดัชนีเดิมและดัชนีใหม่พร้อมกัน ก่อนค่อย ๆ ย้ายเป้าหมายของการ query ไปยังระบบใหม่
ผลลัพธ์
- ทำดัชนีข้อความระดับหลายล้านล้านข้อความ และมี throughput ในการทำดัชนีเพิ่มขึ้น 2 เท่า เมื่อเทียบกับเดิม
- ความเร็วตอบสนองของ query: ค่าเฉลี่ย 500ms → 100ms, p99 จาก 1s → ต่ำกว่า 500ms
- ขณะนี้ดูแล คลัสเตอร์มากกว่า 40 ชุดและดัชนีหลายพันรายการ
- การอัปเกรดคลัสเตอร์และ rolling restart ถูก ทำให้เป็นอัตโนมัติทั้งหมดและไม่มี downtime ของบริการ
4 ความคิดเห็น
ที่ทำงานนี้ไปพร้อมกับการให้บริการจริงได้ด้วย... น่านับถือจริง ๆ ครับ
วิศวกรรมของ Discord เป็นแบบอย่างที่ดีเสมอเลย น่าอิจฉาจังครับ
นึกว่า pubsub คืออะไร ที่แท้ก็เป็น IaaS ที่ GCP ให้บริการนี่เอง
https://cloud.google.com/pubsub?hl=en
น่าประทับใจมาก ทั้งการยกเครื่องใหม่เพื่อแก้ปัญหาด้วย