CAP Theorem

เราอยาก จะได้ทุกอย่าง แต่น่าเสียดายที่เรารู้ว่าเราทำไม่ได้ และเมื่อพูดถึงระบบกระจายอย่างที่เราสร้างด้วยสถาปัตยกรรม microservice เรามีแม้กระทั่งบทพิสูจน์ทางคณิตศาสตร์ที่บอกเราว่าเราทำไม่ได้ คุณอาจเคยได้ยินเกี่ยวกับ CAP theorem โดยเฉพาะในการอภิปรายเกี่ยวกับข้อดีของ data store ประเภทต่างๆ โดยแก่นแล้ว มันบอกเราว่าในระบบกระจาย เรามีสามสิ่งที่เราสามารถแลกเปลี่ยนกันได้: consistency , availability และ partition tolerance โดยเฉพาะอย่างยิ่ง ทฤษฎีนี้บอกเราว่าเราจะรักษาไว้ได้แค่สองอย่างในโหมดล้มเหลว

Consistency คือคุณลักษณะของระบบที่เราจะได้คำตอบเดียวกันไม่ว่าจะไปที่ node ไหน Availability หมายถึงทุก request ได้รับ response Partition tolerance คือความสามารถของระบบในการรับมือกับความจริงที่ว่าการสื่อสารระหว่างส่วนต่างๆ ของมันบางครั้งเป็นไปไม่ได้

นับตั้งแต่ Eric Brewer เผยแพร่ข้อสันนิษฐานดั้งเดิมของเขา ไอเดียนี้ก็ได้รับบทพิสูจน์ทางคณิตศาสตร์ ผมจะไม่ลงลึกไปในคณิตศาสตร์ของบทพิสูจน์นี้ เพราะไม่ใช่แค่ว่านี่ไม่ใช่หนังสือแนวนั้น แต่ผมยังรับประกันได้ว่าผมคงทำผิดด้วย แทนที่จะทำแบบนั้น มาใช้ตัวอย่างที่ลองทำจริงเพื่อช่วยให้เราเข้าใจว่า ลึกๆ แล้ว CAP theorem เป็นการกลั่นกรองของชุดเหตุผลที่มีตรรกะมาก

ลองนึกภาพว่า microservice Inventory ของเราถูก deploy ข้ามสอง data center แยกกัน ตามที่แสดงใน Figure 12-7 หนุนหลัง service instance ของเราในแต่ละ data center คือฐานข้อมูล และฐานข้อมูลทั้งสองนี้คุยกันเพื่อพยายาม synchronize ข้อมูลระหว่างกัน การอ่านและเขียนทำผ่าน database node ในพื้นที่ และ replication ถูกใช้เพื่อ synchronize ข้อมูลระหว่าง node

Using multi-primary replication to share data between two database nodes

Figure 12-7. การใช้ multiprimary replication เพื่อแชร์ข้อมูลระหว่าง database node สองตัว

ทีนี้มาดูกันว่าจะเกิดอะไรขึ้นเมื่อบางอย่างล้มเหลว ลองนึกภาพว่ามีบางอย่างง่ายๆ เช่น network link ระหว่าง data center ทั้งสองหยุดทำงาน การ synchronization ในจุดนี้ล้มเหลว การเขียนที่ทำกับฐานข้อมูล primary ใน DC1 จะไม่ propagate ไปยัง DC2 และในทางกลับกัน ฐานข้อมูลส่วนใหญ่ที่รองรับการตั้งค่าแบบนี้ยังรองรับเทคนิค queuing บางอย่างเพื่อให้แน่ใจว่าเราสามารถฟื้นตัวจากสิ่งนี้ได้ในภายหลัง แต่จะเกิดอะไรขึ้นในระหว่างนั้น?

Sacrificing Consistency (การเสียสละ Consistency)

สมมติว่าเราไม่ได้ปิด microservice Inventory ทั้งหมด ถ้าผมเปลี่ยนแปลงข้อมูลใน DC1 ตอนนี้ ฐานข้อมูลใน DC2 จะไม่เห็นมัน หมายความว่า request ใดๆ ที่ทำกับ inventory node ของเราใน DC2 อาจเห็นข้อมูลที่เก่าไป กล่าวอีกนัยหนึ่ง ระบบของเรายังคง available อยู่ในแง่ที่ทั้งสอง node สามารถให้บริการ request ได้ และเราทำให้ระบบยังคงทำงานต่อไปได้แม้จะเกิด partition แต่เราก็เสีย consistency ไป เราไม่สามารถรักษาทั้งสามคุณสมบัติไว้ได้ นี่มักถูกเรียกว่าระบบ AP เพราะ availability และ partition tolerance ของมัน

ระหว่าง partition นี้ ถ้าเรายังคงรับการเขียนต่อไป เราก็ยอมรับความจริงที่ว่าในบางจุดของอนาคตพวกมันจะต้อง resynchronize กัน ยิ่ง partition กินเวลานานเท่าไร การ resynchronization นี้ก็ยิ่งยากขึ้นเท่านั้น

ความจริงคือแม้ว่าเราจะไม่มี network failure ระหว่าง database node ของเรา การ replication ข้อมูลก็ไม่ได้เกิดขึ้นทันที ตามที่กล่าวไว้ก่อนหน้านี้ ระบบที่ยินดียกให้ consistency เพื่อรักษา partition tolerance และ availability ถูกเรียกว่า eventually consistent นั่นคือ เราคาดหวังว่าในบางจุดของอนาคต node ทั้งหมดจะเห็นข้อมูลที่อัปเดตแล้ว แต่มันจะไม่เกิดขึ้นพร้อมกัน ดังนั้นเราต้องอยู่กับความเป็นไปได้ที่ผู้ใช้จะเห็นข้อมูลเก่า

Sacrificing Availability (การเสียสละ Availability)

จะเกิดอะไรขึ้นถ้าเราต้องการรักษา consistency และต้องการทิ้งอย่างอื่นแทน? เอาล่ะ เพื่อรักษา consistency database node แต่ละตัวต้องรู้ว่าสำเนาข้อมูลที่ตัวเองมีเหมือนกับ database node อีกตัว ตอนนี้ในระหว่าง partition ถ้า database node คุยกันไม่ได้ พวกมันก็ไม่สามารถประสานงานกันเพื่อรับประกัน consistency ได้ เราไม่สามารถรับประกัน consistency ได้ ดังนั้นตัวเลือกเดียวของเราคือปฏิเสธที่จะตอบสนองต่อ request กล่าวอีกนัยหนึ่ง เราเสียสละ availability ระบบของเราคือ consistent และ partition tolerant หรือ CP ในโหมดนี้ บริการของเราจะต้องหาวิธีลดระดับฟังก์ชันการทำงานจนกว่า partition จะได้รับการเยียวยาและ database node สามารถ resynchronize ได้

Consistency ข้าม node หลายตัวเป็นเรื่องยากมากจริงๆ มีสิ่งไม่กี่อย่าง (บางทีอาจไม่มีเลย) ที่ยากกว่านี้ในระบบกระจาย ลองคิดดูสักครู่ ลองนึกภาพว่าผมต้องการอ่าน record จาก database node ในพื้นที่ ผมจะรู้ได้อย่างไรว่ามันเป็นข้อมูลล่าสุด? ผมต้องไปถาม node อีกตัว แต่ผมยังต้องขอให้ database node นั้นไม่อนุญาตให้มันถูกอัปเดตขณะที่การอ่านเสร็จสมบูรณ์ด้วย กล่าวอีกนัยหนึ่ง ผมต้องเริ่ม transactional read ข้าม database node หลายตัวเพื่อรับประกัน consistency แต่โดยทั่วไปคนไม่ทำ transactional read กัน ใช่ไหม? เพราะ transactional read นั้นช้า พวกมันต้องการ lock การอ่านหนึ่งครั้งสามารถบล็อกทั้งระบบได้

ตามที่เราได้พูดถึงไปแล้ว ระบบกระจายต้องคาดหวังความล้มเหลว ลองพิจารณา transactional read ของเราข้ามชุด node ที่ consistent ผมขอให้ remote node ล็อก record หนึ่งๆ ขณะที่การอ่านเริ่มขึ้น ผมทำการอ่านเสร็จและขอให้ remote node ปลด lock ของมัน แต่ตอนนี้ผมคุยกับมันไม่ได้แล้ว จะเกิดอะไรขึ้นตอนนี้? Lock นั้นยากที่จะทำให้ถูกต้องแม้ในระบบ single process และยากกว่านั้นมากที่จะ implement ได้ดีในระบบกระจาย

จำได้ไหมตอนที่เราพูดถึง distributed transaction ใน Chapter 6 ? เหตุผลหลักที่มันท้าทายก็เพราะปัญหานี้เกี่ยวกับการรับประกัน consistency ข้าม node หลายตัว

การทำให้ multinode consistency ถูกต้องนั้นยากมากจนผมขอแนะนำอย่าง ยิ่งยวด ว่าถ้าคุณต้องการมัน อย่าพยายามคิดค้นมันขึ้นมาเอง แทนที่จะทำแบบนั้น เลือก data store หรือ lock service ที่มีคุณสมบัติเหล่านี้ Consul เช่น ที่เราพูดถึงใน "Dynamic Service Registries" implement key-value store ที่ consistent อย่างมากซึ่งออกแบบมาเพื่อแชร์ configuration ระหว่าง node หลายตัว เช่นเดียวกับ "เพื่อนไม่ปล่อยให้เพื่อนเขียน crypto เอง" ควรมี "เพื่อนไม่ปล่อยให้เพื่อนเขียน distributed consistent data store เอง" ด้วย ถ้าคุณคิดว่าคุณต้องเขียน CP data store ของตัวเอง อ่าน paper ทั้งหมดในหัวข้อนี้ก่อน จากนั้นไปเรียนปริญญาเอก แล้วเตรียมใช้เวลาหลายปีในการทำมันผิดพลาด ในระหว่างนั้น ผมจะใช้อะไรก็ตามที่มีอยู่แล้วที่ทำสิ่งนี้ให้ผม หรือมีแนวโน้มมากกว่าคือพยายาม อย่างหนักจริงๆ สร้างระบบ AP ที่ eventually consistent แทน

Sacrificing Partition Tolerance? (การเสียสละ Partition Tolerance?)

เราเลือกได้สองอย่างใช่ไหม? ดังนั้นเราจึงมีระบบ AP แบบ eventually consistent เรามีระบบ CP ที่ consistent แต่สร้างและ scale ยาก แล้วทำไมไม่มีระบบ CA ล่ะ? เอาล่ะ เราจะเสียสละ partition tolerance ได้อย่างไร? ถ้าระบบของเราไม่มี partition tolance มันก็รันบนเครือข่ายไม่ได้ กล่าวอีกนัยหนึ่ง มันต้องเป็น process เดียวที่ทำงานในพื้นที่ (local) เท่านั้น ระบบ CA ไม่มีอยู่จริงในระบบกระจาย

AP or CP?

อันไหนถูก AP หรือ CP? เอาล่ะ ความจริงคือ มันขึ้นอยู่กับ สถานการณ์ ในฐานะคนที่สร้างระบบ เรารู้ว่า trade-off นี้มีอยู่จริง เรารู้ว่าระบบ AP scale ได้ง่ายกว่าและสร้างง่ายกว่า และเรารู้ว่าระบบ CP จะต้องใช้ความพยายามมากกว่าเพราะความท้าทายในการรองรับ distributed consistency แต่เราอาจไม่เข้าใจผลกระทบทางธุรกิจของ trade-off นี้ สำหรับระบบ inventory ของเรา ถ้า record หนึ่งเก่าไปห้านาที จะโอเคไหม? ถ้าคำตอบคือใช่ ระบบ AP อาจเป็นคำตอบ แต่แล้วยอดคงเหลือของลูกค้าในธนาคารล่ะ? มันเก่าไปได้ไหม? โดยไม่รู้บริบทที่การดำเนินการนั้นถูกใช้ เราก็ไม่สามารถรู้สิ่งที่ถูกต้องที่ควรทำได้ การรู้เกี่ยวกับ CAP theorem ช่วยให้คุณเข้าใจว่า trade-off นี้มีอยู่จริง และคำถามอะไรที่ควรถาม

It's Not All or Nothing (ไม่ใช่ทุกอย่างหรือไม่มีอะไรเลย)

ระบบของเราโดยรวมไม่จำเป็นต้องเป็น AP หรือ CP อย่างใดอย่างหนึ่ง แคตตาล็อกของ MusicCorp ของเราอาจเป็น AP เพราะเราไม่ค่อยกังวลเรื่อง record ที่เก่าไป แต่เราอาจตัดสินใจว่า inventory service ของเราต้องเป็น CP เพราะเราไม่อยากขายของให้ลูกค้าในสิ่งที่เราไม่มี แล้วต้องมาขอโทษทีหลัง

แต่แต่ละบริการก็ไม่จำเป็นต้องเป็น CP หรือ AP ด้วยซ้ำ

ลองคิดถึง microservice Points Balance ของเรา ที่เราเก็บ record ว่าลูกค้าของเราสะสมคะแนน loyalty ไว้เท่าไร เราอาจตัดสินใจว่าเราไม่สนใจถ้ายอดคงเหลือที่เราแสดงให้ลูกค้าเก่าไปบ้าง แต่เมื่อพูดถึงการอัปเดตยอดคงเหลือ เราต้องการให้มัน consistent เพื่อให้แน่ใจว่าลูกค้าไม่ใช้คะแนนมากกว่าที่มีอยู่ microservice นี้เป็น CP หรือ AP หรือทั้งสองอย่าง? จริงๆ แล้วสิ่งที่เราทำคือผลักดัน trade-off เกี่ยวกับ CAP theorem ลงไปสู่ความสามารถของ microservice แต่ละตัว

ความซับซ้อนอีกอย่างคือทั้ง consistency และ availability ไม่ใช่ทั้งหมดหรือไม่มีอะไรเลย หลายระบบให้เรา trade-off ที่ละเอียดกว่านั้นมาก ตัวอย่างเช่น กับ Cassandra ผมสามารถทำ trade-off ที่แตกต่างกันสำหรับแต่ละ call ดังนั้นถ้าผมต้องการ consistency ที่เข้มงวด ผมสามารถทำการอ่านที่บล็อกจนกว่า replica ทั้งหมดจะตอบสนอง ยืนยันว่าค่านั้น consistent หรือจนกว่า replica quorum เฉพาะจะตอบสนอง หรือแม้แต่แค่ node เดียว เห็นได้ชัดว่า ถ้าผมบล็อกรอ replica ทั้งหมดตอบกลับมาและตัวหนึ่งใช้งานไม่ได้ ผมจะบล็อกอยู่นานมาก ในทางกลับกัน ถ้าผมต้องการให้การอ่านของผมตอบสนองเร็วที่สุดเท่าที่จะทำได้ ผมอาจแค่รอฟังจาก node เดียว—ซึ่งในกรณีนั้นมีความเป็นไปได้ว่านี่อาจเป็นมุมมองข้อมูลที่ไม่ consistent

คุณมักจะเห็นโพสต์เกี่ยวกับคนที่ "เอาชนะ" CAP theorem ได้ พวกเขาไม่ได้เอาชนะมันจริงๆ หรอก สิ่งที่พวกเขาทำคือสร้างระบบที่ความสามารถบางอย่างเป็น CP และบางอย่างเป็น AP บทพิสูจน์ทางคณิตศาสตร์เบื้องหลัง CAP theorem ยังคงเป็นจริงอยู่

And the Real World (และโลกแห่งความเป็นจริง)

ส่วนใหญ่ที่เราพูดถึงคือโลกอิเล็กทรอนิกส์—bit และ byte ที่เก็บไว้ในหน่วยความจำ เราพูดถึง consistency ในลักษณะที่ค่อนข้างเด็กๆ เราจินตนาการว่าภายในขอบเขตของระบบที่เราสร้างขึ้น เราสามารถหยุดโลกและทำให้ทุกอย่างสมเหตุสมผลได้ แต่สิ่งที่เราสร้างส่วนใหญ่ก็เป็นแค่ภาพสะท้อนของโลกแห่งความจริง และเราก็ควบคุมมันไม่ได้ ใช่ไหม?

มาย้อนกลับไปที่ระบบ inventory ของเรากัน มันแมปกับสิ่งของทางกายภาพในโลกจริง เราเก็บจำนวนอัลบั้มที่เรามีในคลังสินค้าของ MusicCorp เมื่อเริ่มวันเรามีอัลบั้ม Give Blood ของวง Brakes อยู่ 100 ชุด เราขายไปหนึ่งชุด ตอนนี้เราเหลือ 99 ชุด ง่ายใช่ไหม? แต่จะเกิดอะไรขึ้นถ้าตอนที่กำลังส่งคำสั่งซื้อออกไป มีคนทำอัลบั้มหล่นพื้นแล้วมันแตกล่ะ? จะเกิดอะไรขึ้นตอนนี้? ระบบของเราบอกว่ามี 99 ชุดบนชั้น แต่จริงๆ แล้วมีแค่ 98 ชุด

ถ้าเราทำให้ระบบ inventory ของเราเป็น AP แทนล่ะ แล้วเราต้องติดต่อผู้ใช้ในภายหลังเป็นครั้งคราวเพื่อบอกว่าสินค้าตัวหนึ่งของเขาหมดสต็อกจริงๆ จะเป็นเรื่องแย่ที่สุดในโลกไหม? มันจะสร้างและ scale ได้ง่ายกว่าแน่นอน และมั่นใจได้ว่าถูกต้อง

เราต้องยอมรับว่าไม่ว่าระบบของเราจะ consistent แค่ไหนในตัวมันเอง มันก็ไม่สามารถรู้ทุกอย่างที่เกิดขึ้นได้ โดยเฉพาะเมื่อเรากำลังเก็บ record ของโลกแห่งความจริง นี่คือหนึ่งในเหตุผลหลักที่ระบบ AP กลายเป็นตัวเลือกที่ถูกต้องในหลายสถานการณ์ นอกเหนือจากความซับซ้อนของการสร้างระบบ CP แล้ว มันก็ไม่สามารถแก้ปัญหาทั้งหมดของเราได้อยู่ดี

Antifragility (ความต้านทานต่อความปั่นป่วน)

ในฉบับพิมพ์ครั้งแรก ผมพูดถึงแนวคิดของ antifragile ที่ Nassim Taleb ทำให้เป็นที่นิยม แนวคิดนี้อธิบายว่าระบบได้ประโยชน์จากความล้มเหลวและความไม่เป็นระเบียบอย่างไร และถูกยกให้เป็นแรงบันดาลใจให้บางส่วนของ Netflix ดำเนินงาน โดยเฉพาะเกี่ยวกับแนวคิดอย่าง chaos engineering เมื่อมองแนวคิดของ resiliency ในภาพกว้างขึ้น เราก็รู้ว่า antifragility เป็นแค่ส่วนย่อยของแนวคิด resiliency เมื่อเราพิจารณาแนวคิด graceful extensibility และ sustained adaptability ที่เราแนะนำไปก่อนหน้านี้ สิ่งนี้ก็ชัดเจนขึ้น

ผมคิดว่าตอนที่ antifragility กลายเป็นแนวคิดที่ hype อยู่ช่วงสั้นๆ ในวงการ IT มันเกิดขึ้นท่ามกลางบริบทที่เรามองเรื่อง resiliency แคบเกินไป—ที่ซึ่งเรามองแค่ robustness และอาจจะ rebound แต่ละเลยส่วนที่เหลือ ด้วยสาขา resilience engineering ที่ได้รับการยอมรับและความสนใจมากขึ้นในตอนนี้ ดูเหมือนว่าเหมาะสมที่จะก้าวข้ามคำว่า antifragile ไป ในขณะที่ยังคงเน้นย้ำแนวคิดบางอย่างที่อยู่เบื้องหลังมันซึ่งเป็นส่วนหนึ่งของ resiliency โดยรวมอย่างแท้จริง