Internals of DynamoDB

Anuwat Orachun
FLOWACCOUNT TECH BLOG
4 min readMay 9, 2022

What is DynamoDB ?

DynamoDB คือ Database system ตัวหนึ่งที่เกิดมาในรูปแบบของ NoSQL ที่มีการเก็บข้อมูลและการเข้าถึงแบบ key-value (ไม่มีความสามารถด้าน Relational) การเกิดมาของ DynamoDB จะเน้นไปที่ความสามารถในเรื่องความพร้อมใช้งานตลอดเวลา(high availability) และความสามารถในการขยายประสิทธิภาพ(scalability) โดยทางผู้พัฒนาให้คำนิยามของคุณสมบัติว่า “always writeable” ซึ่ง DynamoDB ถูกใช้เป็นส่วนหนึ่งบน Service หลายๆตัวของ Amazon’s platform เช่น Best seller list, Shopping cart, Customer preference, Session management, Sale rank และ Product catalog หากชีวิตประจำวันของเราต้องทำงานวนเวียนอยู่กับ Cloud เจ้าดังอย่าง AWS(Amazon Web Service) การได้รู้จัก DynamoDB อาจช่วยให้ชีวิต Developer อย่างเรามีตัวเลือกที่มากขึ้นในการพัฒนาระบบ

Background and abilities

Scalability(ความสามารถในการขยาย) ใช่แล้ว DynamoDB ก็เกิดมาพร้อมกับเรื่องนี้เหมือนกัน ซึ่งการเก็บข้อมูลจะกระจายตัวออกไปยัง host หลายๆตัว ซึ่งใน DynamoDB เราจะเรียกสิ่งนี้ว่า node และแน่นอนการกระจายข้อมูลออกไปหลายๆ node ก็เหมือนการช่วยกันทำงาน ซึ่งสามารถช่วยเพิ่มประสิทธิภาพของระบบในการทำงานกรณีที่ระบบของเรามีความใหญ่ขึ้นหรือผู้ใช้งาน(request) เพิ่มมากขึ้น

Symmetry(ความสมมาตร) พอพูดถึง node การทำงานของ node แต่ละ node ในระบบของ DynamoDB ก็แปลกๆไปอีก ทุกๆ node มีความสามารถในการทำงานเหมือนกันหมด ไม่มี node หัวหน้า(master) หรือ node ลูกน้อง(slave) ทำให้ node ไหนๆก็สามารถทำงานแทน node อื่นได้อย่างสมบูรณ์

Heterogeneity(ความแตกต่างที่เข้ากันได้) แม้ว่าแต่ละ node จะมีความแตกต่างกัน เกิดไม่พร้อมกันประสิทธิภาพ(spec) ไม่เท่ากัน แต่ DynamoDB ยังสามารถทำงานบนความแตกต่างนี้ได้ ซึ่งทางผู้พัฒนาก็โฆษณาอีกว่า DynamoDB ของเราเนี้ยเวลาจะเพิ่ม node เข้าไปใหม่หรือถอด Node เก่าออกมันแทบจะไม่กระทบอะไรต่อระบบเดิมเลย

Decentralization(ความไม่มีจุดศูนย์กลาง) เมื่อทุกๆ node ทำงานได้เหมือนๆกันระบบก็ไม่จำเป็นต้องมีศูนย์กลางเพื่อคอยจัดการกิจกรรมต่างๆที่จะเกิดขึ้นในการทำงานอีกต่อไป ทำให้มีประสิทธิภาพในการพร้อมใช้งาน(high availability)ต่อการรับ request ไม่เกิดปัญหาคอขวดที่ node ไหนแค่ node เดียว

Core distributed systems techniques

System Interface

การทำงานกับข้อมูลใน DynamoDB จะมีเพียงสอง method เป็นหลัก คือ Get(key) สำหรับเรียกดูข้อมูลซึ่งจะส่งค่า object และ context สำหรับ key นั้นๆกลับไป และ Put(key, context, value) สำหรับเพิ่มหรือแก้ไขข้อมูลในระบบ ซึ่งโดยปกติแล้วการทำงานจะต้องระบุ context ของ key นั้นๆเข้ามาพร้อมกับ request เสมอเพื่อใช้ตรวจสอบความถูกต้องและ version ของข้อมูลในระบบที่จะต้องการเพิ่มหรือแก้ไข

Partitioning

เพื่อให้การทำงานของระบบตอบโจทเรื่อง Scalability การจัดการข้อมูลภายใน DynamoDB จะมีรูปแบบการเข้าถึงข้อมูลที่เรียกว่า Circular hash ring ซึ่งเมื่อมี node เพิ่มเข้ามาในระบบ ระบบจะทำการกำหนดค่าเพื่อบอกตำแหน่งที่ node มีหน้าที่รับผิดชอบต่อข้อมูลที่เข้ามาในแต่ละส่วนของระบบ เราจะเรียกพื้นที่ที่ต้องรับผิดชอบนั้นว่า partition และเรียก node ที่มีหน้าที่รับผิดชอบในแต่ละ partition ว่า coordinate node การทำงานแบบ Circular hash ring มีข้อดีคือ เมื่อมี node ถูกถอดออกหรือเพิ่มเข้ามา ระบบจะไม่ได้รับผลกระทบทั้งหมดแต่จะมีผลเฉพาะกับ node ในระบบที่อยู่ถัดไปจากตัวที่ถูกถอดออกหรือเพิ่มเข้ามาเท่านั้น

การเก็บข้อมูลระบบจะทำการสร้าง hash จาก key ที่ถูกส่งเข้ามาด้วยวิธี consistent hashing เพื่อใช้สำหรับการทำ partition routing ในการระบุตำแหน่งว่าข้อมูล key นี้จะถูกรับผิดชอบจาก node ไหน รวมไปถึงการเข้าถึงข้อมูล ระบบจะใช้ hash เพื่อระบุว่า node ไหนมีหน้าที่รับผิดชอบการ response ข้อมูลที่ร้องขอเข้ามา โดยการหาว่า node ไหนมีหน้าที่รับผิดชอบข้อมูล key นั้น จะหาได้จากการวิ่งตามเข็มนาฬิกาไปบน ring จนพบ node แรกตามลำดับ นั่นเท่ากับว่า node แรกที่พบนั้นคือ coordinate node สำหรับข้อมูล key ดังกล่าว

ในการจัดการ partition ของข้อมูลในระบบ DynamoDB จะใช้ concept การทำ “virtual nodes” เข้ามาช่วย ซึ่งแต่ละ node จะสามารถรับผิดชอบต่อข้อมูลภายใน ring ได้หลายตำแหน่งหรือหลาย partition

Replication

DynamoDB สามารถทำการเก็บข้อมูลในระบบไว้บนหลายๆ node ซึ่งข้อมูลแต่ละ Key จะถูกเก็บไว้ใน node(physical Nodes ไม่ใช่ virtual nodes) ที่มีหน้าที่รับผิดชอบ Key นั้นๆ รวมไปถึงการ replicate ข้อมูล key ที่ต้องรับผิดชอบไปยัง node อื่นๆด้วย ซึ่งการทำ replication นั้นตัว node จะทำการ replicate ข้อมูลจาก node ก่อนหน้า(n-1) ซึ่งลำดับของ node จะเป็นไปตามกระบวนการของ Circular hash ring โดยค่าเริ่มต้นของจำนวน node ที่จะเกิด replication จะถูกกำหนดไว้ที่ 3 node

การเขียนข้อมูลไปยัง replica node จะไม่รอจนเขียนเสร็จครบทุก node หากข้อมูลถูกเขียนลงไปยัง coordinate node จนสำเร็จ DynamoDB จะทำการ response กลับไปยัง client ว่าสำเร็จแล้วทันที ซึ่งการทำเช่นนี้จะช่วยให้การเขียนข้อมูลสำเร็จเสมอตามที่ผู้พัฒนาได้โฆษณาไว้ “always writeable” แต่ในทางกลับกันจะทำให้เกิดความไม่สอดคล้องกันของข้อมูลในแต่ละ replica node เกิดขึ้น ซึ่งปัญหานี้เราจะพูดถึงต่อไปในเรื่องของการทำ Data Versioning

เมื่อเกิดการทำ replication ขึ้นในระบบคำถามต่อไปคือ
“แล้วจะรู้ได้อย่างไรว่าแล้ว node ไหนบ้างล่ะที่มีข้อมูลสำหรับ key นั้น ?”

คำตอบคือเมื่อมีการเพิ่ม Key ใหม่เข้ามายังระบบ DynamoDB จะสร้างรายการของ node ที่ต้องรับผิดชอบต่อ key นั้นๆ เราจะเรียกสิ่งนี้ว่า “Preference list” ซึ่งข้อมูลส่วนนี้ไม่ได้มีหน้าที่แค่บอกว่า node ไหนรับผิดชอบต่อ key ไหน แต่มันยังสามารถบอกได้ว่าหาก node แรกใน list ไม่พร้อมทำงาน node ต่อไปที่จะต้องทำหน้าที่แทนคือ node ไหน และข้อมูล node ที่ถูกกำหนดไว้ใน list จะถูกกำหนดเฉพาะ physical node เท่านั้น ระบบจะไม่นำ virtual node มาเพิ่มใน list

Versioning

เนื่องจากการทำ replication ข้อมูลจาก coordinate node ไปยัง replica node ตัวระบบจะทำงานแบบ“asynchronously” จึงทำให้เกิดช่วงเวลาที่ในแต่ละ node จะมีข้อมูลที่ไม่สอดคล้องกัน หรือแม้แต่ในกรณีที่บาง node เกิด downtime(ระหว่าง maintenance node หรือในขณะที่ network มีปัญหา) ก็อาจทำให้เกิดข้อมูลที่ไม่อัพเดทล่าสุดอยู่ในบาง node รวมไปถึงเหตุการณ์ที่มี request ขอแก้ไขข้อมูล key เดียวกันเข้ามาในเวลาเดียวกัน

“หากเกิดปัญหานี้ขึ้นการเรียกดูข้อมูลปัจจุบันในระบบอาจได้รับผลลัพธ์ของข้อมูลที่ไม่ถูกต้องแล้ว DynamoDB จัดการยังไงละ ?”

DynamoDB จะใช้กระบวนการที่เรียกว่า “vector clocks” เพื่อช่วยในการจัดการข้อมูลที่มีหลาย version ในเวลาเดียวกัน โดยการเก็บข้อมูลในแต่ละ node ระบบจะเก็บประวัติการแก้ไขข้อมูลตามช่วงเวลา ในรูปแบบของ (node, counter) และจะทำการ reconcile ข้อมูลจากทุก node ที่เกี่ยวข้องตามช่วงเวลาที่เกิดขึ้นตามลำดับ(syntactic reconciliation) และหากเกิดกรณีที่ข้อมูลนั้นมีหลาย version และระบบไม่สามารถ reconcile ข้อมูลได้ ระบบจะส่งข้อมูลทั้งสอง version กลับไปให้ client และให้ client เป็นตัวตัดสินว่า version ไหนเป็นข้อมูลที่ถูกต้อง(semantic reconciliation) และทำการ merge vector clocks กลับไปยังทุกๆ node เพื่อเป็นการยืนยันความถูกต้องของข้อมูลในระบบ

จากตัวอย่างการทำงานของระบบที่ประกอบไปด้วย 3 node(N1, N2, N3) client ทำการส่งข้อมูลที่มี key เท่ากับ K1 และมี value เท่ากับ 100 เข้ามาเพื่อสร้างข้อมูลผ่าน node N1 ระบบจะทำการ replicate ข้อมูลไปยัง node N2 และ N3 โดย vector clocks จะมีค่าเท่ากับ [(N1, 1)]

หาก client ทำการแก้ไขข้อมูล K1 โดยกำหนด value ให้มีค่าเท่ากับ 200 ผ่าน node N1 การทำงานนี้จะยังไม่ทำให้เกิดปัญหา version conflict ขึ้นในระบบ node N1 จะทำการ update vector clocks เป็น [(N1, 2)]

ต่อมาในขณะที่ node N1 ไม่สามารถทำงานได้และ client ทำการแก้ไขข้อมูล K1 โดยกำหนดให้มีค่าเท่ากับ 300 ผ่านเข้ามาทาง node N2 ระบบจะทำการ update vector clocks เป็น [(N1, 2), (N2, 1)] แต่จะไม่ทำการ replicate ข้อมูลไปยัง node อื่นๆทันที

และในขณะเดียวกับ client ก็ทำการแก้ไขข้อมูล K1 โดยกำหนดให้มีค่าเท่ากับ 400 ผ่านเข้ามาทาง node N3 ระบบจะทำการ update vector clocks เป็น [(N1, 2), (N3, 1)] และเช่นเดียวกันระบบจะไม่ทำการ replicate ข้อมูลไปยัง node อื่นๆทันที เหตุการณ์นี้จะทำให้เกิดปัญหา version conflict ระหว่าง node N2 และ N3 ขึ้นในระบบ ซึ่งไม่สามารถแก้ไขได้ด้วยการทำ syntactically reconciled

ซึ่งหาก client มีการเรียกดูข้อมูล K1 ระบบจะทำการส่งข้อมูลที่ conflict ทั้งสอง version กลับไปเพื่อให้ client ทำ semantic reconciliation และส่งข้อมูลกลับมายืนยันในระบบ หาก client ทำการยืนยันเข้ามาว่าข้อมูล K1 ที่ถูกต้อง(ล่าสุด) มีค่าเท่ากับ 300 ผ่าน node N1 ระบบจะทำการ update vector clock ให้มีค่าเป็น [(N1, 3), (N2, 1), (N3, 1)] และ replicate ข้อมูลไปยัง node อื่นๆ

Summary

การที่เราต้องหยิบ DynamoDB เข้ามาเป็นส่วนหนึ่งในระบบของเรา อาจไม่จำเป็นต้องรู้หรือเข้าใจวิธีการทำงานทั้งหมดในทุกขั้นตอนที่ DynamoDB ทำงาน เราเองก็ยังสามารถใช้งานมันได้โดยไม่ติดปัญหาอะไร ยิ่งเป็นการใช้งานผ่าน AWS console แล้ว developer อย่างเรายิ่งมีความรู้สึกว่าการหาข้อมูลกลไกลต่างๆเป็นเรื่องที่เกินความจำเป็นมาก แต่หากเราเข้าใจว่าภายในของสิ่งที่เรากำลังจะหยิบมาใช้งานทำงานอย่างไร เราอาจจะคาดเดาถึงปัญหาที่จะเกิดขึ้นในอนาคต หรือแม้แต่การหาแนวทางเพื่อเพิ่มประสิทธิภาพให้ระบบปัจจุบันของเราให้ทำงานได้ดียิ่งขึ้น นอกเหนือจากกระบวนการทำ Partition, Replication และ Versioning ยังมีอีกหลายส่วนที่ไม่ได้พูดถึง ไม่ว่าจะเป็นการทำ Hinted Handoff, การทำงานในส่วน Failure Detection, หรือแม้แต่วิธีการจัดการของระบบในขณะ Adding/Removing Storage Nodes ซึ่งทั้งหมดนี้ AWS เป็นคนคอยจัดการให้โดยที่เราเองก็ไม่รู้ตัวขณะใช้งานด้วยซ้ำไป

หากชอบบทความของ FlowAccount Tech Blog อย่าลืมกด Follow นะครับ ติดตามบทความอื่นจาก FlowAccount Tech Blog ได้ที่ https://medium.com/flowaccount-tech

Open Positions jobs available on FlowAccount > https://flowaccount.com/en/jobs

Reference
[1]: Dynamo: Amazon’s Highly Available Key-value Store
[2]: Internals of DynamoDB by Udit Sharma
[3]: Deconstructing Dynamo by Jonathon Henderson
[4]: Distributed Systems Case Study: Amazon Dynamo by Steve Ko
[5]: DynamoDB: An Inside Look Into NoSQL
[6]: Amazon DynamoDB by Nicolas Travers

--

--