Skip to content

บทที่ 1 — Consistency + CAP

← บทที่ 0 | สารบัญ | บทที่ 2 →

ก่อนอ่าน — ต้องรู้อะไรมาก่อน

  • พื้นฐาน 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 ข้อให้อ่านง่าย):

  1. ทุก operation "ดูเหมือน" เกิดขึ้นที่จุดเวลาเดียว (instantaneous — ไม่ใช่ช่วงเวลา)
  2. จุดเวลานั้นอยู่ระหว่างเวลาที่ operation เริ่มกับเวลาที่ operation จบ
  3. ทุก 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

PropertyLinearizableSequentialEventual
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 synclinearizable แต่ assume bounded clock skew (~500 ms)
YugabyteDB (ยู-กา-ไบต์-ดีบี)HLC + NTP syncPostgres-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

ใช้ใน:

  • MongoDBcausalConsistency + clusterTime
  • Azure Cosmos DBx-ms-session-token (session consistency)
  • DynamoDBConsistentRead: 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 (กันขายเกินสต๊อก)
❌ การอัปเดต authentication

CRDT (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-CounterCounter เพิ่มอย่างเดียวLike, page view, total sales
PN-CounterCounter เพิ่ม + ลดActive user (online/offline), inventory level
G-SetSet ที่ add อย่างเดียวFollowers list, tag list
OR-SetSet ที่ add + removeCart items, collaborative todo
LWW-RegisterSingle value (timestamp wins)User profile field, config
RGA / Yjs / AutomergeSequence (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:

text
Doctor 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 ข้างบน); จะเจาะในบท 3Postgres 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 ค้างมาก ๆ

เปรียบเทียบครบ — ใช้อะไรเมื่อไหร่?

ModelUse caseLatencyAvailability
LinearizableBanking, lock, leader electionสูงมากต่ำ (CP)
SequentialReplicated state machineสูงกลาง
Snapshot IsolationOLTP DB transactions (Postgres)กลางกลาง
Bounded StalenessRead replicas, analytics ทันเวลาประมาณกลางสูง
CausalComments, chat, collaborativeต่ำสูง
Read-Your-WritesProfile updates, user-facingต่ำสูง
EventualLikes, 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 conflict

13. 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 ได้

⚠️ หมายเหตุ: Cassandra default consistency level ใน CQL คือ ONE ไม่ใช่ QUORUMQUORUM คือค่าที่ "แนะนำสำหรับ production" ที่ทีมส่วนใหญ่ตั้งเอง

text
W + R = 4 > 3 = N → strong consistency ✅

Tolerate failures:
- เขียน: 1 ตัวตายได้ (เพราะ W=2 ของ N=3)
- อ่าน: 1 ตัวตายได้ (R=2 ของ N=3)

ใช้เมื่อ: balance ระหว่าง consistency + availability + speed

Case 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 หลัง return

3. 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 optimistically

15. 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 แบบ MySQL

16. 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 replicas

Stickiness — ผูก 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 affinity

17. 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 residency

Cross-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 design

18. เลือก 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 safe

19. 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 write

20. 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 conflict

LWW 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 clock

21. 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 NX lock เท่านั้น ไม่ใช่ 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 → Eventual

23. 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 + test

25. ตัวอย่างจริง — 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 engineering

27. 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 = compromise

28. 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


← บทที่ 0 | บทที่ 2 → Consensus + Replication