Transactions & Concurrency Control
เมื่อหลาย client เข้าถึงข้อมูลชุดเดียวกันพร้อมกัน ผลลัพธ์ต้องยังถูกต้องเสมือนมีคนทำทีละคน — บทนี้อธิบายว่า "ถูกต้อง" หมายถึงอะไรในทางทฤษฎี (serializability) และมีกลไกอะไรบ้างที่ทำให้มันเกิดขึ้นจริง
1Transaction และคุณสมบัติ ACID
| คุณสมบัติ | ความหมาย |
|---|---|
| Atomicity | Transaction สำเร็จทั้งหมด (commit) หรือไม่มีผลอะไรเลย (abort/rollback) ไม่มีผลลัพธ์บางส่วน |
| Consistency | Transaction พาข้อมูลจากสถานะที่ถูกต้องหนึ่งไปยังอีกสถานะที่ถูกต้อง โดยไม่ละเมิด invariant ของระบบ |
| Isolation | Transaction ที่ทำงานพร้อมกันต้องไม่เห็นสถานะกลาง ๆ ของกันและกัน — เสมือนรันทีละตัว |
| Durability | เมื่อ commit สำเร็จแล้ว ผลลัพธ์ต้องอยู่ถาวรแม้ระบบล่มทันทีหลังจากนั้น |
บทนี้เน้นที่ Isolation เป็นหลัก เพราะเป็นคุณสมบัติที่ซับซ้อนที่สุดในการรับประกันเมื่อมี concurrency
2ปัญหาที่เกิดขึ้นเมื่อไม่มี Concurrency Control
| ปัญหา | ตัวอย่าง |
|---|---|
| Lost update | T1 อ่านค่า x=10, T2 อ่านค่า x=10 พร้อมกัน ทั้งคู่บวก 5 แล้วเขียนกลับ — ผลลัพธ์ควรเป็น 20 แต่กลายเป็น 15 เพราะ T2 เขียนทับ T1 |
| Inconsistent retrieval (dirty read) | T1 กำลังโอนเงินจากบัญชี A ไป B (ยังไม่ commit) T2 มาอ่านยอดรวม A+B ระหว่างนั้นพอดี อาจเห็นยอดรวมผิดชั่วขณะ หรือถ้า T1 ท้าย abort T2 ก็อ่านค่าที่ไม่เคยมีจริง |
3Locking: กลไก Pessimistic Concurrency Control
| ชนิด Lock | อนุญาต |
|---|---|
| Shared lock (read lock) | หลาย transaction ถือ shared lock บน object เดียวกันพร้อมกันได้ (อ่านพร้อมกันได้) |
| Exclusive lock (write lock) | มีได้แค่ transaction เดียวเท่านั้นที่ถือ และห้ามมี lock อื่นใดค้างอยู่บน object นั้น |
Two-Phase Locking (2PL)
Deadlock: การติดตายเมื่อรอ Lock กันเป็นวงกลม
| แนวทางรับมือ | วิธีการ |
|---|---|
| Deadlock Prevention | ออกแบบการขอ lock ให้ deadlock เกิดไม่ได้ตั้งแต่ต้น เช่น บังคับขอ lock ตามลำดับคงที่เสมอ — แต่จำกัดความยืดหยุ่นของระบบมาก |
| Deadlock Detection | ยอมให้ deadlock เกิดขึ้นได้ แล้วตรวจจับด้วย wait-for graph (ใครรออะไรอยู่) หากพบวงกลมในกราฟ แปลว่ามี deadlock ต้อง abort transaction ตัวใดตัวหนึ่งในวงเพื่อตัดวงจร |
| Lock Timeout | กำหนดเวลาสูงสุดที่รอ lock ได้ ถ้าเกินเวลาถือว่าอาจติด deadlock แล้ว abort ทิ้งไปเลย — ใช้งานง่ายแต่ไม่แม่นยำ (อาจ abort transaction ที่ไม่ได้ deadlock จริง แค่รอนาน) |
4Optimistic Concurrency Control (OCC)
| Phase | การทำงาน |
|---|---|
| 1. Working phase | Transaction อ่าน/เขียนข้อมูลบนสำเนาชั่วคราวของตัวเอง (tentative version) ไม่กระทบข้อมูลจริง |
| 2. Validation phase | ตรวจสอบว่า operation ที่ทำไปขัดแย้งกับ transaction อื่นที่ commit ไปแล้วระหว่างนี้หรือไม่ |
| 3. Update phase | ถ้า validate ผ่าน เขียนผลลัพธ์จริงลงระบบ (commit) ถ้าไม่ผ่าน abort แล้วเริ่มใหม่ |
| รูปแบบ Validation | วิธีตรวจสอบ |
|---|---|
| Backward validation | เทียบชุดข้อมูลที่ transaction นี้อ่าน/เขียน กับ transaction ที่ commit ไปแล้วก่อนหน้า — มองย้อนกลับไปในอดีต |
| Forward validation | เทียบกับ transaction ที่ ยังทำงานอยู่ในขณะนี้ (concurrent, ยังไม่ commit) — มองไปข้างหน้า |
5Timestamp Ordering
6ทันสมัย: MVCC และ Isolation Level ในฐานข้อมูลปัจจุบัน
| Isolation Level (SQL Standard) | อนุญาตปัญหาอะไรได้บ้าง |
|---|---|
| Read Uncommitted | Dirty read ได้ (แทบไม่ใช้จริงในปัจจุบัน) |
| Read Committed | ป้องกัน dirty read แต่ non-repeatable read ยังเกิดได้ (อ่านค่าเดิมสองครั้งได้ค่าไม่เท่ากัน) |
| Repeatable Read / Snapshot Isolation | อ่านเห็นภาพข้อมูล ณ จุดเริ่ม transaction เสมอ แต่ phantom read บางกรณียังเกิดได้ |
| Serializable | รับประกัน serializability เต็มรูปแบบ ตรงกับทฤษฎีในหัวข้อที่ 2 — ราคาคือประสิทธิภาพต่ำสุดในบรรดา isolation level ทั้งหมด |
- อธิบายปัญหา lost update และ dirty read พร้อมยกตัวอย่างที่ต่างจากในบทเรียน
- อธิบายว่า serial equivalence เป็นเกณฑ์ตัดสิน "ความถูกต้อง" ของ schedule อย่างไร
- เปรียบเทียบ pessimistic concurrency control (locking) กับ optimistic concurrency control ว่าเหมาะกับสถานการณ์แบบใดต่างกัน
- อธิบายว่าทำไม Two-Phase Locking จึงนำไปสู่ความเป็นไปได้ของ deadlock เสมอ และมีวิธีรับมือ deadlock กี่แบบ
- อธิบายว่า MVCC ในฐานข้อมูลสมัยใหม่ผสมผสานแนวคิดจาก timestamp ordering และ optimistic concurrency control อย่างไร
7Transaction รักษา Invariant ไม่ใช่เพียงรวมคำสั่งหลายบรรทัด
คนมักอธิบาย transaction ว่า “ทำทั้งหมดหรือไม่ทำเลย” ซึ่งครอบ Atomicity แต่ยังไม่อธิบายว่าทำไปเพื่ออะไร แก่นจริงคือการรักษา invariant หรือเงื่อนไขที่ต้องเป็นจริงของข้อมูล เช่นยอดเงินรวมไม่หาย ที่นั่งหนึ่งที่ไม่ขายให้สองคน และ stock ไม่ติดลบโดยไม่มีนโยบายรองรับ
Consistency ใน ACID ไม่ได้หมายถึง replica ทุกชุดเห็นข้อมูลเหมือนกันแบบ distributed consistency แต่หมายถึง transaction ที่ถูกต้องพาระบบจาก state ที่ invariant เป็นจริงไปยังอีก state ที่ invariant ยังเป็นจริง ฐานข้อมูลช่วยด้วย constraint และ isolation แต่ business invariant ที่ไม่ประกาศให้ระบบรู้ยังเป็นหน้าที่ของ application
| Invariant | ตัวอย่าง operation | กลไกที่ช่วย |
|---|---|---|
| ยอดรวม A+B คงเดิม | โอนเงิน | transaction + row lock/serializable |
| email ไม่ซ้ำ | สมัครสมาชิกพร้อมกัน | unique constraint ไม่ใช่ check ในโค้ดอย่างเดียว |
| stock ≥ 0 | ตัด stock หลาย order | atomic conditional update/version |
| หนึ่ง idempotency key ต่อหนึ่งผล | retry ชำระเงิน | unique key ผูกกับ business commit |
Application check ที่ดูถูกแต่แข่งกันได้
if stock >= quantity:
stock = stock - quantity
save(stock)
โค้ดนี้ถูกในโลกทีละ request แต่ผิดเมื่อสอง transaction อ่าน stock เดิมพร้อมกัน การตรวจแล้วเขียนเป็นสองเหตุการณ์ ถ้าไม่มี lock, version check หรือ atomic SQL update ทั้งคู่ผ่านเงื่อนไขได้ การเขียนโค้ดที่ “อ่านง่าย” ไม่ได้ทำให้ schedule ถูกต้อง
8จาก Interleaving ไปสู่ Serializability
Concurrency ทำให้ operation ของหลาย transaction สลับกันได้ เราไม่จำเป็นต้องบังคับให้รันทีละ transaction เพราะจะเสีย parallelism เป้าหมายคืออนุญาต interleaving ที่ผลเทียบเท่ากับ serial order บางลำดับ สิ่งนี้เรียกว่า serializability
Operation conflict เมื่อมาจากคนละ transaction เข้าถึง item เดียวกัน และอย่างน้อยหนึ่ง operation เป็น write: read–write, write–read และ write–write ส่วน read–read สลับกันได้โดยไม่เปลี่ยนผล
T1: R(A) W(A) T2: R(A) W(A) Schedule S: R1(A), R2(A), W1(A), W2(A)
ถ้าทั้งคู่เพิ่มค่า A จาก 10 คนละ 5 ต่างอ่าน 10 และเขียน 15 ผลสุดท้ายเป็น 15 แทน 20 นี่คือ lost update Schedule ไม่เทียบเท่ากับการรัน T1→T2 หรือ T2→T1 ซึ่งควรได้ 20
Precedence/Serialization Graph
สร้าง node ต่อ transaction และวาด edge Ti→Tj ถ้า operation ของ Ti มาก่อนและ conflict กับ Tj บน item เดียวกัน ถ้ากราฟไม่มี cycle schedule เป็น conflict-serializable และ topological order ให้ serial order ที่เทียบเท่า ถ้ามี cycle ไม่มีทางสลับ non-conflicting operation ให้กลายเป็น serial schedule ได้
- เขียน schedule พร้อม transaction ID และ data item
- หา conflict ทุกคู่ตามลำดับที่เกิด
- วาด edge จาก operation ก่อนหน้าไป transaction หลัง
- ตรวจ cycle และอธิบาย ไม่ใช่เดาจากหน้าตาว่าสลับเยอะ
Serializability ไม่เท่ากับลำดับเวลาจริงเสมอ
Serializability ต้องการผลเทียบเท่า serial order หนึ่ง แต่ไม่ได้บังคับว่า transaction ที่เริ่มหรือจบก่อนในโลกจริงต้องมาก่อนเสมอ Strict serializability เพิ่ม real-time order เข้าไป ถ้า T1 จบก่อน T2 เริ่ม ทุกคนต้องเห็น T1 ก่อน T2 แนวคิดนี้สำคัญใน distributed database ที่ต้องอธิบาย guarantee ต่อผู้ใช้
9Anomaly มากกว่า Dirty Read และ Lost Update
| Anomaly | เหตุการณ์ | ผลที่เห็น |
|---|---|---|
| Dirty read | อ่านค่าที่ยังไม่ commit | ใช้ค่าที่อาจถูก rollback |
| Non-repeatable read | อ่าน row เดิมสองครั้งแต่มีคน commit เปลี่ยนกลางทาง | transaction เดียวเห็นค่าไม่เท่ากัน |
| Phantom | query ตาม predicate สองครั้ง มี row เข้า/ออกชุด | จำนวนผลลัพธ์เปลี่ยน |
| Lost update | write ใหม่ทับผลของอีก transaction จากค่าเก่า | การแก้หนึ่งชุดหาย |
| Write skew | สอง transaction อ่าน snapshot เดียวและเขียนคนละ row | แต่ละ row ไม่ conflict ตรง ๆ แต่ invariant รวมเสีย |
| Read skew | อ่านข้อมูลสัมพันธ์กันจากคนละเวลา | เห็น state ที่ไม่เคยอยู่พร้อมกัน |
Write Skew: กับดักของ Snapshot Isolation
สมมุติต้องมีแพทย์เวรอย่างน้อยหนึ่งคน Alice และ Bob อยู่เวร ทั้งคู่ต้องการลา แต่ละ transaction อ่าน snapshot ว่าอีกคนยังอยู่จึงแก้ row ของตนเป็นลา ทั้งคู่เขียนคนละ row ไม่มี write–write conflict จึง commit ได้ ผลสุดท้ายไม่มีใครอยู่เวรและ invariant เสีย
Snapshot Isolation ป้องกัน anomaly จำนวนมากและให้ read concurrency ดี แต่ไม่เท่ากับ serializable ทุกกรณี การป้องกัน write skew อาจใช้ serializable isolation, explicit lock บนสิ่งแทน invariant, materialized constraint หรือออกแบบข้อมูลให้ conflict บน row เดียวกัน
10Locking แบบที่ระบบจริงต้องตัดสินใจ
Shared/Exclusive lock เป็นจุดเริ่ม แต่ระบบจริงต้องเลือก granularity ถ้าล็อกทั้ง table overhead ต่ำแต่ concurrency ต่ำ ถ้าล็อกทีละ row concurrency สูงแต่มี lock จำนวนมาก และ predicate/range query อาจต้องป้องกัน phantom ด้วย range/index lock
| Granularity | ข้อดี | ข้อเสีย |
|---|---|---|
| Database/Table | จัดการง่าย lock น้อย | block งานที่ไม่ conflict จริงจำนวนมาก |
| Page/Partition | สมดุลบาง workload | row คนละตัวใน page เดียวกันยังขวางกัน |
| Row | concurrency สูง | memory/management overhead และ deadlock ซับซ้อน |
| Predicate/Range | ป้องกัน phantom ตามเงื่อนไข query | implementation และ reasoning ยากขึ้น |
Two-Phase Locking กับ Strictness
Basic 2PL รับประกัน conflict serializability โดยมี growing/shrinking phase แต่ถ้าปล่อย write lock ก่อน commit transaction อื่นอาจอ่านค่าที่ยัง rollback ได้และเกิด cascading abort Strict 2PL ถือ exclusive lock ถึง commit/abort ส่วน rigorous 2PL ถือทั้ง shared และ exclusive ถึงจบ ทำ recovery reasoning ง่ายขึ้นแต่เพิ่มเวลาถือ lock
Deadlock เป็นผลจากนโยบายรอ ไม่ใช่ Bug แปลกประหลาด
ถ้า T1 lock A รอ B และ T2 lock B รอ A เกิดวงจรใน wait-for graph แนวทางมีสามกลุ่ม: ป้องกันด้วย ordering/preclaim, หลีกเลี่ยงด้วย timestamp policy เช่น wait-die/wound-wait หรือยอมให้เกิดแล้ว detect cycle/เลือก victim
| วิธี | หลัก | ผลข้างเคียง |
|---|---|---|
| Global lock order | ทุกคนขอ A ก่อน B | ต้องรู้ชุด lock/order และอาจลด flexibility |
| Wait-die | transaction เก่ารอ ใหม่ abort เมื่อชนเก่า | ลด cycle แต่ transaction ใหม่อาจ restart |
| Wound-wait | เก่าบังคับใหม่ abort ใหม่รอเก่า | ให้ priority งานเก่า |
| Detection | สร้าง wait-for graph หา cycle | มี overhead และต้องเลือก victim |
| Timeout | รอนานแล้ว abort | ง่ายแต่ false positive และแก้ช้าถ้าค่ายาว |
การเลือก victim ควรดู work ที่ทำไป จำนวน lock, rollback cost, อายุ และ starvation Transaction ที่ถูก abort ซ้ำต้องมี priority/backoff ไม่เช่นนั้นระบบ technically เดินหน้าได้แต่ผู้ใช้รายหนึ่งไม่มีวันสำเร็จ
11Optimistic Concurrency Control: เดิมพันว่า Conflict น้อย
OCC ให้ transaction ทำงานบน tentative state แล้ว validate ก่อน commit จึงไม่มี lock wait ระหว่าง working phase เหมาะกับ read-heavy, transaction สั้น และ conflict ต่ำ แต่เมื่อ contention สูง งานจำนวนมากทำเกือบเสร็จแล้ว abort ทำให้ทรัพยากรถูกใช้ซ้ำ
คำว่า optimistic ไม่ได้แปลว่ามองโลกดี แต่เป็นสมมติฐานเชิง workload ว่า conflict rare พอให้ตรวจทีหลังคุ้ม ส่วน pessimistic ยอมจ่าย waiting ล่วงหน้าเพื่อไม่ให้ทำงานที่ต้องทิ้ง ไม่มีวิธีใดดีกว่าโดยไม่รู้ conflict rate และ cost ของ retry
| Workload | แนวทางที่มักเหมาะ | เหตุผล |
|---|---|---|
| อ่านมาก เขียนคนละ key | OCC/MVCC | ลดการ block ที่ไม่จำเป็น |
| hot counter/key เดียว | lock/atomic operation/partition redesign | OCC จะ abort ซ้ำสูง |
| transaction ยาว มี user think time | version check/short transaction | ห้ามถือ DB lock ระหว่างรอคน |
| invariant สำคัญหลาย row | serializable/explicit invariant lock | snapshot อาจ write skew |
Version Column ใน Web Application
UPDATE documents SET body = :new_body, version = version + 1 WHERE id = :id AND version = :version_read;
ถ้า affected rows เป็น 0 แปลว่ามีคนแก้หลังจากเราอ่าน Application ต้องเลือกแจ้ง conflict, merge หรือโหลดใหม่ ไม่ควรเขียนทับเงียบ ๆ นี่คือ OCC แบบที่เห็นได้ใน API ด้วย ETag/If-Match
12Timestamp Ordering และเวลาที่ไม่ได้แปลว่านาฬิกาผนัง
Timestamp ordering กำหนด serial order จาก timestamp ของ transaction แล้วตรวจ read/write ให้ไม่ละเมิดลำดับ Timestamp ในที่นี้เป็น logical order ได้ ไม่จำเป็นต้องเป็นเวลาจริง ทุก data item เก็บ read timestamp และ write timestamp เพื่อรู้ว่า operation ใหม่มาช้าหรือเร็วเกินลำดับที่กำหนด
ข้อดีคือไม่มี deadlock เพราะ transaction ไม่รอ lock แต่ conflict ทำให้ abort และ restart ได้มาก โดยเฉพาะ transaction เก่าที่ช้าถูก transaction ใหม่แซง Thomas Write Rule อาจละทิ้ง obsolete write บางกรณีโดยไม่ abort แต่ต้องวิเคราะห์ semantics ให้ถูก
13MVCC: หลายเวอร์ชันเพื่อให้การอ่านไม่ขวางการเขียน
MVCC เก็บ row หลายเวอร์ชันพร้อม metadata ว่า version ใดมองเห็นสำหรับ snapshot ของ transaction Reader เลือก version ที่เหมาะแทนรอ writer ทำให้ read concurrency สูง แต่ไม่ได้แปลว่าไม่มี lock เลย Writer ยัง conflict กัน และ metadata/version เก่าต้อง vacuum/garbage collect
| ประโยชน์ | ต้นทุนที่ซ่อน |
|---|---|
| consistent snapshot | version storage และ visibility check |
| reader ไม่ block writer หลายกรณี | long transaction กัก version เก่า ทำให้ bloat |
| rollback ใช้ version | cleanup และ transaction ID wraparound บางระบบ |
| replica/read scaling | replication lag ทำ snapshot เก่าอีกชั้น |
Isolation Level เป็น Contract ไม่ใช่ปุ่มความเร็ว
ระดับต่ำกว่ายอม anomaly บางชนิดเพื่อ concurrency แต่ application ต้องรับผลได้ อย่าเลือก Read Committed เพราะ default แล้วสมมุติ transaction ทั้งหมด serializable ในทางกลับกัน Serializable อาจ abort transaction ด้วย serialization failure ซึ่ง application ต้อง retry ทั้ง transaction อย่างถูกต้อง
ชื่อ level มีรายละเอียดต่างกันตาม database เช่น Repeatable Read ของ PostgreSQL ใกล้ snapshot isolation ขณะที่ implementation อื่นต่างออกไป ต้องอ่านเอกสารระบบจริงและทดสอบ anomaly ไม่ใช้ชื่อมาตรฐานอย่างเดียวอนุมาน guarantee
14Concurrency Control ข้าม Process กับข้าม Service
บทนี้เน้น database/site เดียวเป็นฐาน ก่อนบท Distributed Transactions แต่ application สมัยใหม่มักเผลอแบ่ง invariant ข้าม service เช่น Order กับ Inventory อยู่คนละฐานข้อมูล Lock ในฐานหนึ่งไม่ครอบอีกฐาน การเรียกสอง API ต่อกันจึงไม่เป็น transaction เดียวโดยธรรมชาติ
ถ้าต้อง consistency ทันที อาจใช้ distributed transaction/2PC โดยยอม coordination และ availability cost ถ้ายอมขั้นกลางได้ ใช้ Saga/compensation แต่ต้องนิยาม state intermediate และ action ที่ย้อนกลับไม่ได้ สิ่งสำคัญคือไม่เรียก workflow หลาย service ว่า ACID transaction หากไม่มี protocol ที่รับประกันจริง
15Workshop: ทำให้ Anomaly เกิดก่อนแก้
ใช้ PostgreSQL/MySQL หรือฐานข้อมูลที่เข้าถึงได้ เปิดสอง session และทำการทดลองทีละขั้น การเห็น anomaly จริงช่วยแยกสิ่งที่ฐานข้อมูลรับประกันจากสิ่งที่เราคิดว่า transaction ควรทำ
- สร้างบัญชี A/B และตารางแพทย์เวร พร้อม constraint พื้นฐาน
- ทำ lost update ด้วย read–compute–write จากสอง session
- แก้ด้วย atomic update และเปรียบเทียบกับ SELECT FOR UPDATE
- ทดลอง non-repeatable read ภายใต้ Read Committed
- ทดลอง snapshot เดิมภายใต้ Repeatable Read
- สร้าง write skew แพทย์เวร แล้วทดลอง Serializable
- สร้าง deadlock โดย lock A/B สลับลำดับ จด error และ victim
- เพิ่ม version column ทำ OCC แล้วทดสอบ conflict
- วัด workload conflict ต่ำ/สูง เปรียบเทียบ waiting กับ abort rate
| สิ่งที่เก็บ | เหตุผล |
|---|---|
| SQL พร้อมลำดับเวลา | สร้าง schedule ที่ตรวจซ้ำได้ |
| ค่าที่แต่ละ session อ่าน | เห็น local view ต่างกัน |
| commit/abort/error | รู้ว่า database เลือกรักษา guarantee อย่างไร |
| lock/wait information | เชื่อมอาการช้ากับ contention |
| final invariant | performance ไม่มีความหมายถ้าข้อมูลผิด |
16แนวทางเลือกกลไกโดยไม่เริ่มจากความชอบ
- เขียน invariant และ anomaly ที่ยอมรับไม่ได้ก่อน
- ระบุขอบเขตข้อมูล: row, table, database หรือหลาย service
- วัด read/write ratio, conflict distribution, transaction duration และ hot key
- เลือก isolation/control ที่ป้องกัน anomaly ด้วยต้นทุนต่ำสุดที่รับได้
- ออกแบบ retry สำหรับ deadlock/serialization failure ให้ทั้ง transaction และ idempotent
- ทดสอบ concurrent schedule ไม่ใช่ unit test ทีละ request
- monitor lock wait, abort rate, long transaction, version bloat และ tail latency
17สรุปและขั้นตอนถัดไป
Concurrency Control เริ่มจาก invariant และ schedule ไม่ใช่เริ่มจากเลือก lock ACID แยกความรับผิดชอบหลายด้าน โดย Isolation ทำให้ผลของงานพร้อมกันมีความหมายเสมือน serial order ระดับหนึ่ง แต่ isolation level ที่ต่ำกว่า Serializable ยอม anomaly บางชนิดและ application ต้องรู้
Locking จ่ายด้วย waiting/deadlock, OCC จ่ายด้วย validation/abort, timestamp ordering จ่ายด้วย restart เมื่อผิดลำดับ และ MVCC จ่ายด้วยหลายเวอร์ชัน/cleanup การเลือกต้องสัมพันธ์กับ contention และ invariant ระบบที่เร็วเพราะไม่ตรวจ conflict อาจเพียงผลิตข้อมูลผิดได้เร็วขึ้น ซึ่งไม่นับเป็น scalability ที่น่าภูมิใจนัก
ขั้นตอนถัดไปคือ Distributed Transactions เมื่อ atomic action ต้องข้ามหลาย process และหลายฐานข้อมูล เราจะพบว่า lock และ commit ภายใน site เดียวไม่พอ ต้องมี protocol ให้ participant ตกลงผลร่วมกัน ทั้งที่ coordinator หรือ network อาจล้มกลางทาง นั่นคือจุดที่ correctness เริ่มแลกกับ availability และเวลารออย่างชัดเจนขึ้น