- Apple ใช้ Cassandra และ FoundationDB สำหรับ iCloud และ CloudKit
- ฐานข้อมูลเหล่านี้จัดเก็บฐานข้อมูลได้หลายพันล้านรายการภายใต้สถาปัตยกรรมมัลติเทนเนนซีระดับสุดขั้ว
บทเรียนจากโลกจริงที่ใช้ได้เสมอ
- ทั้ง Meta และ Apple ต่างใช้ การประมวลผลแบบอะซิงโครนัส เพื่อให้ฟีเจอร์สำหรับผู้ใช้ทำงานได้อย่างราบรื่น
- ทั้งสองบริษัทใช้สถาปัตยกรรมแบบ stateless เพื่อแก้ปัญหาด้านการขยายระบบ
- แยกทรัพยากรเชิงตรรกะเพื่อให้ได้ความน่าเชื่อถือและความพร้อมใช้งาน
- จัดการความต้องการที่หลากหลายให้เรียบง่าย
- สร้างชั้น abstraction เพื่อปรับปรุงประสบการณ์ของนักพัฒนา
- รู้จักผู้ใช้และกำหนดแต่ละเลเยอร์, API และการออกแบบตามนั้น
Cassandra
- Cassandra เป็นระบบจัดการฐานข้อมูล NoSQL แบบคอลัมน์กว้าง
- เดิมพัฒนาขึ้นที่ Facebook เพื่อใช้กับฟีเจอร์ค้นหาใน inbox ของ Facebook
- ที่น่าสนใจคือ Meta เองได้แทนที่การใช้งาน Cassandra ส่วนใหญ่ด้วย ZippyDB
- iCloud ใช้ Cassandra บางส่วน และ Apple ดำเนินการดีพลอย Cassandra ที่ใหญ่ที่สุดแห่งหนึ่งของโลก
- มากกว่า 300,000 อินสแตนซ์/โหนด
- ข้อมูลหลายร้อยเพตะไบต์
- มากกว่า 2 เพตะไบต์ต่อคลัสเตอร์
- คิวรีหลายล้านครั้งต่อวินาที
- แอปพลิเคชันหลายพันรายการ
- Cassandra ยังถูกพัฒนาและปรับปรุงอย่างต่อเนื่องภายใน Apple
- อย่างไรก็ตาม CloudKit + Cassandra ชนข้อจำกัดด้านการขยายระบบ จึงหันมาใช้ FoundationDB
FoundationDB
- Apple ใช้งาน FoundationDB อย่างเปิดเผย และเข้าซื้อกิจการในปี 2015
- FoundationDB เป็นที่เก็บคีย์-ค่าแบบทรานแซกชันกระจายตัวโอเพนซอร์สที่ออกแบบมาสำหรับจัดการข้อมูลขนาดใหญ่
- เหมาะทั้งกับเวิร์กโหลดการอ่าน/เขียนและเวิร์กโหลดที่เน้นการเขียนหนัก
- Apple ใช้ FoundationDB Record Layer อย่างกว้างขวางใน CloudKit
- FoundationDB Record Layer มี Java API สำหรับการจัดเก็บข้อมูลแบบมีโครงสร้าง
- Record Layer รองรับมัลติเทนเนนซีระดับสุดขั้ว
ทำไม FoundationDB จึงใช้ Record Layer
- FoundationDB จัดการงานด้านระบบกระจายและการควบคุมภาวะพร้อมกัน
- Record Layer ทำหน้าที่เสมือนฐานข้อมูลเชิงสัมพันธ์ที่ทำให้ FoundationDB ใช้งานง่ายขึ้น
- CloudKit อยู่ในชั้นบนสุดและมอบฟังก์ชันกับ API สำหรับนักพัฒนาแอปพลิเคชัน
- ผ่าน Record Layer ทำให้ Apple รองรับมัลติเทนเนนซีขนาดใหญ่ได้
- ใช้กับมัลติเทนเนนซีระดับสุดขั้วที่ให้ record store แยกอิสระแก่ผู้ใช้แต่ละคนของแต่ละแอปพลิเคชัน
- โฮสต์ฐานข้อมูลอิสระหลายพันล้านรายการที่แชร์สคีมาหลายพันแบบ
CloudKit ใช้ FoundationDB และ Record Layer อย่างไร
- ใน CloudKit แอปพลิเคชันถูกแสดงเป็น 'logical container' ที่เป็นไปตามสคีมาที่กำหนดไว้
- สคีมานี้สรุปประเภทเรคอร์ด ฟิลด์ และดัชนีที่จำเป็นสำหรับการค้นคืนข้อมูลและการคิวรีอย่างมีประสิทธิภาพ
- แอปพลิเคชันสามารถจัดข้อมูลเป็น 'โซน' ภายใน CloudKit เพื่อจัดกลุ่มเรคอร์ดเชิงตรรกะและซิงก์กับอุปกรณ์ไคลเอนต์ได้ตามต้องการ
- ผู้ใช้แต่ละคนจะได้รับ subspace เฉพาะภายใน FoundationDB และจะมีการสร้าง record store สำหรับแต่ละแอปพลิเคชันที่ผู้ใช้นั้นโต้ตอบด้วย
- โดยพื้นฐานแล้ว CloudKit จัดการฐานข้อมูลเชิงตรรกะจำนวนมหาศาล เท่ากับจำนวนผู้ใช้คูณจำนวนแอปพลิเคชัน
- แต่ละฐานข้อมูลมีชุดเรคอร์ด ดัชนี และเมทาดาทาของตัวเอง จนรวมกันเป็นฐานข้อมูลหลายพันล้านรายการ
- เมื่อ CloudKit รับคำขอจากอุปกรณ์ไคลเอนต์ ระบบจะส่งคำขอนั้นไปยังโปรเซสบริการ CloudKit ที่พร้อมใช้งานผ่านการทำโหลดบาลานซ์
- โปรเซสนั้นจะโต้ตอบกับ record store ของ Record Layer ที่เกี่ยวข้องเพื่อประมวลผลคำขอ
- CloudKit แปลงสคีมาแอปพลิเคชันที่กำหนดไว้เป็นคำนิยามเมทาดาทาภายใน Record Layer ซึ่งเก็บไว้ใน metadata store แยกต่างหาก
- เมทาดาทานี้ถูกเสริมด้วย system field เฉพาะของ CloudKit ที่ติดตามเวลาในการสร้างและแก้ไขเรคอร์ด รวมถึงโซนที่เก็บเรคอร์ดนั้น
- เพื่อให้เข้าถึงเรคอร์ดภายในแต่ละโซนได้อย่างมีประสิทธิภาพ คีย์หลักจะถูกเติมชื่อโซนนำหน้าไว้
- นอกจากดัชนีที่ผู้ใช้กำหนดแล้ว CloudKit ยังดูแล 'system index' สำหรับงานภายใน เช่น การจัดการโควตาสโตเรจ โดยติดตามขนาดของเรคอร์ดแยกตามประเภท
การใช้ FoundationDB ร่วมกับ Record Layer ช่วยแก้ปัญหาสำคัญ 4 ข้อของ Apple ที่ Cassandra เพียงอย่างเดียวแก้ไม่ได้
1. แก้ปัญหาการค้นหาแบบ full-text ที่ปรับให้เหมาะกับแต่ละบุคคล
- FoundationDB รองรับการค้นหาแบบ full-text ที่ปรับให้เหมาะกับผู้ใช้แต่ละคน เพื่อให้ผู้ใช้เข้าถึงข้อมูลของตนเองได้อย่างรวดเร็ว
- ด้วยการใช้ลำดับคีย์ของ FoundationDB ระบบจึงค้นหาจุดเริ่มต้นของข้อความได้อย่างรวดเร็ว (การจับคู่แบบ prefix) และยังทำการค้นหาที่ซับซ้อนขึ้นได้โดยไม่มีโอเวอร์เฮดเพิ่มเติม เช่น การค้นหาแบบ proximity และ phrase search ที่ต้องหาคำซึ่งอยู่ใกล้กันหรืออยู่ในลำดับที่กำหนด
- ในระบบค้นหาแบบเดิมมักต้องรันโปรเซสเพิ่มเติมเบื้องหลังเพื่อทำให้ดัชนีค้นหาทันสมัยอยู่เสมอ แต่ระบบของ Apple จัดการทุกอย่างแบบเรียลไทม์ ดังนั้นเมื่อข้อมูลเปลี่ยน ดัชนีค้นหาก็อัปเดตทันทีโดยไม่ต้องมีขั้นตอนเพิ่ม
2. แก้ปัญหาโซนที่มีความพร้อมกันสูง
- CloudKit ใช้ FoundationDB เพื่อจัดการอัปเดตจำนวนมากที่เกิดขึ้นพร้อมกันได้อย่างราบรื่น
- ก่อนหน้านี้เมื่อใช้ Cassandra นั้น CloudKit พึ่งพาดัชนีพิเศษที่ติดตามการเปลี่ยนแปลงของแต่ละโซนเพื่อซิงก์ข้อมูลข้ามหลายอุปกรณ์
- เมื่ออุปกรณ์ต้องอัปเดตข้อมูล ก็จะตรวจสอบดัชนีนี้เพื่อดูว่ามีอะไรใหม่
- แต่ข้อเสียคือหากมีการอัปเดตหลายรายการพร้อมกัน อาจเกิดความขัดแย้งได้
- เมื่อใช้ FoundationDB CloudKit จะใช้ดัชนีชนิดพิเศษที่ติดตามลำดับที่แน่นอนของแต่ละการเปลี่ยนแปลงโดยไม่ก่อให้เกิดความขัดแย้ง
- ทำได้โดยกำหนด 'เวอร์ชัน' ที่ไม่ซ้ำกันให้กับทุกการเปลี่ยนแปลง และเมื่อจำเป็นต้องซิงก์ CloudKit จะตรวจสอบเวอร์ชันเหล่านี้เพื่อดูว่าอุปกรณ์พลาดอัปเดตใดไปบ้าง
- หาก CloudKit ต้องย้ายข้อมูลระหว่างคลัสเตอร์สตอเรจหลายชุดเพื่อกระจายภาระให้สม่ำเสมอขึ้น สถานการณ์จะซับซ้อนขึ้นเพราะหมายเลขเวอร์ชันของแต่ละคลัสเตอร์ไม่ตรงกัน
- เพื่อแก้ปัญหานี้ CloudKit จะกำหนด 'จำนวนครั้งที่ย้าย' ให้กับข้อมูลของผู้ใช้แต่ละคน (เรียกว่า 'incarnation') ซึ่งจะเพิ่มขึ้นทุกครั้งที่ข้อมูลถูกส่งไปยังคลัสเตอร์ใหม่
- การอัปเดตเรคอร์ดแต่ละครั้งจะมีหมายเลข 'incarnation' ปัจจุบันของผู้ใช้อยู่ด้วย ดังนั้นแม้หลังย้ายแล้ว CloudKit ก็ยังตรวจสอบทั้ง incarnation และหมายเลขเวอร์ชันเพื่อหาลำดับอัปเดตที่ถูกต้องได้
- เมื่อต้องเปลี่ยนไปใช้ระบบใหม่ CloudKit ต้องเผชิญปัญหาในการจัดการข้อมูลเก่าที่ไม่มีหมายเลขเวอร์ชันเหล่านี้
- แต่ระบบก็แก้ปัญหาอย่างชาญฉลาดด้วยฟังก์ชันพิเศษที่จัดเรียงการอัปเดตเก่าจากระบบเดิมให้อยู่ก่อนการอัปเดตของระบบใหม่
- ทำให้ไม่ต้องแก้แอปให้ซับซ้อนหรือคงโค้ดเก่าไว้
- โดยพิจารณาทั้งค่า incarnation, เวอร์ชัน และตัวนับการอัปเดตแบบเก่าเพื่อรักษาลำดับประวัติที่ถูกต้อง
3. แก้ปัญหาคิวรีที่มี latency สูง
- FoundationDB ถูกออกแบบมาเพื่อรองรับ concurrency สูงมากกว่าการมี latency ต่ำ กล่าวคือเน้นให้ระบบรับงานจำนวนมากพร้อมกันได้ แทนที่จะโฟกัสความเร็วของงานเดี่ยว
- เพื่อใช้ประโยชน์จากการออกแบบนี้ให้เต็มที่ Record Layer จึงทำงานจำนวนมากแบบ 'อะซิงโครนัส'
- โดยนำงานที่จะเสร็จในภายหลังเข้าไปไว้ในคิว และใช้เวลาระหว่างนั้นทำงานอื่นต่อได้
- แนวทางนี้ช่วยกลบ latency ที่อาจเกิดขึ้นระหว่างงานเหล่านี้
- อย่างไรก็ตาม เครื่องมือที่ FoundationDB ใช้สื่อสารกับฐานข้อมูลถูกออกแบบให้ใช้เธรดเดียวสำหรับงานเครือข่าย จึงทำงานได้ทีละอย่าง
- ในเวอร์ชันก่อนหน้า การตั้งค่านี้ทำให้เกิดคอขวด เพราะทุกงานต้องรอคิวบน network thread นี้
- เนื่องจาก Record Layer ใช้แนวทางเธรดเดียวนี้อยู่ จึงเกิดปัญหาคอขวด
- เพื่อปรับปรุง Apple จึงลดภาระงานของ network thread นี้
- ตอนนี้ระบบสามารถทำงานกับฐานข้อมูลพร้อมกันได้หลายด้านโดยไม่เกิดคิวสะสม ทำให้งานที่ซับซ้อนเร็วขึ้น
- วิธีนี้ช่วยซ่อน latency หรือความช้าที่มองเห็นได้ เพราะระบบไม่ต้องรอให้งานหนึ่งเสร็จก่อนจึงเริ่มอีกงาน
4. แก้ปัญหาทรานแซกชันที่ขัดแย้งกัน
- ใน FoundationDB 'transaction conflict' จะเกิดขึ้นเมื่อทรานแซกชันหนึ่งอ่านคีย์บางตัว ในขณะที่อีกทรานแซกชันแก้ไขคีย์เดียวกันพร้อมกัน
- FoundationDB มีความสามารถในการควบคุมชุดคีย์ที่อาจก่อให้เกิดความขัดแย้งระหว่างการอ่านหรือเขียน ทำให้จัดการความขัดแย้งเหล่านี้ได้อย่างละเอียดแม่นยำ
- วิธีทั่วไปในการหลีกเลี่ยงความขัดแย้งที่ไม่จำเป็น คือการใช้การอ่านชนิดพิเศษที่ไม่ก่อ conflict หรือ 'snapshot' read กับคีย์หลายตัว
- หากการอ่านแบบนี้พบคีย์ที่สำคัญ ทรานแซกชันจะทำเครื่องหมายเฉพาะคีย์ที่อาจมีความขัดแย้ง แทนที่จะทำกับช่วงทั้งหมด
- วิธีนี้ทำให้ทรานแซกชันได้รับผลกระทบเฉพาะจากการเปลี่ยนแปลงที่สำคัญต่อผลลัพธ์จริง ๆ
- Record Layer ใช้กลยุทธ์นี้เพื่อจัดการโครงสร้างที่เรียกว่า skip list ซึ่งเป็นส่วนหนึ่งของระบบ rank index อย่างมีประสิทธิภาพ
- อย่างไรก็ตาม การตั้งช่วง conflict เหล่านี้ด้วยตนเองอาจยุ่งยาก และโดยเฉพาะเมื่อปะปนกับลอจิกหลักของแอปพลิเคชัน ก็อาจนำไปสู่บั๊กที่ระบุได้ยาก
- ดังนั้นในระบบที่สร้างบน FoundationDB จึงควรสร้างเครื่องมือระดับสูงกว่า เช่น custom index เพื่อจัดการรูปแบบเหล่านี้
- แนวทางนี้ช่วยหลีกเลี่ยงการโยนภาระในการผ่อนคลายกฎ conflict ไปให้แอปพลิเคชันฝั่งไคลเอนต์แต่ละตัว ซึ่งอาจนำไปสู่ข้อผิดพลาดและความไม่สอดคล้องกันได้
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ผู้ใช้ Hacker News คนหนึ่งได้แบ่งปันมุมมองจากตอนที่ทำงานที่ Apple เกี่ยวกับความแตกต่างระหว่างฐานข้อมูลกับระบบไฟล์ โดยระบุว่าทั้งฐานข้อมูลและระบบไฟล์นั้นโดยพื้นฐานแล้วทำหน้าที่เดียวกัน และเป็นการปรับให้เหมาะกับการแก้ปัญหาเฉพาะด้าน ตัวอย่างเช่น iCloud แสดงให้เห็นวิธีนิยามระบบไฟล์บนพื้นฐานของฐานข้อมูล ผู้ใช้นี้ยังเล่าประสบการณ์การใช้ Cassandra เพื่อจัดเก็บวิดีโอด้วย
ผู้ใช้อีกรายหนึ่งกล่าวถึงประสบการณ์จากบริษัทก่อนหน้า ที่ใช้ FoundationDB และ RecordLayer เพื่อสร้างระบบแค็ตตาล็อกแบบทรานแซกชัน ระบบนี้มีประสิทธิภาพมาก และการใช้ gRPC กับ Protobuf ก็เป็นเรื่องที่เป็นธรรมชาติ อย่างไรก็ตาม เขาชี้ให้เห็นว่าข้อเสียคือมีอุปสรรคในการเริ่มต้นใช้งาน FoundationDB ในระดับขนาดใหญ่ค่อนข้างสูง
ผู้ใช้คนหนึ่งประเมินว่าฟีเจอร์ซิงก์ของ Apple Notes จัดการกับความขัดแย้งได้ดีกว่าแอปจดโน้ตที่อิง Markdown และบอกว่านี่คือเหตุผลที่ท้ายที่สุดย้ายมาใช้ Apple Notes
มีการกล่าวถึงโพสต์ก่อนหน้านี้เกี่ยวกับ FoundationDB ซึ่งรวมถึงลิงก์เกี่ยวกับ distributed key-value store ของ FoundationDB, record layer, การเข้าซื้อกิจการโดย Apple รวมถึงหลักการทำงานและคุณลักษณะของ FoundationDB
มีการกล่าวถึงแง่มุมที่น่าสนใจของสถาปัตยกรรมซอฟต์แวร์เดสก์ท็อปเนทีฟที่ค่อย ๆ ย้ายไปสู่การจัดเก็บบนคลาวด์และการทำงานร่วมกัน โดยการจัดการ schema change และ version migration ได้ดีเป็นสิ่งสำคัญ และสิ่งเหล่านี้เกิดขึ้นในวงกว้างโดยไม่ต้องมีผู้ดูแลเข้ามาแทรกแซง
ผู้ใช้คนหนึ่งหวังว่า iCloud จะสามารถเก็บข้อมูลสำรอง Time Machine ได้
เนื่องจาก FoundationDB มีพื้นฐานมาจาก SQLite จึงมีการตั้งคำถามว่าเอนจิน HCTree จะสามารถนำมาปรับใช้กับ FoundationDB ได้หรือไม่ โดย HCTree มีศักยภาพที่จะเพิ่มประสิทธิภาพการอ่าน/เขียนของ SQLite ได้ถึง 10 เท่า
มีข้อไม่พอใจเกี่ยวกับวิธีที่ iCloud จัดการไฟล์ของผู้ใช้ โดยบางครั้งการที่ iCloud ย้ายไฟล์ แอป และรูปภาพที่ใช้งานล่าสุดขึ้นคลาวด์โดยอัตโนมัติเพื่อเพิ่มพื้นที่ว่าง กลับกลายเป็นปัญหา
ผู้ใช้คนหนึ่งหวนรำลึกถึงระบบรายงานชื่อ Hyperion ที่เคยใช้ตอนทำงานในธนาคารในอดีต ระบบนี้สร้างฐานข้อมูลใหม่สำหรับแต่ละรายงาน ซึ่งในเวลานั้นดูแปลก แต่เมื่อมองย้อนกลับไป เขาระบุว่านี่เป็นแนวทางที่ล้ำยุคเกินสมัยในตอนนั้น