- ในการออกแบบระบบ เป็นโจทย์ที่แทบเป็นไปไม่ได้ในทางปฏิบัติที่จะทำให้ได้ทั้งความสอดคล้องสมบูรณ์แบบ ความพร้อมใช้งาน เวลาแฝงต่ำ และปริมาณงานสูงพร้อมกัน
- สิ่งสำคัญคือการหาแบบออกแบบที่เหมาะสมผ่านการหาจุดสมดุลที่สอดคล้องกับแอปพลิเคชัน
- ในการออกแบบฟีด Following/ไทม์ไลน์ของ Bluesky มีการใช้ trade-off โดย ยอมลดความสอดคล้องลงบางส่วน เพื่อ ปรับปรุงประสิทธิภาพการเขียน
- ผลลัพธ์คือ ลด P99 latency ได้มากกว่า 96% โดยไม่ส่งผลกระทบเชิงลบต่อผู้ใช้
การ fanout ของไทม์ไลน์
- เมื่อผู้ใช้โพสต์ข้อความบน Bluesky ระบบจะทำการสร้างดัชนี เก็บลงฐานข้อมูล และให้บริการผ่าน API response
- ในเวลาเดียวกัน โพสต์นี้จะผ่านกระบวนการ “fanout” โดยแทรกลงในตารางไทม์ไลน์ของผู้ติดตามแต่ละคน
- กระบวนการนี้จะดึงรายการผู้ติดตาม แล้วทำการแทรกแบบย้อนลำดับลงในตารางไทม์ไลน์ของผู้ติดตามแต่ละราย
- ตารางไทม์ไลน์ถูก partition ตามผู้ใช้และจัดเก็บในฐานข้อมูลแบบกระจาย (ScyllaDB) พร้อมทำ replication ไปยังหลาย shard เพื่อความพร้อมใช้งานสูง
- ผู้ใช้แต่ละคนอาจถูกจัดสรรไปยัง shard ที่ต่างกัน
- เพื่อประหยัดพื้นที่จัดเก็บ ไทม์ไลน์ที่ยาวเกินขนาดที่กำหนดจะมีการลบ reference ของโพสต์เก่าออกเป็นระยะ
ปัญหา hot shard
- Bluesky มีผู้ใช้ประมาณ 32 ล้านคน และฐานข้อมูลไทม์ไลน์ถูกแบ่งออกเป็นหลายร้อย shard
- ในระบบที่มีผู้ใช้งานระดับหลายล้านคน อาจมีผู้ใช้ที่มีความสัมพันธ์การติดตามจำนวนมากผิดปกติได้
- เช่น ผู้ใช้ที่ติดตามคนอื่นหลายแสนบัญชี
- หนึ่ง shard จะเก็บไทม์ไลน์ของผู้ใช้หลายคนร่วมกัน
- หากผู้ใช้บางรายก่อให้เกิดการเขียนจำนวนมากผิดปกติ shard นั้นก็จะเข้าสู่ภาวะโอเวอร์โหลด (“hot shard”)
- hot shard เหล่านี้ทำให้การอ่านหรือเขียนกระจุกตัว และความล่าช้าจะลามไปถึงผู้ใช้อื่นที่อยู่บน shard เดียวกัน
การสะสมของเวลาแฝง
- หากผู้ใช้คนหนึ่งมีผู้ติดตาม 2,000,000 คน การเขียนแบบลำดับต่อเนื่องอาจใช้เวลามากกว่า 20 นาที
- เพื่อแก้ปัญหานี้ การทำ fanout แบบขนานช่วยลดเวลาแฝงเฉลี่ยได้
- อย่างไรก็ตาม P99 latency (ราว 15 มิลลิวินาทีขึ้นไป) อาจเกิดขึ้นหลายครั้งและทำให้งานแบบขนานทั้งหมดช้าลง
- เมื่อมีผู้ติดตามจำนวนมากมาก P99 หรือ P99.9 latency อาจทำให้เวลา fanout ทั้งหมดเลวร้ายที่สุดยืดออกจากระดับหลายนาทีไปจนถึงหลายสิบนาที
ไทม์ไลน์แบบ Lossy
- สำหรับผู้ใช้ที่ติดตามบัญชีจำนวนมากเกินไป การแสดงทุกโพสต์อย่างถูกต้องครบถ้วนตามลำดับนั้นแทบเป็นไปไม่ได้ในทางปฏิบัติ
- และในความเป็นจริง มนุษย์เองก็ยากจะบริโภคโพสต์ทั้งหมดได้
- ดังนั้น สำหรับไทม์ไลน์ของผู้ใช้ที่มีจำนวนการติดตามเกินเกณฑ์ที่กำหนด (เช่น
reasonable_limit) จึงมีการนำวิธี “drop” การเขียนบางส่วนแบบสุ่มตามความน่าจะเป็นมาใช้
- ใช้สูตร
loss_factor = min(reasonable_limit / num_follows, 1)
- ระหว่าง fanout จะมีการสร้างค่าสุ่ม และหากค่ามากกว่า
loss_factor ก็จะข้ามการเขียนลงไทม์ไลน์
- วิธีนี้ช่วยจำกัดการเขียนที่มากเกินไปต่อไทม์ไลน์ของผู้ใช้บางราย และป้องกันไม่ให้ประสิทธิภาพของ shard ทั้งก้อนลดลง
ว่าด้วยการแคช
- การเขียนไทม์ไลน์เกิดขึ้นมากกว่าหนึ่งล้านครั้งต่อวินาที ดังนั้นถ้าต้อง query จำนวนบัญชีที่ผู้ใช้ติดตามจากฐานข้อมูลโดยตรงทุกครั้งที่เขียน ภาระโหลดจะสูงมาก
- แทนที่จะทำเช่นนั้น ระบบจะ cache บัญชีที่มีจำนวนการติดตามสูงไว้ใน Redis ด้วย sorted set
- อินสแตนซ์ของบริการ fanout จะโหลดข้อมูลแคชนี้เข้าหน่วยความจำทุก ๆ 30 วินาที
- ส่งผลให้สามารถตรวจสอบข้อมูลผู้ใช้ที่มีจำนวนการติดตามสูงได้อย่างรวดเร็วแม้อยู่ระหว่างกระบวนการ fanout
- เนื่องจากข้อมูลแคชไม่จำเป็นต้องทันสมัยสมบูรณ์แบบ แนวทางนี้จึงยอมรับความไม่สมบูรณ์เล็กน้อยเพื่อแลกกับประสิทธิภาพและความสามารถในการขยายระบบ
ผลลัพธ์
- หลังนำไทม์ไลน์แบบ lossy มาใช้ ปัญหา hot shard ในฐานข้อมูล Timelines ก็แทบหายไป
- P99 latency สำหรับการประมวลผล fanout หนึ่งหน้า ลดลงมากกว่า 90%
- เมื่อมองที่เวลารวมของงาน fanout ทั้งหมด งานที่เดิมใช้เวลา 5–10 นาทีในเกณฑ์ P99 ถูกลดลงเหลือต่ำกว่า 10 วินาที
- สิ่งนี้แสดงให้เห็นว่า แม้จะยอมเสียความสอดคล้องไปบางส่วน ก็ยังสามารถตอบสนองความคาดหวังของผู้ใช้บริการได้อย่างเพียงพอ พร้อมรักษาความสามารถในการขยายระบบระดับใหญ่
- สถาปัตยกรรมไทม์ไลน์ของ Bluesky ยังมีพื้นที่ให้ปรับปรุงต่อไป แต่การเปลี่ยนแปลงครั้งนี้ช่วยเพิ่มทั้งปริมาณงานเขียนและความสามารถในการขยายระบบอย่างมาก
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ในฐานะคนที่ชอบระบบ ก็เป็นคนที่ชอบบทความแบบนี้มาก และมันง่ายที่จะติดอยู่กับแนวคิดที่ว่า "ต้องสมบูรณ์แบบ"
สงสัยว่าทำไมถึงไม่ทำไทม์ไลน์แบบไฮบริดตามความนิยมของบัญชี
เป็นวิธีแก้ปัญหาที่น่าสนใจสำหรับปัญหาที่น่าสนใจ ขอบคุณที่แชร์
สงสัยเกี่ยวกับกลยุทธ์นี้ที่ยอมแลกความสอดคล้องกันไป และอยากรู้ว่ามีความคิดเกี่ยวกับวิธีอื่นที่ไม่ใช่ fan-out แบบเต็มในการอ่านหรือการเขียนหรือไม่
ไม่จำเป็นต้องส่งมอบทุกอย่างที่ผู้ใช้หลายพันคนที่ทุกคนติดตามโพสต์ไว้แบบเรียงตามเวลาอย่างสมบูรณ์ แต่การมีคอนเทนต์มากพอให้ไทม์ไลน์มีของใหม่อยู่เสมอก็ดูสมเหตุสมผล
สงสัยว่าการจำกัดจำนวนผู้ติดตามเพื่อหลีกเลี่ยงปัญหา hot shard จะทำงานอย่างไร
AWS มีแนวทางทั่วไปที่เจ๋งสำหรับปัญหานี้
บัญชีที่ติดตามผู้ใช้หลายแสนคนนั้นชัดเจนว่าเป็นบอตที่ไล่กวาดคอนเทนต์ ควรบล็อกไปเลยแล้วจบ
เวลาเข้าไปที่โปรไฟล์ของผู้ใช้โดยตรงเพื่อดูโพสต์ทั้งหมด บางครั้งก็มีโพสต์ที่ควรอยู่ในไทม์ไลน์แต่หายไป
สงสัยว่าทำไมถึงทำ fan-out ในแบบที่แต่ละ "หน้า" ไปบล็อกการดึงหน้าถัดไป