โหมดมืด
บทที่ 1 — Consistency + CAP
ก่อนอ่าน — ต้องรู้อะไรมาก่อน
- พื้นฐาน distributed system จาก บทที่ 0 — Mindset (network ไม่น่าเชื่อถือ, latency มีจริง)
- พื้นฐาน database transaction (ACID) ระดับใช้งาน — read/write/commit
- ไม่ต้องเก่งคณิตศาสตร์ — บทนี้มีสูตรเล็กน้อย (quorum) จะอธิบายตั้งแต่ต้น
ศัพท์ย่อที่จะเจอบ่อย — กางไว้ก่อน:
- CAP (ซี-เอ-พี) = Consistency / Availability / Partition tolerance
- PACELC (เพ-เซล-ซี) = Partition→A-vs-C / Else→Latency-vs-Consistency
- ACID = Atomicity, Consistency, Isolation, Durability (transaction ของ SQL DB)
- BASE = Basically Available, Soft state, Eventual consistency (ตรงข้ามกับ ACID)
- SI = Snapshot Isolation, SSI = Serializable Snapshot Isolation
- MVCC = Multi-Version Concurrency Control
- CRDT = Conflict-free Replicated Data Type
หลังจบบท คุณจะ:
- เข้าใจ Consistency models — strong, eventual, causal, ...
- ลึก CAP theorem + PACELC
- เลือกแบบที่เหมาะ application
- เข้าใจ quorum + read/write balance
1. ทำไม Consistency ยาก
บนเครื่องเดียว consistency เป็นเรื่องง่าย — เขียนแล้วอ่านครั้งต่อไปก็เห็นค่าใหม่ทันที แต่พอมีหลาย node (replica) ปัญหาก็โผล่: เขียนที่ node A แต่ไปอ่านที่ node B ที่ยังไม่ได้รับ update จะเห็นข้อมูลเก่า (stale) นี่คือ trade-off หลักของระบบกระจายที่ทั้งบทนี้จะเจาะ:
text
เครื่องเดียว (Single machine):
- เขียนเสร็จ → อ่านครั้งถัดไปเห็นค่าใหม่ทันที
- ง่าย
กระจาย (Distributed):
- เขียนที่ Node A
- อ่านจาก Node B (replica)
- มีช่องว่างเวลา: B ได้รับ update แล้วหรือยัง?
- ระหว่างช่องว่างนั้น: B อาจตอบค่าเก่า (stale read)→ Trade-off: ความสอดคล้องทันที (instant consistency) แลกกับ availability + latency
2. Consistency Levels (Strongest → Weakest)
"consistency" ไม่ใช่มีแค่เปิด/ปิด แต่เป็นสเปกตรัมหลายระดับ — ไล่จากแรงสุด (linearizability: ทุกคนเห็นเหมือนเครื่องเดียว) ลงมาถึงอ่อนสุด (eventual: เดี๋ยวก็ตรงกันเอง) ยิ่งแรงยิ่งเข้าใจง่ายแต่แลกด้วย latency/availability ภาพนี้คือแผนที่ที่หัวข้อถัด ๆ ไปจะอธิบายทีละชั้น:
คำอ่าน + คำแปล (สำหรับผู้อ่านไทย):
- Linearizability (ลิ-เนีย-ไรซ์-อะ-บิ-ลิ-ตี้) — ความสอดคล้องแบบเชิงเส้น (ทุกคนเห็นเหมือนเครื่องเดียว)
- Sequential Consistency (ซี-เควน-เชิล คอน-ซิส-เทน-ซี) — ความสอดคล้องตามลำดับ
- Causal Consistency (คอ-เซิล คอน-ซิส-เทน-ซี) — ความสอดคล้องตามเหตุ-ผล
- Read-Your-Writes (รีด-ยัว-ไรท์ส) — อ่านสิ่งที่ตัวเองเขียนได้
- Monotonic Reads (โม-โน-โท-นิก รีดส์) — อ่านไม่ถอยหลัง (monotonic = ไปทางเดียว)
- Eventual Consistency (อี-เวน-ชวล คอน-ซิส-เทน-ซี) — สอดคล้องในที่สุด
3. Linearizability — "เหมือนใช้ machine เดียว"
นี่คือ consistency ที่แรงที่สุดเท่าที่ทำได้ในระบบ distributed
เริ่มจาก Analogy ก่อน (ค่อยขึ้นนิยามทางการทีหลัง)
นิยามเป็นทางการของ linearizability อ่านครั้งเดียวไม่เข้าหัวแน่ — เริ่มจากภาพ ATM ก่อน แล้วค่อยกลับมาดูนิยามทีหลัง
Analogy — ตู้ ATM ที่มีเพียงเครื่องเดียว
text
ลองนึกว่ามี ATM เครื่องเดียวในโลก
- คุณกด "ถอน 1000" → ATM จ่ายเงิน → ยอดในบัญชี ลด
- เพื่อนกด "เช็คยอด" หลังจากนั้น → เห็นยอดที่ลดแล้วแน่นอน
- ไม่มีทางที่เพื่อนจะ "เห็นยอดเก่า"
นี่คือ linearizable — เพราะมี ATM เดียว
มี state เดียว ที่ทุกคน "เห็น" ตรงกันทีนี้ในระบบ distributed — มีหลาย replicas (replicas A, B, C) แต่ระบบ linearizable ทำเหมือน มี state เดียว
→ แม้ client 2 อ่านจาก replica คนละตัวจาก client 1 — ต้อง เห็นค่าใหม่
📌 นิยามอย่างเป็นทางการ (ตัดเป็น 3 ข้อให้อ่านง่าย):
- ทุก operation "ดูเหมือน" เกิดขึ้นที่จุดเวลาเดียว (instantaneous — ไม่ใช่ช่วงเวลา)
- จุดเวลานั้นอยู่ระหว่างเวลาที่ operation เริ่มกับเวลาที่ operation จบ
- ทุก client มองเห็นลำดับเดียวกัน และลำดับนั้นตรงกับเวลาจริง (wall clock)
⚠️ Linearizability ≠ Serializability — สองคำนี้ฟังคล้ายกันแต่ไม่ใช่อันเดียวกัน:
- Linearizability = property ของ single object (เช่น single key) ที่ทุก operation ดูเหมือน atomic ตามเวลาจริง
- Serializability = property ของ transaction (multi-object) ที่ schedule เทียบเท่ากับการรัน serial
- Spanner = ทั้ง linearizable + serializable; Postgres SERIALIZABLE = serializable แต่ไม่จำเป็นต้อง linearizable (single instance ก็ linearizable โดยปริยาย, distributed ไม่)
ตัวอย่างที่ "ไม่ linearizable"
text
Replica A: ──Write(x=1)──→[ACK to Client 1]
(ยังไม่ replicate ไป B)
Replica B: ←──Read(x)── Client 2
B ตอบ "x=0"
❌ ไม่ linearizable!
เพราะ Client 1 เขียนเสร็จไปก่อน Client 2 อ่าน
แต่ Client 2 เห็นค่าเก่าเปรียบเทียบ Linearizability vs Sequential vs Eventual
| Property | Linearizable | Sequential | Eventual |
|---|---|---|---|
| Order ตรงกับเวลาจริง | ✅ | ❌ | ❌ |
| ทุก client เห็น order ตรงกัน | ✅ | ✅ | ❌ |
| Read หลัง write = ค่าใหม่ | ✅ | ❌ ไม่รับประกัน (ไม่ใช่ real-time) | ❌ |
| Slow? | ⚠️ Yes (coordination) | ⚠️ Yes | ✅ Fast |
ต้นทุนของ Linearizability
text
✅ ข้อดี:
- ใช้คิดง่าย (เหมือน single machine)
- bug น้อยกว่า (programmer ไม่ต้องคิด edge case)
❌ ข้อเสีย:
- ช้า — ทุก operation ต้อง coordinate ข้าม nodes
- ลด availability — partition = block (CAP)
- ขัด latency requirement ของ multi-region (รอ round-trip 100-300ms)Tools ที่ให้ Linearizability
| Tool | คืออะไร (1 บรรทัด) |
|---|---|
| Spanner (Google) | Distributed SQL database ของ Google — ใช้ TrueTime (hardware GPS + atomic clock) → linearizable ระดับ global |
| CockroachDB (ค็อก-โรช-ดีบี) | Distributed SQL ของ Cockroach Labs — ใช้ HLC (software clock) → linearizable ระดับ cluster |
| etcd (เอ็ท-ซีดี) | Key-value store ของ CoreOS — Raft-based, linearizable สำหรับ small dataset (ใช้ใน Kubernetes) |
| Postgres (sync replication) | RDBMS โอเพ่นซอร์ส — linearizable ที่ primary + replicas ที่ sync |
⚠️ TrueTime คือ infra พิเศษของ Google — ทั่วไป copy ไม่ได้: TrueTime ทำงานได้เพราะ Google deploy GPS receivers + Caesium atomic clocks เป็น hardware ในทุก datacenter เพื่อให้คืน "ช่วงเวลา" (interval) ที่รับประกัน bounded uncertainty (typical 4 ms, upper bound ~5-7 ms ตาม Spanner paper) — ผู้อ่านที่ไม่ใช่ Google ไม่สามารถ copy ได้ตรง ๆ
📌 ส่วนนี้มี jargon เยอะ (HLC, sequencer, TSO, NTP, deterministic transactions) — ถ้ายังไม่คุ้น ข้ามได้ ค่อยกลับมาอ่านหลังอ่าน บทที่ 4 — Time + Ordering
ปัจจุบันใน open source โลก ตัวเลือก linearizable distributed SQL ใช้ software clocks แทน:
Tool กลไก หมายเหตุ CockroachDB (ค็อก-โรช-ดีบี) HLC (Hybrid Logical Clock) + NTP sync linearizable แต่ assume bounded clock skew (~500 ms) YugabyteDB (ยู-กา-ไบต์-ดีบี) HLC + NTP sync Postgres-compatible distributed SQL FoundationDB (ฟาวเดชัน-ดีบี) Centralized sequencer (เหมือนเครื่องแจกตั๋วในธนาคาร) Apple-owned (acquired 2015), Apache 2.0 (2018), ใช้ใน Snowflake metadata + iCloud Calvin / FaunaDB (คาลวิน / ฟอ-นา-ดีบี) Deterministic transactions ไม่ใช้ clock งานวิจัย Yale + commercial spinoff TiDB / TiKV (ไท-ดีบี / ไท-เควี) Multi-Raft + PD (Placement Driver) เป็น TSO (timestamp oracle) MySQL-compatible distributed SQL จาก PingCAP เลือกตาม trade-off: HLC = บัง latency จริง แต่ correctness ขึ้นกับ NTP; sequencer = correctness สูง แต่ throughput bound by 1 component
เมื่อไหร่จำเป็นต้องใช้ Linearizability
text
ต้องใช้:
✅ Banking — ยอดเงินต้องตรง ไม่งั้นเงินหาย
✅ Inventory — ห้าม oversell (ขายเกินสต๊อก)
✅ Distributed lock — only 1 holder
✅ Authentication — change password ต้องมีผลทันที
ไม่จำเป็น (ใช้ weaker ได้):
- Like count
- View count
- Social feed
- Search index→ กฎทอง: ใช้ linearizable เฉพาะที่จำเป็น — ที่เหลือใช้ weaker model จะเร็วกว่า + availability สูงกว่า
4. Sequential Consistency (ซี-เควน-เชิล คอน-ซิส-เทน-ซี = ความสอดคล้องตามลำดับ) — "ทุกคนเห็น order เดียวกัน แต่ไม่จำเป็นต้องตรงกับเวลาจริง"
นี่คือ consistency ที่อ่อนกว่า linearizable เล็กน้อย — แต่ยังใช้คิดง่ายอยู่
ความแตกต่างจาก Linearizable
text
Linearizable:
- ทุก operation ต้อง "appear" ที่เวลาจริงระหว่าง start กับ end
- เคารพเวลาจริง (wall clock)
Sequential:
- ทุก operation จาก process เดียวกัน → in order
- ข้าม processes → order ไหนก็ได้ (แต่ทุกคนเห็น order เดียวกัน)
- ไม่จำเป็นต้องตรงกับเวลาจริงตัวอย่างเปรียบเทียบ
text
Real-time: P1 Write(x=1) → 10:00:00.000
P1 Write(x=2) → 10:00:00.100
P2 Read(x) → 10:00:00.050 (อยู่ระหว่าง W1 กับ W2)
Linearizable:
P2 ต้องได้ x=1 (เพราะ Read อยู่ระหว่าง W1 กับ W2 ในเวลาจริง)
ไม่สามารถได้ x=0 หรือ x=2
Sequential:
P2 อาจได้ x=0 หรือ x=1 หรือ x=2 ก็ได้
ตราบใดที่ "order ใน P1" ยังคง (1 ก่อน 2)
และทุก observer เห็น order เดียวกันAnalogy — "หนังสือพิมพ์รายวัน"
text
หนังสือพิมพ์ออกทุกวัน 6:00 เช้า
- พิมพ์ข่าวของเมื่อวาน (อาจช้ากว่าเวลาจริงครึ่งวัน)
- แต่ทุกคนที่อ่านหนังสือพิมพ์ → เห็น order ของข่าวเดียวกัน
ไม่ linearizable (ข่าวที่เกิดเช้านี้ → ไม่เห็นในฉบับเช้านี้)
แต่ sequential (ทุกคนเห็นข่าวเรียงลำดับเดียวกัน)เมื่อไหร่ใช้
ในระบบจริง — sequential consistency ใช้น้อยกว่า linearizable + eventual เพราะ:
- เร็วกว่า linearizable เพียงเล็กน้อย
- แต่ implementation ยังยุ่งยาก (ต้องใช้ consensus)
- ผู้ใช้ส่วนใหญ่ "ยอม" ไปใช้ eventual หรือ causal เลย
ตัวอย่าง use case:
- Replicated state machine ของ distributed DB บางตัว
- Distributed shared memory ในงานวิจัย
- Cache coherency ใน multi-core CPU (Intel x86 ใช้ TSO — Total Store Order, อ่าน "ทอ-ทอล สโตร์ ออเดอร์" = ลำดับการ store ทั้งหมด)
⚠️ x86-TSO ≠ Sequential Consistency: x86-TSO อ่อนกว่า sequential — อนุญาตให้ store-to-load reordering ได้ (StoreLoad reorder) เพื่อให้ CPU เร็วขึ้น ถ้าต้องการ sequential semantics จริง ๆ ต้องใช้
mfenceหรือคำสั่งlock-prefixed
→ มือใหม่: รู้ว่ามี ก็พอ, ใช้บ่อยน้อยกว่าตัวอื่น
5. Causal Consistency — ลำดับเหตุ-ผล
causal consistency รับประกันแค่สิ่งที่ "มีเหตุ-ผลต่อกัน" ต้องเห็นตามลำดับเดียวกันทุกที่ ส่วนเหตุการณ์ที่ไม่เกี่ยวกัน (concurrent) จะสลับลำดับได้ — เช่นทุกคนต้องเห็นโพสต์ก่อนเห็นคอมเมนต์ของโพสต์นั้น แต่โพสต์ของคนอื่นที่ไม่เกี่ยวกันจะมาก่อนหรือหลังก็ได้ เป็นจุดสมดุลที่ดีระหว่างความแรงกับ performance:
text
"เคารพลำดับเหตุ-ผล (cause and effect)"
operation ที่มีความสัมพันธ์เหตุ-ผลกัน: เห็นลำดับเดียวกันทุกที่
operation ที่ไม่เกี่ยวกัน (concurrent): ลำดับสลับกันได้
ตัวอย่าง:
- Alice โพสต์ "Hello"
- Bob คอมเมนต์ "Hi" บนโพสต์นั้น (เกิดจากโพสต์ = มีเหตุ-ผล)
- ผู้ชมทุกคน: เห็นโพสต์ "ก่อน" คอมเมนต์เสมอ
- โพสต์ของคนอื่นที่ไม่เกี่ยวกัน: ลำดับไหนก็ได้นิยาม (Lamport's Happens-Before — "เกิดก่อน")
📌 Lamport อ่าน "แลม-พอร์ต" — มาจากชื่อ Leslie Lamport นักวิจัยผู้คิดค้นแนวคิดนี้ (เจ้าของรางวัล Turing Award 2013)
text
A "เกิดก่อน (happens before)" B เมื่อ:
1. A และ B อยู่ใน process เดียวกัน และ A มาก่อน
2. A คือการส่ง (send), B คือการรับ (receive) ของ message เดียวกัน
3. ส่งต่อกันได้ (transitive): ถ้า A → B และ B → C แปลว่า A → C
operation ที่ไม่มีความสัมพันธ์ happens-before = concurrent (เกิดพร้อมกัน/ไม่เกี่ยวกัน)การใช้งาน
text
✅ คอมเมนต์ปรากฏหลังโพสต์แม่ (Twitter)
✅ ข้อความตอบกลับปรากฏหลังข้อความต้นทาง (chat)
✅ ผู้ใช้เห็นผลของ action ตัวเอง
❌ ไม่รับประกันลำดับรวม (global ordering) ของ event ที่ไม่เกี่ยวกัน6. Read-Your-Writes — "ตัวเองเขียน ตัวเองต้องเห็น"
นี่เป็น consistency ที่ "user-facing" — สำคัญมากสำหรับ UX ที่ดี
ปัญหาที่ Read-Your-Writes แก้
text
User update profile picture → server save → response 200 OK
User refresh page → ❌ ยังเห็นรูปเก่า!
ทำไม?
- Write ไปที่ primary
- Read ไปที่ replica (LB เลือก random)
- Replica ยังไม่ sync
- User งง: "ฉันเปลี่ยนแล้ว ทำไมยังไม่เปลี่ยน?"→ Read-your-writes: รับประกันว่า user อ่านค่าของตัวเองได้เสมอ (ส่วน user อื่นไม่จำเป็นต้องเห็นทันที)
Analogy — Email ส่งให้ตัวเอง
text
คุณส่ง email หาตัวเอง
- ส่ง → server save
- เปิด inbox → ต้องเห็น email ที่เพิ่งส่ง (read-your-writes)
- แต่เพื่อนที่ login email server เดียวกัน → ไม่จำเป็นต้องเห็น
ของคุณทันที (ยอม eventual ได้)Implementation Patterns
Pattern 1: Sticky session — route reads ไปที่ primary หลังเขียน
typescript
// After write
session.lastWriteAt = Date.now();
session.useLeaderUntil = Date.now() + 10_000; // 10 วินาที
// Before read
function chooseReplica(session) {
if (session.useLeaderUntil > Date.now()) {
return primary; // อ่านจาก primary
}
return replicas[random()]; // อ่านจาก replica
}ข้อดี: ง่าย, ใช้ได้กับ DB ทั่วไป ข้อเสีย: Primary โหลดสูง, ไม่ scale ดี
Pattern 2 — Write-then-read consistency token (ใช้ token ตามการเขียน)
typescript
// After write, server returns a "consistency token"
const result = await db.write({...});
// result.token = "WAL position 12345" หรือ "timestamp"
// Subsequent read includes token
const data = await db.read({ id, requireConsistencyToken: result.token });
// → DB ตรวจสอบว่า replica sync ถึง token นี้แล้ว, ถ้าไม่ → route ไป leader หรือ waitใช้ใน:
- MongoDB —
causalConsistency+clusterTime - Azure Cosmos DB —
x-ms-session-token(session consistency) - DynamoDB —
ConsistentRead: true - หมายเหตุ: CockroachDB อ่านเป็น linearizable by default จึงไม่ต้องใช้ token;
AS OF SYSTEM TIMEใน CockroachDB เป็น historical/bounded-staleness read ไม่ใช่ session token
Pattern 3 — Optimistic UI + background sync (อัปเดต UI ทันที + sync เบื้องหลัง)
typescript
// User edits profile
await api.updateProfile(newData);
// UI updates immediately (assume success)
setLocalState(newData);
// Background: invalidate query, fetch fresh after replica catches up
setTimeout(() => queryClient.invalidateQueries(['profile']), 2000);ใช้ใน: Modern SPAs (React Query, SWR, Apollo Client)
Monotonic Reads — น้องของ Read-Your-Writes
text
"อย่าเดินถอยหลังในเวลา"
User read X = 5 ครั้งแรก
Next read X → ห้ามได้ค่าเก่ากว่า 5 (เช่น 4)
ปัญหาเมื่อไม่มี:
- Read replica 1 (sync) → X = 5
- Refresh → LB เลือก replica 2 (lag) → X = 4
- User งง: "ค่ามันลดได้ยังไง?"
Implementation:
- Sticky session (same replica per session)
- Read consistency token→ Read-Your-Writes + Monotonic Reads = combo ที่ดีสำหรับ user-facing apps
7. Eventual Consistency — หลวมที่สุด
text
- การเขียน propagate ไปถึงทุก replica "ในที่สุด" (eventually)
- ระหว่าง propagate: บาง replica ยังไม่ตรงกัน
- เมื่อเวลาผ่านไปพอ: ทุกตัวมาบรรจบ (converge) ที่ค่าเดียวกัน
ไม่มีขอบเขตว่า "ในที่สุด" คือนานแค่ไหน (ในทางปฏิบัติมักเป็นหลัก ms-วินาที)ตัวอย่าง
text
✅ DNS — ใช้เวลา propagate เป็นนาที/ชั่วโมง
✅ Social media — like, view count
✅ Cassandra (ค่า default)
✅ แอป internet-scale ส่วนใหญ่
❌ การโอนเงินธนาคาร
❌ Inventory (กันขายเกินสต๊อก)
❌ การอัปเดต authenticationCRDT (Conflict-free Replicated Data Type) — โครงสร้างข้อมูลที่ "ไม่มีวันขัดแย้ง"
ปัญหาที่ CRDT แก้:
text
Multi-master setup:
- Replica A: counter = 5
- Replica B: counter = 5
Replica A: increment → counter = 6
Replica B: increment (พร้อมกัน) → counter = 6
Sync:
- ใครชนะ? ทั้งคู่บอกว่าเป็น 6
- คำตอบที่ถูก = 7 (เพราะ increment 2 ครั้ง)
- แต่ "last write wins" จะตอบ 6 → ลดหายไป 1 increment!CRDT แก้ปัญหานี้ โดยออกแบบ data structure ให้:
text
✅ Associative (อะ-โซ-ซิ-เอ-ทิฟ = จับกลุ่มได้) — (a ∪ b) ∪ c = a ∪ (b ∪ c)
✅ Commutative (คอม-มิว-ทา-ทิฟ = สลับที่ได้) — a ∪ b = b ∪ a
✅ Idempotent (ไอ-เด็ม-โพ-เทนต์ = ทำซ้ำได้ผลเดิม) — a ∪ a = a
ผลลัพธ์: ไม่ว่าจะ merge ตามลำดับใด ก็ได้ผลเดียวกัน
→ "Eventually convergent" — replicas ทุกตัวจะมาเจอที่ค่าเดียวกันG-Counter (Grow-only Counter) — Trace ตัวอย่าง
text
3 replicas: A, B, C (เก็บค่า counter ของแต่ละ node แยกกัน)
Initial state:
A: {A:0, B:0, C:0} → total = 0
B: {A:0, B:0, C:0} → total = 0
C: {A:0, B:0, C:0} → total = 0
Step 1: A increments
A: {A:1, B:0, C:0} → total = 1
B: ยังไม่รู้
C: ยังไม่รู้
Step 2: C increments (พร้อมกัน — ไม่รู้ของ A)
C: {A:0, B:0, C:1} → total = 1
B: ยังไม่รู้
Step 3: A และ C ต่าง send vector ของตัวเองไป B (ตอนนี้ A กับ C ยังไม่รู้ของกันและกัน)
B รับจาก A: {A:1, B:0, C:0}
B รับจาก C: {A:0, B:0, C:1}
B merge (element-wise max):
{A: max(1,0)=1, B: max(0,0)=0, C: max(0,1)=1}
= {A:1, B:0, C:1}
total = 2 ✅ (ตรงตามคาด!)
Step 4: B sync กลับไป A, C
A merge: {A:1, B:0, C:1} → total = 2 ✅
C merge: {A:1, B:0, C:1} → total = 2 ✅
→ ทุก replica มาบรรจบที่ 2 — converged!→ เคล็ดลับ: เก็บค่าของ "แต่ละ node แยกกัน" — แล้ว merge ด้วย max → ไม่มีทาง overwrite กันได้
Types ของ CRDT — สำหรับ use case ต่างๆ
| CRDT Type | ใช้กับ | ตัวอย่าง |
|---|---|---|
| G-Counter | Counter เพิ่มอย่างเดียว | Like, page view, total sales |
| PN-Counter | Counter เพิ่ม + ลด | Active user (online/offline), inventory level |
| G-Set | Set ที่ add อย่างเดียว | Followers list, tag list |
| OR-Set | Set ที่ add + remove | Cart items, collaborative todo |
| LWW-Register | Single value (timestamp wins) | User profile field, config |
| RGA / Yjs / Automerge | Sequence (text) | Collaborative editor (Notion, Figma) |
📌 คำย่อ CRDT type names:
- G = Grow-only (เพิ่มอย่างเดียว, ไม่ลด/ไม่ remove)
- PN = Positive-Negative (เพิ่ม + ลด)
- OR = Observed-Remove (ลบเฉพาะที่ "เห็น" แล้ว — แก้ปัญหา add/remove race)
- LWW = Last Write Wins (timestamp ชนะ)
- RGA = Replicated Growable Array (sequence สำหรับ text)
💡 มือใหม่: จำแค่ 2 ตัวก็พอ — G-Counter (counter เพิ่มอย่างเดียว) กับ LWW-Register (single value, timestamp ใหม่ชนะ) ที่เหลือเรียนเมื่อต้องใช้
Pros / Cons ของ CRDT
ข้อดี:
- ✅ Multi-master ที่ไม่ต้อง coordinate
- ✅ Offline-first apps (ทำงานได้ไม่มี internet)
- ✅ Eventually consistent โดยอัตโนมัติ
- ✅ ไม่มี conflict resolution UI
ข้อเสีย:
- ❌ ต้องเก็บ metadata เพิ่ม (vector, tombstone) → memory + bandwidth
- ❌ Schema เปลี่ยนยาก
- ❌ ใช้กับ "constraint" ยาก (เช่น "stock ไม่ติดลบ" — ไม่ง่ายกับ CRDT)
- ❌ บางอย่างทำไม่ได้ (เช่น linearizable counter)
ใช้ใน production จริง
- Yjs / Automerge — Notion-clone, Figma-clone, Linear
- Redis Enterprise CRDTs — global active-active database
- Riak — Dynamo-inspired KV store
- Apple Notes — sync ระหว่าง devices
- SoundCloud — CRDT counter สำหรับ play count
8. Snapshot Isolation + Bounded Staleness (สำคัญในระบบจริง)
นอกจาก linearizable + eventual ที่อยู่สุดขั้ว — ในระบบจริงยังมี consistency level "กลาง ๆ" ที่ใช้บ่อย
Snapshot Isolation (SI) — "อ่านจาก snapshot ณ เวลาเริ่ม transaction"
text
Time: ──────────────────────────→
TX 1: Begin─Read(x=5)─...──Commit
↑
TX 2: Write(x=10)─Commit
TX 1 จะเห็น x=5 ตลอด (snapshot ตอน begin)
แม้ TX 2 เขียน x=10 ระหว่างนั้นใช้ใน:
- Postgres — default isolation = Read Committed; REPEATABLE READ = SI (มี write skew anomaly); SERIALIZABLE = SSI (Serializable Snapshot Isolation, Cahill 2008) ที่ detect write skew ผ่าน predicate locks
- CockroachDB — SERIALIZABLE via timestamp ordering + read refresh + uncertainty intervals (ไม่ใช่ classical SSI แบบ Postgres); 23.1+ รองรับ READ COMMITTED ด้วย
- MongoDB — Snapshot read concern
⚠️ Snapshot Isolation ≠ Serializable — มี anomaly ที่เรียก Write Skew:
textDoctor on-call example (Berenson et al. 1995): - Rule: ต้องมีหมออย่างน้อย 1 คน on-call เสมอ - Alice และ Bob ทั้งคู่ on-call (มี 2 คน → OK) - TX1 (Alice): อ่าน count=2 → "มี 2 คน OK ฉันลาได้" → set Alice off-call - TX2 (Bob พร้อมกัน): อ่าน count=2 → "มี 2 คน OK ฉันลาได้" → set Bob off-call - ทั้งคู่ commit สำเร็จ → ไม่มีหมอ on-call เลย ❌เพราะทั้งคู่อ่าน snapshot เดิม (count=2) แล้วเขียนคนละ row → SI ไม่ detect; SSI (Postgres SERIALIZABLE) detect ได้ ผ่าน predicate locks/SIREAD locks
ข้อดี:
- ✅ Reader ไม่ block writer (และ vice versa)
- ✅ Read ใน transaction = consistent view
- ✅ ใกล้เคียง linearizable แต่เร็วกว่ามาก
ข้อเสีย:
- ❌ Write Skew — anomaly ที่ TX สองตัวอ่าน snapshot เดิม แล้วเขียนคนละ row ที่ break invariant ร่วม (ตัวอย่าง doctor on-call ข้างบน); จะเจาะในบท 3 — Postgres SERIALIZABLE/SSI แก้ได้, REPEATABLE READ/SI แก้ไม่ได้
- ❌ ใช้พื้นที่ disk เพิ่ม (เก็บหลาย version)
- MVCC (Multi-Version Concurrency Control) — DB เก็บข้อมูลหลาย version ให้ reader อ่านได้โดยไม่ block writer
- 💡 เปรียบเทียบ: เหมือนเก็บประวัติ version ของไฟล์ Word ทุกครั้งที่ save (ดู glossary บท 11)
Bounded Staleness — "Stale ได้ แต่ไม่เกิน X"
แทนที่จะเลือกระหว่าง "linearizable (slow)" กับ "eventual (might be infinitely stale)" — กำหนด upper bound ว่า stale ได้แค่ไหน
text
Bounded staleness: max staleness = 5 วินาที
- Read อาจได้ค่าเก่า แต่ไม่เก่ากว่า 5 วินาที
- ถ้า replica lag > 5 วินาที → ปฏิเสธ read หรือ route ไป leaderใช้ใน:
- Azure Cosmos DB — มี 5 consistency levels: Strong, Bounded Staleness, Session (default, ใช้บ่อยสุด), Consistent Prefix, Eventual
- DynamoDB (configurable: "strongly consistent read" vs "eventually consistent read")
- Postgres + read replica (set max replica lag threshold)
ข้อดี:
- ✅ Predictable — เผื่อ UI/UX ได้
- ✅ Performance ดีกว่า linearizable
- ✅ ป้องกันกรณี replica ค้างมาก ๆ
เปรียบเทียบครบ — ใช้อะไรเมื่อไหร่?
| Model | Use case | Latency | Availability |
|---|---|---|---|
| Linearizable | Banking, lock, leader election | สูงมาก | ต่ำ (CP) |
| Sequential | Replicated state machine | สูง | กลาง |
| Snapshot Isolation | OLTP DB transactions (Postgres) | กลาง | กลาง |
| Bounded Staleness | Read replicas, analytics ทันเวลาประมาณ | กลาง | สูง |
| Causal | Comments, chat, collaborative | ต่ำ | สูง |
| Read-Your-Writes | Profile updates, user-facing | ต่ำ | สูง |
| Eventual | Likes, view count, analytics | ต่ำ | สูงสุด |
9. เลือก Consistency ตาม Use Case
text
Banking transfer: Linearizable
Inventory (oversell): Linearizable
Auth / role change: Read-your-writes
User post visible: Read-your-writes + causal
Comment on post: Causal
View count: Eventual (approximate OK)
Like count: Eventual (approximate OK)
Social feed: Eventual (a few sec lag OK)
DNS: Eventual
Trending hashtags: Eventual (compute every minute)
Recommendation: Eventual→ อย่าใช้ strong consistency ในที่ที่ไม่จำเป็น — แพง ทั้ง latency และ throughput
10. CAP Theorem — เจาะลึก
CAP theorem คือกฎพื้นฐานที่สุดของระบบกระจาย — เมื่อเกิด network partition (node คุยกันไม่ได้) คุณเลือกได้แค่ 2 ใน 3: Consistency, Availability, Partition tolerance เนื่องจาก network จริงล่มได้เสมอ P จึงบังคับต้องมี ทำให้คำถามจริง ๆ เหลือแค่ "ตอน partition จะเลือก C หรือ A":
text
เสนอโดย Eric Brewer (2000), พิสูจน์โดย Gilbert + Lynch (2002):
เมื่อเกิด network partition (เครือข่ายขาดเป็นกลุ่ม):
- C (Consistency) — ความสอดคล้องระดับ linearizable
- A (Availability) — ทุก request ได้ response (ไม่ timeout/ไม่ error)
- P (Partition tolerance) — ระบบยังทำงานได้แม้เกิด partition
มีครบทั้ง 3 พร้อมกัน "ไม่ได้"
ระบบจริงต้องมี P เสมอ (เพราะ network ล่มได้แน่ ๆ)
→ ตอน partition จึงต้องเลือกระหว่าง C กับ A📌 CAP nuance (สิ่งที่ตำราเรียนไม่ค่อยบอก):
- Brewer's 12-year retrospective (2012) — Brewer เองยอมรับว่า "2 of 3" framing เก่งเกินไป; ในทางปฏิบัติระบบ "บางส่วน" สามารถเลือกระดับ C/A ต่างกันต่อ operation
- Kleppmann critique (2015) — CAP ใช้นิยาม consistency = linearizability และ availability = ทุก non-failing node ตอบ ซึ่งแคบเกินไป เสนอให้ใช้ PACELC + ระบุระดับ consistency ที่ชัดเจน (linearizable / serializable / session / eventual)
- สิ่งที่ต้องจำ: ระบบจริง P เกิดขึ้นเสมอ (network ไม่น่าเชื่อถือ) → "เลือกได้จริง" เหลือแค่ "ตอน partition จะ block (เลือก C) หรือ serve stale (เลือก A)" และ "ตอนปกติจะแลก latency กับ consistency แค่ไหน" (PACELC)
มองภาพ
CP System — ตัวอย่าง etcd / Spanner
text
ตอน network partition:
- ฝั่ง majority (เสียงข้างมาก): ยังรับ request ได้ (มี quorum)
- ฝั่ง minority (เสียงข้างน้อย): ปฏิเสธ (block เพื่อรักษา consistency)
หลัง network heal: ทำงานต่อจากฝั่ง majority (การเขียนของฝั่ง minority หาย)
ใช้กับ: configuration, state สำคัญAP System — ตัวอย่าง Cassandra / DynamoDB
text
ตอน network partition:
- ทั้งสองฝั่งยังรับ request
- แต่ละฝั่งรับการเขียนแยกกันอิสระ
หลัง network heal: ปรับให้ตรงกัน (reconcile) ด้วย LWW, vector clock ฯลฯ
อาจเสียข้อมูลบางส่วนจากการ resolve conflict
ใช้กับ: งานที่ต้องการ availability สูง ยอมรับข้อมูลเก่า/conflict ได้11. PACELC (เพ-เซล-ซี) — ส่วนต่อขยายของ CAP
CAP บอกแค่ตอน partition แต่ความจริงระบบส่วนใหญ่รันในสภาวะปกติ (ไม่ partition) เกือบตลอดเวลา PACELC จึงเติมอีกครึ่ง: ตอนปกติ (Else) ก็ยังต้องเลือกระหว่าง Latency กับ Consistency อยู่ดี — ทำให้อธิบายพฤติกรรม DB จริงได้ครบกว่า CAP เดิม:
📌 กางตัวย่อ PACELC (เสนอโดย Daniel Abadi, 2010):
- P = Partition (เกิด partition หรือไม่)
- A = Availability (ตอน partition เลือกความพร้อมใช้งาน)
- C = Consistency (ตอน partition เลือกความสอดคล้อง)
- E = Else (ตอนปกติ ไม่ partition)
- L = Latency (ตอนปกติเลือก latency ต่ำ)
- C = Consistency อีกครั้ง (ตอนปกติเลือกความสอดคล้อง)
คำอธิบาย latency-vs-consistency ตอนปกติ: แม้ไม่มี partition การ replicate ข้ามหลาย node ก็ต้องใช้เวลา — ระบบที่ "ต้อง" รอ ACK จากหลาย replica ก่อนตอบ (เพื่อ consistency) จะช้ากว่าระบบที่ "ตอบทันที จาก local replica" (ยอม stale เพื่อ latency ต่ำ)
text
ส่วนต่อขยายของ CAP:
ถ้า (P)artition เกิด: ต้องเลือกแลกระหว่าง (A)vailability กับ (C)onsistency
ไม่งั้น (E)lse: ต้องเลือกแลกระหว่าง (L)atency กับ (C)onsistency
ตัวอย่าง:
- PA/EL — Cassandra: ตอน partition เลือก A, ตอนปกติเลือก L (latency ต่ำ)
- PC/EC — Spanner: ตอน partition เลือก C, ตอนปกติก็เลือก C (consistency เสมอ)
- MongoDB — ขึ้นกับ config (ดูหมายเหตุข้างล่าง)
- PC/EL — VoltDB: ตอน partition เลือก C, ตอนปกติเลือก L
→ เลือกตาม use case (latency ก็สำคัญ ไม่ใช่แค่ตอน partition!)⚠️ MongoDB classification = config-dependent (ไม่ใช่ค่าเดียวตายตัว):
คำกำกับสำหรับคนที่ไม่ได้ใช้ MongoDB:
writeConcern= setting บอก MongoDB ว่าต้องการให้ confirm จาก replica กี่ตัวก่อน return success
w:1= primary 1 ตัวพอ (เร็วแต่อาจเสีย data)w:"majority"= ส่วนใหญ่ของ replica (ช้าแต่ปลอดภัย)readConcern= setting บอกว่า read เห็น data ระดับไหน
r:"majority"= เห็นเฉพาะ data ที่ majority confirm แล้วr:"local"(default ของ MongoDB driver ส่วนใหญ่) = เห็น local data ที่อาจ rollback ได้Default ของ MongoDB 5.0+ (กรกฎาคม 2021 เป็นต้นไป):
writeConcern: majority= default ตั้งแต่ MongoDB 5.0 (เดิมก่อนหน้านี้ default คือw:1)readConcern: local= default ของ driver ส่วนใหญ่ (ไม่ใช่ majority)- คำอธิบายเก่าที่บอก "MongoDB default = w:1" ตกรุ่นแล้ว 5 ปี — อ้างอิง MongoDB 5.0 release notes
Classification ที่สอดคล้องกับ glossary:
- MongoDB 5.0+ default (
w:majority,r:local) = PA/EC — writes ทน partition (failover ได้, AP), แต่ตอนปกติแลก latency เพื่อรอ majority ACK (EC)- Legacy pre-5.0 (
w:1) = PA/EL — เร็วทั้ง partition และปกติ แต่เสี่ยงข้อมูลหาย- Recommended for transactions (
w:majority,r:majority) = PC/EC — เข้มสุด ตอน partition primary บางตัวจะ block จนกว่า majority พร้อม→ บทนี้ใช้ PA/EC เป็น MongoDB default (สอดคล้องกับ MongoDB 5.0+ docs และ glossary บท 11)
12. กลยุทธ์ Replication
Single-Leader (Master-Slave) — ผู้นำเดียว
✅ คิดตามง่าย · ✅ consistency แรงที่ leader ❌ leader เป็นจุดเดียวที่พังได้ (SPOF) · ❌ throughput การเขียนจำกัด · ❌ มี replication lag
ใช้กับ: SQL DB ส่วนใหญ่ (ค่า default), งานที่อ่านเยอะ
Multi-Leader (Master-Master) — หลายผู้นำ
✅ เขียนที่ไหนก็ได้ · ✅ latency ดีกว่า (เขียนที่ leader ใกล้สุด) ❌ ต้องมีวิธี resolve conflict · ❌ ซับซ้อน
Leaderless — ไม่มีผู้นำ
text
Client เขียนไปยัง N replicas
Client อ่านจาก N replicas
ใช้ quorum ตัดสิน
ตัวอย่าง: Cassandra, DynamoDB
เขียน: ส่งไปทั้ง N, รอ W ตัว ACK
อ่าน: ส่งไปทั้ง N, รอ R ตัว, คืนค่าที่ใหม่สุด
Quorum: W + R > N
- ถ้า W=2, R=2, N=3 → 2+2 > 3 ✅ ซ้อนทับกัน → เห็นค่าล่าสุด
✅ ไม่มี leader เดียว
✅ availability สูง
❌ ค่า default เป็น eventual consistency (ปรับได้)
❌ ต้องมีวิธี resolve conflict13. Quorum (Voting) — "ใช้เสียงข้างมากตัดสิน"
Quorum เป็นเทคนิคสำคัญที่สุดเทคนิคหนึ่งใน distributed system — เข้าใจตรงนี้แล้ว แทบทุกบทถัดไปจะตามต่อได้
ปัญหา — ทำไมต้องโหวต?
text
มี N replicas ที่เก็บข้อมูลเดียวกัน
- Write: ต้อง update ทุก replica? หรือบางตัวก็พอ?
- Read: ต้องอ่านทุก replica? หรือตัวเดียวก็พอ?
ถ้า update + read ทุกตัว → ช้ามาก, ไม่ทน failure
ถ้า update + read แค่ตัวเดียว → ไม่ consistent→ Quorum = ทางสายกลาง: ใช้แค่ "ส่วนใหญ่" ก็พอ
นิยาม
📌 จำสัญลักษณ์นี้ไว้ — ใช้ตลอดบท:
- N = จำนวน replica ทั้งหมด (เช่น RF=3 → N=3)
- W = Write quorum (จำนวน node ที่ต้อง ACK ตอนเขียน ก่อน return success)
- R = Read quorum (จำนวน node ที่ต้อง response ตอนอ่าน)
- quorum อ่าน "ควอ-รั่ม" = องค์ประชุม / เสียงข้างมากที่ต้องครบ
text
N = จำนวน replicas ทั้งหมด
W = จำนวน replicas ที่ต้อง ACK สำหรับ write
R = จำนวน replicas ที่ต้องอ่านสำหรับ read
Quorum (majority) = N/2 + 1
N=3 → 2
N=5 → 3
N=7 → 4
กฎทอง: ถ้า W + R > N → strong consistency
ถ้า W + R ≤ N → eventual consistencyทำไม W + R > N → consistent? (Pigeon Hole)
นี่คือเหตุผลทางคณิตศาสตร์ที่ทำให้ quorum "ทำงาน":
text
N = 3 replicas: [A] [B] [C]
W = 2 (เขียนถึง 2 ตัว)
R = 2 (อ่านจาก 2 ตัว)
Step 1: Write x=5
→ เขียนถึง 2 ใน 3 ตัว, สมมติได้ [A] กับ [B]
[A]: x=5, [B]: x=5, [C]: x=0 (ยังไม่ได้)
Step 2: Read
→ อ่านจาก 2 ใน 3 ตัว, สมมติได้ [B] กับ [C]
เห็น: [B]: x=5, [C]: x=0
→ ใช้ค่าที่ใหม่กว่า (timestamp / version) = x=5 ✅
ทำไม overlap แน่นอน?
- W = 2 ตัวจาก 3 → เขียนถึง 2 ตัว
- R = 2 ตัวจาก 3 → อ่าน 2 ตัว
- 2 + 2 = 4 > 3 → ต้องมี overlap (ตามหลักลิ้นชัก / pigeon hole)
- ต้องมี "อย่างน้อย 1 ตัว" ที่อยู่ทั้งใน write group และ read group
- ตัวนั้น = มีค่าใหม่แน่นอน
อธิบายง่าย ๆ: ถ้ามี 3 ช่อง แล้วเลือก 2 ช่อง สองครั้ง รวม 4 ครั้ง
→ ต้องมีอย่างน้อย 1 ช่องที่ถูกเลือกซ้ำ (เพราะ 4 > 3)
พิสูจน์ formal: |W ∩ R| ≥ |W| + |R| - N = 2 + 2 - 3 = 1📌 Pigeon Hole Principle (หลักลิ้นชัก / หลักนกพิราบ) = ถ้าใส่นกพิราบ n+1 ตัวลงในช่อง n ช่อง ต้องมีอย่างน้อย 1 ช่องที่มีนก ≥ 2 ตัว — ใช้พิสูจน์ quorum overlap ตรง ๆ
ภาพประกอบ:
Worked Examples
ลองคำนวณตามดู — recap สัญลักษณ์: N = จำนวน replica ทั้งหมด, W = ต้อง ACK กี่ตัวตอนเขียน, R = อ่านกี่ตัว
Case 1: N=3, W=1, R=1
text
W + R = 2 < 3 = N → eventual consistency
Write goes to: 1 ใน 3 replicas
Read goes from: 1 ใน 3 replicas
→ อาจไม่ overlap = อ่านได้ค่าเก่า
ใช้เมื่อ: ต้องการ throughput สูงสุด, latency ต่ำสุด, ยอม stale ได้Case 2: N=3, W=2, R=2 (Cassandra QUORUM — recommended for production)
⚠️ หมายเหตุ: Cassandra default consistency level ใน CQL คือ
ONEไม่ใช่QUORUM—QUORUMคือค่าที่ "แนะนำสำหรับ production" ที่ทีมส่วนใหญ่ตั้งเอง
text
W + R = 4 > 3 = N → strong consistency ✅
Tolerate failures:
- เขียน: 1 ตัวตายได้ (เพราะ W=2 ของ N=3)
- อ่าน: 1 ตัวตายได้ (R=2 ของ N=3)
ใช้เมื่อ: balance ระหว่าง consistency + availability + speedCase 3: N=3, W=3, R=1 (write-heavy)
text
W + R = 4 > 3 = N → strong consistency ✅
แต่:
- เขียน: ห้ามมี node ตาย (W=N) → availability ต่ำ
- อ่าน: เร็ว (R=1) → 1 ตัวตอบก็พอ
ใช้เมื่อ: read >> write, ยอมเขียนช้า/ไม่ available บางครั้งCase 4: N=5, W=3, R=3
text
W + R = 6 > 5 = N → strong consistency ✅
Tolerate failures:
- เขียน: 2 ตัวตายได้ (N-W = 2)
- อ่าน: 2 ตัวตายได้ (N-R = 2)
ใช้เมื่อ: ต้องการ availability สูง (อยู่รอดได้ 2 fail), ยอม latency สูงขึ้นทำไมต้อง majority (N/2 + 1)?
text
ปัญหา: Network partition แบ่ง cluster เป็น 2 ส่วน
[A] [B] | [C]
ส่วนใหญ่ ส่วนเล็ก
ถ้า "ส่วนเล็ก" (minority) ยอมรับ write:
- [C] เขียนข้อมูล x=10
- ส่วนใหญ่ [A,B] เขียนข้อมูล x=20 พร้อมกัน
- พอ partition heal → ค่าขัดกัน → split brain
ถ้าใช้ majority quorum:
- [C] เขียน → ต้องการ majority (2) — แต่มีตัวเดียว → ปฏิเสธ ❌
- [A,B] เขียน → ต้องการ majority (2) — ครบ → สำเร็จ ✅
- จึงมี "ฝั่งเดียว" เท่านั้นที่เขียนได้ = ไม่มี split brain→ Majority = pigeon hole guarantee: ไม่สามารถมี 2 majority groups ที่ไม่ overlap ได้พร้อมกัน
Anti-entropy (แอน-ตี-เอน-โทร-ปี) ใน Cassandra — Hinted Handoff + Read Repair
⚠️ (ขั้นต่อยอด · Cassandra-specific) — basic ที่ใช้ Postgres + Redis เป็นหลัก ข้าม section นี้ได้ ไม่กระทบ; section นี้สำหรับคนที่ดูแล Cassandra/Scylla production
📌 Mini-glossary สำหรับ section นี้:
- Anti-entropy = กลไก sync ข้อมูลของช้าระหว่าง replica เพื่อให้มาบรรจบในที่สุด (ตรงข้ามกับ entropy = ความสับสน/ไม่ตรงกัน)
- Hinted Handoff = ฝาก "hint" ให้ node ที่ตอบไว้ก่อน เมื่อ replica ปลายทางล่ม
- Read Repair = ตอนอ่านพบว่า replica ไม่ตรงกัน → sync ทันที
- Merkle tree = hash tree ใช้เปรียบเทียบ data ระหว่าง replica แบบเร็ว (ดูบท 10)
- Tombstone = marker บอกว่า key ถูก delete (เก็บไว้ชั่วคราวเพื่อ propagate การลบ)
- gc_grace_seconds = config เวลาที่ Cassandra เก็บ tombstone ก่อนล้างจริง (default 10 days)
Quorum protocol รับประกัน consistency เฉพาะระหว่าง read กับ write — แต่ระยะยาวต้องมีกลไก "sync ของช้า" (anti-entropy) ที่ทำให้ replicas ทุกตัวมี data เดียวกันในที่สุด Cassandra ใช้ 3 mechanisms:
1. Hinted Handoff — "ฝาก hint ให้เพื่อน"
text
สถานการณ์:
- Write ที่ N=3 replicas (A, B, C), W=QUORUM=2
- A + B ACK ใน 100ms → return success ถึง client
- C ล่ม / ไม่ตอบ → coordinator (เช่น A) เก็บ "hint" ไว้
Hint = {target: C, mutation: ..., timestamp: ...}
เมื่อ C ฟื้น (gossip detects):
- Coordinator A ส่ง hint replay ไป C
- C apply mutation, sync state เท่า A และ B
Default Cassandra: hint TTL = 3 hours (configurable)⚠️ Hinted Handoff ไม่ใช่ guarantee: ถ้า C ล่มเกิน TTL หรือ coordinator ล่มเองก่อน replay → hint หาย → ต้องพึ่ง read repair หรือ scheduled repair
2. Read Repair — "ซ่อมตอนอ่าน"
text
Client read at R=QUORUM=2:
- Read จาก A, B → A ตอบ x=5 (timestamp t=100), B ตอบ x=3 (t=50)
- Coordinator เห็น mismatch → pick newest (x=5)
- Background: ส่ง update x=5 ให้ B (และ C ถ้าเปิด CL=ALL)
Type:
- Foreground read repair (CL=ALL): ทำเสร็จก่อน return ถึง client
- Background read repair (CL=QUORUM): async หลัง return3. Scheduled Repair — nodetool repair
text
Cassandra ไม่มี anti-entropy แบบ continuous (เหมือน DynamoDB)
→ ต้อง schedule nodetool repair ทุก `gc_grace_seconds` (= Cassandra config ที่ control เวลาเก็บ tombstones ก่อนล้างจริง; default 10 days)
Process:
- Build Merkle tree ของแต่ละ replica
- Compare → identify ranges ที่ต่าง
- Stream diff → sync
ผลกระทบ production:
- ใช้ network + disk I/O เยอะ → ทำกลางคืน
- ใช้ Reaper (Cassandra companion tool) จัดการ schedule💡 Key insight: Quorum (W+R > N) รับประกัน read-after-write consistency ระดับ operation; แต่ระยะยาวต้องมี anti-entropy เพื่อให้ทุก replica converge — มิฉะนั้น replica ที่ล่มนาน + missed hints จะมี data ขาดถาวร
Cassandra/DynamoDB Tunable Consistency
text
ปรับ W และ R per query ได้:
Consistency Level W/R value Use case
───────────────── ──────── ────────
ONE 1 Fast, ยอม stale (analytics)
TWO 2 Slightly safer
THREE 3 Quite safe
QUORUM majority Standard production
ALL N Maximum safety, slow
LOCAL_QUORUM majority of DC ใช้ใน multi-DC (avoid cross-DC latency)
EACH_QUORUM majority of each DC Cross-DC strong consistency→ ผู้ใช้เลือก trade-off ได้เป็นรายการ query — ยืดหยุ่นมาก
14. Replication Lag — ความหน่วงของ replica
เมื่อ master เขียนข้อมูลแล้ว replica (follower) ไม่ได้อัปเดตทันที — ช่องว่างเวลานั้นเรียก "replication lag" และเป็นต้นเหตุของ stale read (ผู้ใช้แก้ข้อมูลแล้วอ่านกลับเห็นค่าเก่า) ส่วนนี้สอนทั้งสาเหตุและวิธีรับมือ (read-your-writes, sync replication, optimistic UI):
text
Master writes → followers eventually update
Time gap = replication lag
Typical:
- Postgres async: 10ms-1s
- Cassandra: < 1s
- Distance: + 100-300ms cross-regionปัญหา — Stale Read (อ่านได้ค่าเก่า)
text
User updates profile → reads back → sees old (lag!)
Solutions:
1. Read your writes:
- Route reads to master for short time after write
2. Synchronous replication:
- Wait for follower ACK before returning
- Slower writes
3. Conflict-free design:
- Accept eventual consistency
- Show "saving..." indicator
- Update UI optimistically15. Synchronous vs Asynchronous Replication — Sync กับ Async
จุดตัดสินใจสำคัญของการ replicate คือจะรอ follower ยืนยันก่อนตอบ success ไหม — sync รอ (ไม่เสียข้อมูลตอน failover แต่ช้า), async ไม่รอ (เร็วแต่เสี่ยงข้อมูลหายถ้า master ตายก่อน replicate), semi-sync รออย่างน้อย 1 ตัวเป็นทางสายกลาง การเลือกขึ้นกับว่า "ยอมเสียข้อมูลได้ไหม":
text
Sync:
Master writes → wait for followers ACK → return success
✅ No data loss on failover
✅ Replicas always current
❌ Slow (waits for slowest)
❌ Unavailable if replicas down
Async:
Master writes → return success immediately
Followers update later
✅ Fast writes
✅ Available even if replicas down
❌ Data loss possible if master dies before replication
Semi-sync:
Wait for at least 1 follower
Compromiseเลือกใช้แบบไหนเมื่อไหร่
text
Sync:
- Banking (no loss tolerance)
- Critical config
- Spanner, CockroachDB
Async:
- Most internet apps
- Postgres default
- Cassandra
- Acceptable: minor data loss on rare failure
Semi-sync:
- MySQL semisync (รออย่างน้อย 1 follower ACK ก่อน return)
Quorum-based (different from semi-sync):
- AWS Aurora — 4/6 storage quorum (writes ACK เมื่อ storage nodes 4 ใน 6 ตัว
ใน 3 AZs confirm log records; reads ใช้ 3/6 quorum)
⚠️ เป็น distributed storage quorum ที่ storage layer ไม่ใช่ semi-sync replication แบบ MySQL16. Read Replicas — ใช้เมื่อไหร่ + อย่างไร
แอปส่วนใหญ่อ่านมากกว่าเขียนมาก — read replica ช่วยกระจายภาระอ่านออกจาก master ทำให้ scale read ได้ แต่ต้องยอมรับ staleness เล็กน้อย ส่วนนี้สอนว่าควรใช้เมื่อไหร่ (และไม่ควรเมื่อไหร่), วิธี route read ไป replica และเรื่อง session stickiness ที่ช่วยให้ผู้ใช้เห็นข้อมูลสม่ำเสมอ:
text
✅ Use when:
- Read >> write (most apps)
- Can tolerate slight staleness
- Want to offload read from master
❌ Don't use when:
- Need consistent read (use master)
- Read amplifies write (sharding better)Routing — กระจาย read ยังไง
text
1. Application-level
- readPool.query(...) → router picks replica
2. Proxy
- PgBouncer (Postgres)
- ProxySQL (MySQL)
- Routes by query type
3. Database-level
- AWS Aurora reader endpoint
- GCP Cloud SQL replicasStickiness — ผูก session กับ replica
text
Stick to replica for session = monotonic reads
- session sees consistent view
- but new writes by other don't appear immediately
Without stickiness = round-robin replicas:
- One read shows new data
- Next read same key → may show old
→ Track session affinity17. Sharding — ทบทวนจาก System Design
เมื่อข้อมูลใหญ่เกินเครื่องเดียวรับไหว เราแบ่งมันออกเป็นชิ้น ๆ (shard) กระจายไปหลายเครื่อง — วิธีแบ่ง (hash/range/geographic) แต่ละแบบมีข้อดีต่างกันและมีปัญหา hot key/hot range ที่ต้องระวัง ข้อควรรู้สำคัญคือ operation ที่ต้องข้าม shard (เช่น join) จะช้า จึงต้องออกแบบให้เลี่ยง:
text
Sharding = horizontal partitioning of data
Hash sharding:
- shard = hash(key) % N
- Uniform distribution
- Hot key issue (1 user = 1 shard)
Range sharding:
- Key 1-1000 → shard 1
- Sequential = on 1 shard (locality)
- Hot range (newer data → newer shard)
Geographic:
- Asia users → Asia shard
- Compliance / data residencyCross-Shard Operations — operation ข้าม shard
text
Join across shards = slow
Solutions:
- Denormalize (duplicate data per shard)
- Application-level join
- Distributed query engine (Vitess, Citus)
- Avoid via design18. เลือก Replication Factor
Replication Factor (RF) คือจำนวนสำเนาของข้อมูลที่เก็บไว้ — ยิ่งมากยิ่งทนต่อการล่ม แต่แลกด้วยพื้นที่และความเร็วเขียน RF=3 เป็นจุดสมดุลที่นิยมที่สุด (ทน 1 node ล่มได้) ส่วน RF=1 ห้ามใช้ใน production เด็ดขาดเพราะ disk เสียทีเดียวข้อมูลหายหมด:
text
Replication Factor (RF) = copies of data
RF=1: 1 copy
- No redundancy
- Lose data on disk failure
- Don't use in production
RF=3 (most common):
- Survives 1 failure (still 2 copies)
- Cost: 3x storage
- Sweet spot for most apps
RF=5+:
- Survives multiple failures
- Required for highest availability
- Used in: critical clusters
Trade-off:
- Higher RF: more redundancy, higher cost, slower writes
- Lower RF: cheaper, faster, less safe19. Write Quorum — รายละเอียด
ในระบบ leaderless (เช่น Cassandra) คุณเลือกได้ว่าต้องให้กี่ node ตอบรับการเขียน (W) และอ่าน (R) — เคล็ดลับสำคัญคือกฎ W+R > N ที่รับประกัน strong consistency เพราะชุดที่เขียนกับชุดที่อ่านต้องซ้อนทับกันอย่างน้อย 1 node ส่วนนี้เจาะรายละเอียดการจูน W/R ให้ได้สมดุลที่ต้องการ:
text
Cassandra with RF=3:
CONSISTENCY ONE (W=1):
- Write goes to 1 node
- Return immediately
- Others updated async
- Fast, may lose data
CONSISTENCY QUORUM (W=2):
- Write goes to 2 of 3
- Slower (waits for 2)
- Tolerates 1 node failure
CONSISTENCY ALL (W=3):
- Write goes to all 3
- Slowest
- 1 node down → unavailable
Read level similarการจับคู่ Read + Write
text
RF=3, W=2, R=2:
- Strong consistency (W+R=4 > N=3, overlap)
- 1 node failure tolerated
RF=3, W=1, R=1:
- No consistency guarantee
- Maximum availability
RF=3, W=3, R=1:
- Fast reads, slow writes
- Use: read-heavy + tolerable slow write20. Conflict Resolution — แก้ conflict
ในระบบ multi-leader หรือ leaderless การเขียนพร้อมกันที่คนละ node ทำให้เกิด conflict อย่างหลีกเลี่ยงไม่ได้ — ต้องมีวิธีตัดสินว่าค่าไหนชนะ ตั้งแต่ง่ายสุด (Last Write Wins — เทียบ timestamp แต่เสี่ยงข้อมูลหาย) ไปจนถึง custom resolver และ CRDT ที่ merge ได้โดยไม่เสียข้อมูล:
text
Multi-leader / leaderless → conflicts inevitable
Strategies:
1. Last Write Wins (LWW):
- Compare timestamps, latest wins
- Simple but lossy
2. Custom resolver:
- App-specific logic
- E.g., union sets, max/min
3. CRDT:
- Data structure that converges
- No conflict possible
4. Operational transformation:
- Transform conflicting ops
- Used in: Google Docs, Figma
5. Manual resolution:
- Show both versions to user
- Used in: Git merge conflictLWW Pitfall — จุดอ่อนของ Last Write Wins
text
Need synchronized clocks (สำหรับ wall-clock LWW เท่านั้น)
- Node 1 clock ahead → its writes always win
- Node 2 writes lost
⚠️ ข้อสังเกต: ปัญหานี้ใช้กับ wall-clock LWW เท่านั้น
ถ้าใช้ HLC-based LWW ปัญหานี้ลดลงมาก (HLC มี logical component คุม clock skew)
Mitigation:
- Use logical clock (ดูบท 4 — Time + Ordering)
- Hybrid logical clock (HLC) — รู้ตอนนี้แค่ว่าเป็น clock แบบใหม่ที่ทน skew ได้
- Spanner (สแปน-เนอร์ — Google's globally-distributed DB) ใช้ TrueTime ที่ hardware-backed clock21. Distributed Locks — ล็อกข้ามเครื่อง
text
"Ensure only 1 node performs critical section"
Use cases:
- Leader election
- Resource reservation
- Avoid duplicate work
Tools:
- Redis Redlock — ⚠️ UNSAFE สำหรับ correctness-critical ถ้าไม่มี fencing token
(Kleppmann 2016 พิสูจน์ด้วยตัวอย่าง GC pause; Antirez ผู้เขียน Redis รับทราบ)
→ ใช้ etcd/ZooKeeper sequence numbers แทน หรือ Redlock + fencing
- ZooKeeper / etcd ephemeral nodes (ปลอดภัย — มี sequence number ในตัว)
- Database row lock (single-DB)
- DynamoDB conditional write⚠️ Kleppmann critique — รายละเอียดสำคัญ:
- Martin Kleppmann (เคล็พ-มัน) — ผู้เขียนหนังสือ DDIA (Designing Data-Intensive Applications) เผยแพร่ paper "How to do distributed locking" (2016) วิจารณ์ Redlock
- ข้อสรุป: Redlock เพียว ๆ (ไม่มี fencing token) ไม่ปลอดภัยสำหรับงาน correctness-critical เช่น financial transactions, distributed leader election
- ทำไม: GC pause / network partition / clock skew ทำให้ holder คิดว่ายังถือ lock อยู่ทั้งที่ lock หมดอายุไปแล้ว
- ทางแก้: ใช้ etcd revision number หรือ ZooKeeper sequence number เป็น fencing token (ดู section ถัดไป) — อย่าใช้ bare Redlock สำหรับ distributed mutual exclusion
Single-Instance Redis Lock — ตัวอย่างพื้นฐาน (ไม่ใช่ Redlock proper)
⚠️ ตัวอย่างนี้คือ single-instance Redis
SET NXlock เท่านั้น ไม่ใช่ Redlock proper:
- Redlock proper ต้องใช้ Redis instance อิสระ N ตัว (มาตรฐาน 5 ตัว) + ขอ lock จากเสียงข้างมาก (majority quorum) ภายใน timeout
- ตัวอย่างด้านล่างใช้ Redis ตัวเดียว → ถ้า Redis ตัวนั้นล่ม lock หาย → ไม่ปลอดภัยสำหรับ correctness-critical
- และไม่ว่าจะเป็น single-instance หรือ Redlock proper — ก็ยังต้องมี fencing token ที่ storage layer เพื่อปลอดภัยจริง
typescript
const lockKey = "lock:resource:123";
const lockValue = uuid();
const ttl = 30; // seconds
// Acquire
const acquired = await redis.set(lockKey, lockValue, "NX", "EX", ttl);
if (acquired) {
try {
// critical section
} finally {
// Release (only if we own)
const script = `
if redis.call('get', KEYS[1]) == ARGV[1]
then return redis.call('del', KEYS[1])
else return 0
end
`;
await redis.eval(script, 1, lockKey, lockValue);
}
}Pitfalls — ทำไม distributed lock ยากกว่าที่คิด
1. TTL too short — lock หมดอายุก่อน critical section เสร็จ
text
Process A:
- Acquire lock (TTL = 30s)
- เริ่ม critical section
- ทำงานหนัก → 35s
- พอ 30s TTL หมด → lock หาย
- Process B acquire lock (สำเร็จ — เพราะหาย)
- A กับ B ทำงาน critical section พร้อมกัน 5 วินาที!2. Process pause (GC, swap) — สาเหตุที่ลึกลับ
ลองตัวอย่าง classic จาก Martin Kleppmann:
text
Time:
0s - Client 1 acquire lock (TTL = 30s, ได้ token=33)
0s - Client 1 เริ่มทำ critical section
2s - Client 1 hit Java GC pause (Stop-The-World)
→ Client 1 หยุดทั้ง process ไป 30 วินาที
30s - Lock ใน Redis หมดอายุ → ถูกลบอัตโนมัติ
31s - Client 2 acquire lock (สำเร็จ — เพราะหาย)
32s - Client 2 ทำ critical section (write to DB)
33s - Client 1 ฟื้นจาก GC → คิดว่ายังถือ lock อยู่
→ Client 1 ก็ write to DB ด้วย
→ ❌ ทั้ง 2 client เขียนพร้อมกัน!📌 GC pause ใน 2026 — modern JVM ลดลงมาก แต่ยังเกิดได้:
- Generational ZGC (JDK 21+) และ Shenandoah ตั้งเป้า pause < 1 ms สำหรับ heap หลาย TB
- แต่ pause ยังเกิดได้เมื่อ: OS swap, container CPU throttling, memory pressure, JIT compilation, safepoint biased toward long methods
- C# / Go ก็มี GC pause เหมือนกัน (Go มี sub-ms pause แต่ภายใต้ load สูงก็ยาวขึ้นได้)
- บทเรียน: แม้ใช้ JDK 25 ที่ ZGC sub-ms pause คุณก็ ยังต้องออกแบบเผื่อ pause — เพราะ OS-level events (swap, CPU throttle) ก็ pause process ได้ทั้ง stack
บทเรียน: GC pause / OS swap / process scheduling / container throttling — ทำให้ process "หยุดเฉยๆ" ได้ทุกเมื่อ Lock ที่อิง TTL อย่างเดียวจึง ไม่ปลอดภัย 100%
3. Network partition — lock holder ไม่รู้ว่าตัวเองตายแล้ว
text
Client 1 ถือ lock + เริ่มทำ critical section
Network partition: Client 1 ขาดจาก Redis
Client 1 ยังคิดว่าถือ lock อยู่ (ไม่รู้ว่าขาดจาก Redis)
Client 1 ทำ critical section ต่อ → เขียน DB
Redis (อีกฝั่งของ partition):
- TTL หมด → ลบ lock
- Client 2 acquire lock สำเร็จ
- Client 2 ก็เขียน DB
→ Both clients write at the same timeทางแก้ — Fencing Token
ทางออกที่ปลอดภัยที่สุด: ใช้ fencing token (เลขเพิ่มขึ้นตามลำดับ)
หลักการ:
text
Lock service (Redis/etcd/ZooKeeper) แจก "token" ที่เพิ่มขึ้นเรื่อยๆ
Client 1 ได้ lock → token = 33
Client 2 ได้ lock (หลังจาก Client 1 timeout) → token = 34
ตอนเขียน data → ต้องส่ง token ไปด้วย
Storage system จดจำ "ล่าสุดเห็น token อะไร"
- เห็น token 33 แล้ว → ถ้า client 1 มาเขียนด้วย token 33 → reject (เพราะ 34 มาแล้ว)
- Client 2 ส่ง token 34 → accept
→ ป้องกัน "old leader" จากการเขียนCode example (Postgres + advisory lock + fencing):
typescript
// Acquire lock + get fencing token (incremented counter)
const result = await db.query(`
UPDATE locks
SET holder = $1, token = token + 1, expires_at = NOW() + INTERVAL '30s'
WHERE resource = 'inventory'
AND (holder IS NULL OR expires_at < NOW())
RETURNING token
`, [clientId]);
if (!result.rows.length) {
throw new Error('Could not acquire lock');
}
const myToken = result.rows[0].token;
// ทำ critical section + ส่ง token ตอน write
await db.query(`
UPDATE inventory
SET stock = stock - 1, last_token = $1
WHERE id = $2
AND last_token < $1 -- ← reject ถ้า token เก่า
`, [myToken, productId]);ZooKeeper / etcd ทำ fencing ด้วย sequence numbers:
text
ZK: /lock/lock-0000000033, /lock/lock-0000000034
(ลำดับใน node path = token)
etcd: revision number ของ key (เพิ่มทุกครั้งที่เปลี่ยน)Lease Pattern — Lock ที่ต่ออายุได้
ทางออกอีกแบบ — ใช้ short lease + heartbeat:
text
Client acquire lock (lease = 10 วินาที)
Client ทำ critical section
→ ทุก 3 วินาที: ส่ง heartbeat (extend lease อีก 10 วินาที)
→ ทำงานนาน ๆ ก็ขยาย lease ไปเรื่อยๆ
Client เสร็จ → release lock
If Client crash / pause:
→ ไม่ส่ง heartbeat
→ 10 วินาทีลีสหมด → lock หลุดอัตโนมัติข้อดี:
- TTL ไม่ต้องตั้งใหญ่ (ไม่ทำให้ recovery ช้าหลัง crash)
- เหมาะกับ work ที่ duration ไม่แน่นอน
ใช้ใน:
- Kubernetes leases (สำหรับ leader election ของ controller)
- etcd lease API
- ZooKeeper session
22. Strong vs Weak — Decision Framework (เลือกอย่างไร)
ทฤษฎีเยอะแล้ว — เอาเข้าจริงจะเลือก strong หรือ eventual consistency ยังไง? ใช้ชุดคำถามนี้ถามตัวเอง: ข้อมูลเก่ากระทบความถูกต้องไหม, ทนเก่าได้นานแค่ไหน, ผู้ใช้จะงงไหม, ข้ามหลาย region หรือเปล่า คำตอบจะชี้ทางให้เองว่าควรลงทุนกับ strong หรือยอมรับ eventual:
text
Q1: Does staleness affect correctness?
Banking: yes (overdraft) → Strong
Social feed: no (5s stale OK) → Eventual
Q2: How long stale tolerable?
Banking: 0 ms
E-commerce browse: 1 min
Analytics: hours
Q3: User confusion if stale?
Profile update: high → Strong (read your writes)
Like count: low → Eventual
Q4: Multi-region operation?
Yes → Eventual most likely
No → Strong feasible
Q5: How much consistency cost?
Banking: must pay (less throughput)
Social: not worth → Eventual23. Database Consistency Spectrum — สเปกตรัมของ DB
ทฤษฎี consistency ฝังอยู่ใน DB จริงที่เราใช้กันยังไง? ส่วนนี้วาง DB ยอดนิยมบนสเปกตรัม — Postgres เดี่ยว (linearizable), Spanner (linearizable ทั่วโลกด้วยนาฬิกาอะตอม), Cassandra/DynamoDB (eventual ปรับได้) ช่วยให้เลือก DB ให้ตรงกับระดับ consistency ที่งานต้องการ:
text
Postgres (single instance):
- Linearizable
- Easy
- Limited scale
Postgres + sync replica:
- Linearizable
- Survives single failure
- Slower
Postgres + async replica:
- Linearizable on master
- Eventual on replicas
Spanner:
- Globally linearizable!
- Special hardware (atomic clocks)
- Industry first
CockroachDB:
- Serializable (close to linearizable)
- Software-based clocks
- Distributed SQL
MongoDB:
- Tunable per query
- Default writeConcern: "majority" (since 5.0, July 2021)
- Default readConcern: "local" (driver default; "majority" is recommended for causal/strong reads)
Cassandra:
- AP เป็น shorthand เฉพาะตอน CL=ONE (default)
- Tunable per query:
- CL=ONE → AP/eventual
- CL=QUORUM (W=2, R=2 ของ RF=3) → ใกล้ strong consistency (read-after-write)
- CL=QUORUM + LWT (Lightweight Transactions) → Paxos-based linearizable per partition (CP profile)
- ⚠️ บอกแค่ "Cassandra = AP" จึงไม่ครบ — profile เปลี่ยนตาม CL
DynamoDB:
- Eventual default
- Strong reads available (more expensive)24. CAP ในทางปฏิบัติ
ในตำราเรียน CAP ดูชัดเจน แต่ในงานจริงมันไม่ได้ "ตัดสินใจ" ให้คุณ — บทเรียนจากสนามจริงคือ: latency สำคัญกว่า partition (PACELC มีประโยชน์กว่า), แอปส่วนใหญ่ต้องการ eventual, และที่สำคัญที่สุดคือต้อง "ทดสอบ" ว่าระบบทำตัวยังไงตอน partition จริง:
text
Real lessons:
1. Mostly CAP doesn't decide for you
- "Choose 2" → too simplistic
- Reality: tunable per operation
2. Latency matters more than partition
- Most "outages" = high latency, not partition
- PACELC more useful than CAP
3. Most apps need eventual
- Strong everywhere = too slow
- Use strong only where needed (banking, inventory)
4. Test failure scenarios
- "How does my app behave during partition?"
- "Does it block? Return stale? Reject?"
- Plan + test25. ตัวอย่างจริง — Shopping Cart
มาดูการตัดสินใจ consistency กับเคสจริงที่คลาสสิกที่สุด — ตะกร้าสินค้า Amazon เลือก eventual consistency เพื่อให้ตะกร้า "ใช้งานได้เสมอ" แม้เกิด partition แล้วแก้ conflict ด้วยการ UNION (รวมของจากทุกฝั่ง ไม่ทิ้ง) ยอมแลกกับ UX เล็กน้อย (ของอาจหายแล้วโผล่กลับ) เพื่อความพร้อมใช้งานสูงสุด:
text
Choice for shopping cart:
Option 1: Strong consistency
- Always see latest cart
- Slow (cross-region sync)
- Block during partition
Option 2: Eventual
- May see old cart briefly
- Fast
- Could "lose" items if conflicting update
Solution (Amazon-inspired):
- Cart in DynamoDB (eventual)
- Merge conflicts by UNION (don't lose items)
- "Did add 2 of A" + "Did add 3 of B" = "2 A + 3 B"
- Final consistency
- Maximum availability
Trade-off:
- May show items briefly disappeared then come back (UX detail)
- Worth: cart always works (no failure)26. Anti-Patterns — สิ่งที่ควรหลีกเลี่ยง
รวมความผิดพลาดเรื่อง consistency ที่เจอบ่อย — ตั้งแต่ใช้ strong ทุกที่ (ฆ่า performance), ใช้ eventual โดยไม่คิดเรื่อง UX, จัดการ replica lag แบบมักง่าย ไปจนถึงเขียน distributed algorithm เอง(เสี่ยงบั๊กลึก) ดูไว้เพื่อจะได้ไม่พลาดซ้ำ:
text
1. "Strong consistency everywhere"
- Performance disaster
- Use where needed
2. "Eventually consistent" without thinking through
- User confusion
- Lost data
- Plan UX for staleness
3. Naive replica lag handling
- User updates → read replica → sees old
- Confusing UX
→ Use sticky session or read from master after write
4. Ignoring CAP
- "Just use any DB"
→ Understand tool's behavior
5. Custom distributed algorithm
- Subtle bugs (CAP impossible)
→ Use proven library/tool
6. No partition test
- "Will work" in normal case
- Untested during partition
→ Chaos engineering27. Cheat Sheet — สรุปด่วน
สรุปทุกอย่างในบทเป็นแผ่นเดียวสำหรับเปิดดูเร็ว ๆ ตอนทำงานจริง — ลำดับความแรง consistency, CAP/PACELC, รูปแบบ replication, สูตร quorum (W+R>N) และ trade-off sync/async เก็บไว้อ้างอิงเวลาตัดสินใจออกแบบ:
text
Consistency strength:
Linearizable > Sequential > Causal > Read-your-writes > Eventual
CAP:
- During partition: choose Consistency OR Availability
- Network always partitions sometime → must design for it
PACELC:
- During partition: A vs C
- Else: L vs C
Replication:
- Single leader (most SQL DB)
- Multi-leader (geo distributed)
- Leaderless (Cassandra, Dynamo)
Quorum:
- W + R > N → strong consistency
- N=3, W=2, R=2 = quorum (common)
Sync vs Async:
- Sync: no data loss but slow
- Async: fast but possible loss
- Semi-sync: 1 replica = compromise28. Checkpoint
ลองวัดความเข้าใจด้วยตัวเอง — แบบฝึกหัดนี้ให้คุณเลือกระดับ consistency ที่เหมาะกับแต่ละสถานการณ์จริง และระบุลักษณะ CAP ของ DB ยอดนิยม ถ้าตอบได้มั่นใจแสดงว่าจับแก่นของบทนี้ได้แล้ว:
🛠️ Checkpoint 1.1 — เลือก Consistency
เลือกระดับ consistency ที่เหมาะกับแต่ละกรณี:
- User login (auth)
- อัปเดตรูปโปรไฟล์
- โอนเงินธนาคาร
- ยอด like ใน Twitter
- ตะกร้าสินค้า e-commerce
- คอมเมนต์ใต้บล็อก
- ราคาหุ้น (real-time)
- อัปเดต DNS record
🛠️ Checkpoint 1.2 — อภิปราย CAP
ระบุลักษณะ CAP ของแต่ละ tool:
- Postgres
- MongoDB
- Cassandra
- DynamoDB
- Redis
- etcd
🛠️ Checkpoint 1.3 — คำนวณ Quorum
คำนวณ:
- N=3, W=1, R=1 → consistency? availability?
- N=3, W=3, R=1 → consistency? latency การอ่าน?
- N=5, W=3, R=3 → ?
- N=5, W=2, R=2 → ?
🛠️ Checkpoint 1.4 — Replica Lag
ออกแบบ:
- แอปที่ "เสมอ" ให้ผู้ใช้เห็นการเขียนของตัวเอง (read-your-writes)
- แอปที่อยู่รอดได้แม้ replica ตาย 1 ตัว
🛠️ Checkpoint 1.5 — อ่าน DDIA บทที่ 5
"Replication" — อ่าน + เขียนสรุป
29. สรุปบท
ทบทวนแก่นของบทนี้ทั้งหมด — ตั้งแต่ระดับความแรงของ consistency, ทฤษฎี CAP/PACELC, สูตร quorum, รูปแบบ replication ไปจนถึงการรับมือ replica lag เช็กลิสต์นี้คือสิ่งที่ควรเข้าใจก่อนไปบทถัดไปเรื่อง consensus:
✅ Consistency strength: Linearizable > Causal > Eventual
✅ CAP: pick 2 in presence of partition (real: P + (A or C))
✅ PACELC: latency also matters (not just partition)
✅ Quorum: W + R > N for strong consistency
✅ Replication: single-leader (SQL), multi-leader (geo), leaderless (Cassandra)
✅ Sync vs Async: data safety vs speed
✅ Replica lag: handle stale read with sticky / read-your-writes
✅ Conflict resolution: LWW (ง่าย), CRDT (ฉลาด), manual (เหมือน Git merge)
✅ ใช้ strong เฉพาะที่จำเป็น — ส่วนใหญ่ eventual ก็พอ
✅ DB แต่ละตัวเลือก trade-off ต่างกัน — เข้าใจก่อนเลือกใช้
✅ อ่าน DDIA บทที่ 5 + 9