Distributed Systems หรือระบบกระจาย เป็นหัวใจของโครงสร้างพื้นฐานทางเทคโนโลยีในปัจจุบัน ตั้งแต่ Google Search, Netflix Streaming, Amazon Shopping ไปจนถึง LINE Messaging ทุกบริการที่คุณใช้ทุกวันล้วนเป็น Distributed System ทั้งสิ้น
บทความนี้จะอธิบายพื้นฐานของ Distributed Systems ตั้งแต่แนวคิด ทฤษฎีสำคัญอย่าง CAP Theorem, Consensus Algorithms (Paxos, Raft), Replication, Sharding ไปจนถึง Distributed Transactions และการวิเคราะห์ระบบจริงอย่าง Cassandra, DynamoDB และ CockroachDB เหมาะสำหรับนักพัฒนาที่ต้องการเข้าใจ System Design อย่างลึกซึ้ง
Distributed Systems คืออะไร?
Distributed System คือระบบที่ประกอบด้วยคอมพิวเตอร์หลายเครื่อง (Nodes) ที่ทำงานร่วมกันผ่าน Network เพื่อให้บริการราวกับเป็นระบบเดียว โดยผู้ใช้ไม่จำเป็นต้องรู้ว่ามีเครื่องหลายเครื่องอยู่เบื้องหลัง
Leslie Lamport นักวิทยาศาสตร์คอมพิวเตอร์ผู้ได้รับรางวัล Turing Award เคยให้คำจำกัดความว่า: "Distributed System คือระบบที่เครื่องคอมพิวเตอร์ที่คุณไม่เคยรู้ว่ามีอยู่ สามารถทำให้ระบบของคุณพังได้"
ทำไมต้องใช้ Distributed Systems?
- Scalability (ขยายได้) — เครื่องเดียวมีขีดจำกัด (CPU, RAM, Disk) การเพิ่มเครื่องช่วยให้รองรับ Load ที่มากขึ้นได้ (Horizontal Scaling)
- Availability (พร้อมใช้งาน) — ถ้าเครื่องเดียวพัง ระบบทั้งหมดพัง การมีหลายเครื่องทำให้ยังทำงานได้แม้บาง Node จะล่ม
- Fault Tolerance (ทนต่อความล้มเหลว) — ข้อมูลถูก Replicate ไปหลายที่ แม้ Disk เสียหรือ Data Center ไฟดับ ข้อมูลไม่หาย
- Geographic Distribution (กระจายภูมิศาสตร์) — วาง Server ใกล้ผู้ใช้เพื่อลด Latency ผู้ใช้ในไทยเข้า Server ที่สิงคโปร์ ไม่ต้องไปถึง US
- Performance (ประสิทธิภาพ) — กระจายงานไปหลายเครื่องทำงาน Parallel ได้เร็วกว่าเครื่องเดียวมาก
ความท้าทายหลักของ Distributed Systems
แม้จะมีข้อดีมากมาย แต่ Distributed Systems มาพร้อมกับความซับซ้อนที่ไม่มีในระบบเดี่ยว ได้แก่:
- Network ไม่น่าเชื่อถือ — Packet อาจหาย, ล่าช้า, มาซ้ำ หรือมาผิดลำดับ
- เวลาไม่ตรงกัน — นาฬิกาแต่ละเครื่องอาจต่างกันเล็กน้อย ทำให้ลำดับ Event ไม่ชัดเจน
- Partial Failure — บาง Node อาจล่มขณะที่อื่นยังทำงานอยู่ ไม่เหมือนระบบเดี่ยวที่ "ทำงาน" หรือ "ไม่ทำงาน"
- Consistency กับ Availability ต้องเลือก — ไม่สามารถมีทั้งสองอย่างสมบูรณ์ในเวลาที่ Network แบ่งแยก (CAP Theorem)
CAP Theorem — ทฤษฎีพื้นฐานที่ต้องรู้
CAP Theorem ถูกเสนอโดย Eric Brewer ในปี 2000 และพิสูจน์โดย Seth Gilbert และ Nancy Lynch ในปี 2002 กล่าวว่า ระบบ Distributed ไม่สามารถมีคุณสมบัติทั้ง 3 อย่างพร้อมกันได้ในกรณีที่เกิด Network Partition
C - Consistency (ความสอดคล้อง)
ทุก Node จะเห็นข้อมูลเดียวกันในเวลาเดียวกัน เมื่อเขียนข้อมูลสำเร็จ การอ่านจาก Node ไหนก็ตามจะได้ค่าล่าสุดเสมอ ตัวอย่างเช่น ถ้าโอนเงิน 1,000 บาท ยอดเงินที่อ่านจากทุก Node ต้องตรงกัน
A - Availability (ความพร้อมใช้งาน)
ทุก Request จะได้รับ Response (ไม่ Error) ภายในเวลาที่เหมาะสม แม้ว่าบาง Node จะล่มไปก็ตาม ระบบต้องยังให้บริการได้
P - Partition Tolerance (ทนต่อการแบ่งแยก Network)
ระบบยังทำงานได้แม้ว่า Network ระหว่าง Node จะขาดหายไป ซึ่งในระบบ Distributed จริงๆ Network Partition เป็นสิ่งที่เกิดขึ้นได้เสมอ ดังนั้น P เป็นสิ่งที่ต้องมี
การเลือก Trade-off
| ตัวเลือก | ยอมเสีย | ตัวอย่างระบบ | Use Case |
|---|---|---|---|
| CP (Consistency + Partition Tolerance) | Availability | ZooKeeper, etcd, HBase, MongoDB (default) | ระบบการเงิน, Configuration Store |
| AP (Availability + Partition Tolerance) | Consistency | Cassandra, DynamoDB, CouchDB, Riak | Social Media Feed, Shopping Cart |
| CA (Consistency + Availability) | Partition Tolerance | Traditional RDBMS (single node) | ระบบที่ไม่ต้อง Distribute |
PACELC Theorem — ขยายจาก CAP
PACELC ขยายจาก CAP โดยเพิ่มเงื่อนไขว่า: ถ้าเกิด Partition (P) ต้องเลือกระหว่าง Availability กับ Consistency (AC) แต่ แม้ไม่มี Partition (E = Else) ก็ยังต้องเลือกระหว่าง Latency กับ Consistency (LC)
| ระบบ | เมื่อเกิด Partition | เมื่อปกติ (Else) |
|---|---|---|
| DynamoDB | เลือก Availability (PA) | เลือก Latency (EL) |
| Cassandra | เลือก Availability (PA) | เลือก Latency (EL) |
| MongoDB | เลือก Consistency (PC) | เลือก Latency (EL) |
| CockroachDB | เลือก Consistency (PC) | เลือก Consistency (EC) |
| Spanner | เลือก Consistency (PC) | เลือก Consistency (EC) |
Consistency Models — ระดับความสอดคล้องของข้อมูล
ไม่ใช่ทุก Use Case ต้องการ Strong Consistency มี Consistency Model หลายระดับให้เลือกตามความต้องการ โดยแต่ละระดับมี Trade-off ระหว่าง Consistency กับ Performance ที่แตกต่างกัน
Strong Consistency (Linearizability)
เข้มงวดที่สุด ทุกการอ่านจะเห็นค่าจากการเขียนล่าสุดเสมอ เสมือนมีเครื่องเดียว ข้อเสียคือช้า เพราะต้องรอ Consensus จากหลาย Node ก่อนตอบ ตัวอย่าง: Spanner, CockroachDB
Eventual Consistency
อ่อนที่สุด รับประกันว่า "สุดท้ายแล้ว" ทุก Node จะเห็นค่าเดียวกัน แต่ไม่รับประกันว่าจะเห็นเมื่อไหร่ อาจใช้เวลาเป็นวินาทีหรือนาที ข้อดีคือเร็วมากเพราะไม่ต้องรอ Sync ตัวอย่าง: DynamoDB (default), DNS, Cassandra (ONE)
Causal Consistency
รับประกันว่า Operation ที่มีความสัมพันธ์เชิงเหตุผล (Causally Related) จะเห็นในลำดับที่ถูกต้อง เช่น ถ้า A โพสต์ข้อความแล้ว B ตอบ ทุกคนจะเห็นโพสต์ของ A ก่อนคำตอบของ B เสมอ ตัวอย่าง: MongoDB (causal read concern)
Read-Your-Writes Consistency
ผู้ใช้จะเห็นข้อมูลที่ตัวเองเพิ่งเขียนไปเสมอ เช่น เมื่อคุณอัปเดต Profile แล้ว Refresh หน้า คุณจะเห็นข้อมูลที่เพิ่งแก้ ส่วนคนอื่นอาจยังเห็นข้อมูลเก่าสักพัก ตัวอย่าง: ใช้ Sticky Session หรืออ่านจาก Leader Node
# Cassandra — ตัวอย่าง Tunable Consistency
# เขียนแบบ Quorum (majority ต้อง Acknowledge)
INSERT INTO orders (id, customer, total)
VALUES (uuid(), 'customer-1', 1500)
USING CONSISTENCY QUORUM;
# อ่านแบบ ONE (เร็ว แต่อาจได้ข้อมูลเก่า)
SELECT * FROM orders WHERE id = ?
USING CONSISTENCY ONE;
# อ่านแบบ ALL (ช้า แต่ได้ข้อมูลล่าสุดแน่นอน)
SELECT * FROM orders WHERE id = ?
USING CONSISTENCY ALL;
# สูตร Strong Consistency ใน Cassandra:
# W + R > N
# W = Write Consistency Level
# R = Read Consistency Level
# N = Replication Factor
# เช่น N=3, W=QUORUM(2), R=QUORUM(2) -> 2+2 > 3 = Strong
Consensus Algorithms — การตกลงร่วมกัน
ปัญหาพื้นฐานของ Distributed Systems คือ: ทำอย่างไรให้ Node หลายตัวตกลงกันได้ว่าค่าของข้อมูลคืออะไร ใครเป็น Leader หรือ Transaction สำเร็จหรือไม่ นี่คือปัญหา Consensus
Paxos
Paxos ถูกคิดค้นโดย Leslie Lamport ในปี 1989 เป็น Consensus Algorithm ตัวแรกที่พิสูจน์ได้ทางคณิตศาสตร์ว่าถูกต้อง แต่มีชื่อเสียงว่ายากต่อการเข้าใจและ Implement
Paxos ทำงานเป็น 2 Phase: Phase 1 (Prepare) ผู้เสนอ (Proposer) ส่ง Prepare Request พร้อม Proposal Number ไปยังผู้ยอมรับ (Acceptor) เสียงส่วนใหญ่ (Majority) ถ้า Acceptor ยังไม่เคยรับ Proposal ที่หมายเลขสูงกว่า จะตอบ Promise กลับมา Phase 2 (Accept) ถ้าได้ Promise จาก Majority แล้ว Proposer จะส่ง Accept Request พร้อมค่าที่ต้องการ เมื่อ Majority ตอบ Accepted ค่านั้นจะถูก "เลือก" (Chosen)
Paxos ถูกใช้ใน Google Chubby, Google Spanner และ Microsoft Azure Cosmos DB
Raft
Raft ถูกออกแบบโดย Diego Ongaro และ John Ousterhout ในปี 2014 โดยมีเป้าหมายให้เข้าใจง่ายกว่า Paxos แต่มีคุณสมบัติเทียบเท่า Raft แบ่ง Consensus ออกเป็น 3 ปัญหาย่อย:
1. Leader Election: Node ทั้งหมดเริ่มเป็น Follower ถ้า Follower ไม่ได้รับ Heartbeat จาก Leader ภายใน Election Timeout จะเปลี่ยนเป็น Candidate แล้วขอ Vote จาก Node อื่น ถ้าได้เสียง Majority จะกลายเป็น Leader ใหม่
2. Log Replication: Leader รับ Request จาก Client แล้วเขียน Log Entry จากนั้น Replicate ไปยัง Follower ทุกตัว เมื่อ Majority ยืนยันว่าเขียน Log แล้ว Entry นั้นจะถูก Commit
3. Safety: รับประกันว่า Log ที่ Commit แล้วจะไม่ถูกเขียนทับ และ Leader ใหม่จะมี Log ที่ครบถ้วน
# Raft ใน etcd (Kubernetes ใช้เป็น Data Store)
# ดูสถานะ Leader
etcdctl endpoint status --write-out=table
# ผลลัพธ์:
# +---------------------+------------------+---------+---------+
# | ENDPOINT | ID | VERSION | IS LEADER |
# +---------------------+------------------+---------+---------+
# | etcd1:2379 | 8e9e05c52164694d | 3.5.12 | true |
# | etcd2:2379 | 91bc3c398fb3c146 | 3.5.12 | false |
# | etcd3:2379 | fd422379fda50e48 | 3.5.12 | false |
# +---------------------+------------------+---------+---------+
# ดู Raft Term
etcdctl endpoint status --write-out=json | python3 -m json.tool
Distributed Data Storage
Replication — สำเนาข้อมูลหลายที่
การ Replicate ข้อมูลไปหลาย Node มี 3 รูปแบบหลัก:
Single-Leader Replication: มี Leader เดียวที่รับ Write ทั้งหมด แล้ว Replicate ไปยัง Follower ข้อดีคือง่ายและ Consistent ข้อเสียคือ Leader เป็น Bottleneck สำหรับ Write ตัวอย่าง: MySQL Replication, PostgreSQL Streaming Replication, MongoDB Replica Set
Multi-Leader Replication: มี Leader หลายตัวที่รับ Write ได้ทั้งหมด เหมาะสำหรับ Multi-datacenter Deployment ข้อเสียคือต้องจัดการ Write Conflict เมื่อ Leader 2 ตัวแก้ไขข้อมูลเดียวกัน ตัวอย่าง: CouchDB, Active-Active PostgreSQL (BDR)
Leaderless Replication: ไม่มี Leader ทุก Node รับ Read/Write ได้เท่ากัน Client ส่งข้อมูลไปหลาย Node พร้อมกัน ใช้ Quorum (W + R > N) เพื่อรับประกัน Consistency ตัวอย่าง: Cassandra, DynamoDB, Riak
Sharding (Partitioning) — แบ่งข้อมูลกระจาย
เมื่อข้อมูลมีขนาดใหญ่เกินกว่าเครื่องเดียวจะเก็บได้ ต้องแบ่งข้อมูลกระจายไปหลาย Node มี 2 วิธีหลัก:
Range-based Sharding: แบ่งตามช่วงของ Key เช่น A-M ไป Shard 1, N-Z ไป Shard 2 ข้อดีคือ Range Query ทำได้ง่าย ข้อเสียคืออาจเกิด Hotspot (บาง Range มีข้อมูลมากกว่า)
Hash-based Sharding: Hash ค่า Key แล้วใช้ผลลัพธ์กำหนดว่าไป Shard ไหน กระจายข้อมูลได้สม่ำเสมอ แต่ Range Query ทำไม่ได้เพราะข้อมูลที่ Key ใกล้กันถูกกระจายไปคนละ Shard
Consistent Hashing
Consistent Hashing แก้ปัญหาของ Hash-based Sharding ปกติเมื่อเพิ่ม/ลด Node ต้อง Re-hash ข้อมูลทั้งหมด Consistent Hashing จัดเรียง Node บน Hash Ring เมื่อเพิ่ม/ลด Node จะย้ายข้อมูลเฉพาะส่วนที่ได้รับผลกระทบเท่านั้น (ประมาณ 1/N ของทั้งหมด)
# Python — Consistent Hashing (Simplified)
import hashlib
class ConsistentHashRing:
def __init__(self, nodes=None, virtual_nodes=150):
self.ring = {}
self.sorted_keys = []
self.virtual_nodes = virtual_nodes
if nodes:
for node in nodes:
self.add_node(node)
def _hash(self, key):
return int(hashlib.md5(key.encode()).hexdigest(), 16)
def add_node(self, node):
for i in range(self.virtual_nodes):
virtual_key = f"{node}:{i}"
hash_val = self._hash(virtual_key)
self.ring[hash_val] = node
self.sorted_keys.append(hash_val)
self.sorted_keys.sort()
def remove_node(self, node):
for i in range(self.virtual_nodes):
virtual_key = f"{node}:{i}"
hash_val = self._hash(virtual_key)
del self.ring[hash_val]
self.sorted_keys.remove(hash_val)
def get_node(self, key):
if not self.ring:
return None
hash_val = self._hash(key)
# หา Node แรกที่ hash >= key hash
for ring_key in self.sorted_keys:
if ring_key >= hash_val:
return self.ring[ring_key]
return self.ring[self.sorted_keys[0]]
# ใช้งาน
ring = ConsistentHashRing(["node-1", "node-2", "node-3"])
print(ring.get_node("user:12345")) # -> node-2
print(ring.get_node("order:67890")) # -> node-1
# เพิ่ม Node — ย้ายข้อมูลแค่บางส่วน
ring.add_node("node-4")
Leader Election
ในระบบ Single-Leader ต้องมีกลไกเลือก Leader ใหม่เมื่อ Leader เก่าล่ม มีหลายวิธี:
- Raft-based: ใช้ Raft Protocol ที่กล่าวมาข้างต้น ใช้ใน etcd, Consul
- ZooKeeper-based: สร้าง Ephemeral Node ใน ZooKeeper Node แรกที่สร้างสำเร็จเป็น Leader ใช้ใน Kafka (ZK mode), HBase
- Bully Algorithm: Node ที่มี ID สูงสุดเป็น Leader เรียบง่ายแต่ไม่ค่อยใช้ใน Production
Distributed Locks
เมื่อหลาย Node ต้องเข้าถึง Resource เดียวกัน ต้องมี Lock เพื่อป้องกัน Race Condition
Redis Redlock
# Python — Distributed Lock ด้วย Redis
import redis
import time
import uuid
class DistributedLock:
def __init__(self, redis_clients, resource, ttl=10000):
self.redis_clients = redis_clients # Redis instances 5 ตัว
self.resource = resource
self.ttl = ttl
self.lock_value = str(uuid.uuid4())
def acquire(self):
n = len(self.redis_clients)
quorum = n // 2 + 1
start = time.monotonic()
acquired = 0
for client in self.redis_clients:
try:
if client.set(self.resource, self.lock_value,
px=self.ttl, nx=True):
acquired += 1
except redis.RedisError:
pass
elapsed = (time.monotonic() - start) * 1000
# Lock สำเร็จถ้า: ได้ Majority + ยังไม่หมดเวลา
if acquired >= quorum and elapsed < self.ttl:
return True
# ถ้าไม่สำเร็จ ปล่อย Lock ทั้งหมด
self.release()
return False
def release(self):
# ใช้ Lua script เพื่อ Atomic check-and-delete
lua_script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
for client in self.redis_clients:
try:
client.eval(lua_script, 1, self.resource, self.lock_value)
except redis.RedisError:
pass
Distributed Transactions
Two-Phase Commit (2PC)
2PC เป็น Protocol สำหรับทำ Transaction ข้ามหลาย Node โดยมี Coordinator ควบคุม:
Phase 1 (Prepare): Coordinator ส่ง "Prepare" ไปยังทุก Participant แต่ละ Participant เขียนข้อมูลลง Log และตอบ "Yes" (พร้อม Commit) หรือ "No" (ไม่พร้อม)
Phase 2 (Commit/Abort): ถ้าทุก Participant ตอบ "Yes" — Coordinator ส่ง "Commit" ทุกคน Commit ถ้ามีใครตอบ "No" — Coordinator ส่ง "Abort" ทุกคน Rollback
ข้อเสียของ 2PC คือเป็น Blocking Protocol ถ้า Coordinator ล่มหลัง Phase 1 Participant จะ "ค้าง" ไม่รู้ว่าจะ Commit หรือ Abort ต้องรอ Coordinator กลับมา
Three-Phase Commit (3PC)
3PC เพิ่ม Phase กลาง (Pre-commit) เพื่อลดปัญหา Blocking ของ 2PC แต่ในทางปฏิบัติไม่ค่อยถูกใช้เพราะยังมีปัญหาในกรณี Network Partition
Saga Pattern
Saga เป็นทางเลือกของ 2PC สำหรับ Microservices แทนที่จะ Lock ทุก Service พร้อมกัน Saga แบ่ง Transaction ใหญ่เป็น Local Transaction ย่อยๆ ถ้า Step ไหนล้มเหลว จะ Run Compensating Transaction เพื่อ Undo Step ก่อนหน้า
# ตัวอย่าง Saga: E-commerce Order
# Happy Path:
# 1. Create Order (Order Service)
# 2. Reserve Inventory (Inventory Service)
# 3. Process Payment (Payment Service)
# 4. Ship Order (Shipping Service)
# Failure at Step 3 (Payment Failed):
# 3. Payment Failed -> Trigger Compensations
# 2c. Release Inventory (Compensating Transaction)
# 1c. Cancel Order (Compensating Transaction)
# Orchestration vs Choreography:
# Orchestration: มี Saga Coordinator สั่งแต่ละ Step (ง่ายกว่า, debug ง่าย)
# Choreography: แต่ละ Service ฟัง Event แล้วทำ Step ถัดไปเอง (loosely coupled)
Vector Clocks และ Logical Time
ในระบบ Distributed เราไม่สามารถใช้นาฬิกาจริง (Wall Clock) เพื่อกำหนดลำดับ Event ได้อย่างน่าเชื่อถือ เพราะนาฬิกาแต่ละเครื่องอาจต่างกัน จึงต้องใช้ Logical Clock แทน
Lamport Timestamps
Lamport Timestamp เป็น Counter ที่เพิ่มขึ้นทุกครั้งที่มี Event และเมื่อรับ Message ที่มี Timestamp สูงกว่า จะอัปเดตเป็นค่าที่สูงกว่า + 1 ช่วยให้กำหนด "ลำดับบางส่วน" (Partial Order) ได้ แต่ไม่สามารถบอกได้ว่า Event สอง Event เกิดขึ้นพร้อมกัน (Concurrent) หรือไม่
Vector Clocks
Vector Clock แก้ปัญหาของ Lamport Timestamp โดยเก็บ Counter แยกสำหรับแต่ละ Node ทำให้สามารถตรวจจับ Concurrent Events ได้ ซึ่งจำเป็นสำหรับ Conflict Detection ใน Multi-Leader และ Leaderless Replication
# Vector Clock Example
# Node A: [A:1, B:0, C:0] -> เขียนค่า X=1
# Node B: [A:1, B:1, C:0] -> รับจาก A, แก้เป็น X=2
# Node C: [A:1, B:0, C:1] -> รับจาก A, แก้เป็น X=3
# เปรียบเทียบ:
# [A:1, B:1, C:0] vs [A:1, B:0, C:1]
# B มากกว่าใน Vector แรก, C มากกว่าใน Vector สอง
# => CONCURRENT! ต้อง Resolve Conflict
# ถ้า [A:2, B:1, C:0] vs [A:1, B:1, C:0]
# Vector แรกมากกว่าหรือเท่าในทุก Component
# => Vector แรก "happens after" Vector สอง
Gossip Protocol
Gossip Protocol (หรือ Epidemic Protocol) เป็นวิธีกระจายข้อมูลในระบบ Distributed โดยเลียนแบบการแพร่ของโรคระบาดหรือข่าวลือ แต่ละ Node จะสุ่มเลือก Node อื่นมาแลกเปลี่ยนข้อมูลเป็นระยะ
ข้อดีคือ Decentralized (ไม่มี Single Point of Failure), Scalable (ใช้ได้กับ Cluster หลายพัน Node) และ Fault-tolerant ข้อเสียคือ Eventual (ใช้เวลาสักพักกว่าข้อมูลจะถึงทุก Node)
ใช้ใน: Cassandra (Node Membership, Failure Detection), Consul (Serf Gossip), Redis Cluster, DynamoDB
Failure Detection
Heartbeat
วิธีพื้นฐานที่สุด: แต่ละ Node ส่ง Heartbeat ไปยัง Monitor เป็นระยะ ถ้าไม่ได้รับ Heartbeat ภายใน Timeout ถือว่า Node นั้นล่ม ข้อเสียคือ False Positive สูงเมื่อ Network ช้าหรือ Node ยุ่ง
Phi Accrual Failure Detector
ใช้ใน Cassandra และ Akka Cluster ทำงานโดยคำนวณ Phi Value จากประวัติ Heartbeat Interval เมื่อ Phi สูงกว่า Threshold (เช่น 8) จึงประกาศว่า Node ล่ม ให้ผลลัพธ์แม่นยำกว่า Heartbeat แบบ Timeout ตายตัว
Split Brain Problem
Split Brain เกิดขึ้นเมื่อ Network Partition ทำให้ Cluster แบ่งเป็น 2 กลุ่มที่ไม่เห็นกัน แต่ละกลุ่มอาจเลือก Leader ของตัวเอง ทำให้เกิด 2 Leader ที่รับ Write ต่างกัน เมื่อ Network กลับมาจะเกิด Conflict
วิธีป้องกัน
- Quorum: ต้องได้เสียง Majority (N/2 + 1) จึงจะเป็น Leader ฝั่งที่มี Node น้อยกว่าจะไม่สามารถเลือก Leader ได้
- Fencing Token: ทุก Lock/Leader มี Token ที่เพิ่มขึ้น Node ที่มี Token เก่าจะถูกปฏิเสธ
- STONITH (Shoot The Other Node In The Head): ใช้ใน HA Cluster โดย Force Shutdown Node ที่ไม่ตอบสนอง
Service Discovery
ในระบบ Distributed ที่ Service มีหลาย Instance และ IP เปลี่ยนบ่อย (โดยเฉพาะใน Kubernetes) จำเป็นต้องมี Service Discovery เพื่อให้ Service หากันเจอ
- Client-side Discovery: Client Query Service Registry โดยตรง (เช่น Netflix Eureka)
- Server-side Discovery: Client ส่งไปที่ Load Balancer, Load Balancer ค้นหาจาก Registry (เช่น AWS ALB + Cloud Map)
- DNS-based: ใช้ DNS Record ชี้ไป Service (เช่น Consul DNS, Kubernetes CoreDNS)
Distributed Caching
Cache เป็นสิ่งจำเป็นในระบบ Distributed เพื่อลด Latency และ Load บน Database มี Strategy หลัก:
- Cache-Aside: Application ตรวจ Cache ก่อน ถ้าไม่มีจึงอ่านจาก DB แล้วเขียนลง Cache
- Write-Through: เขียนทั้ง Cache และ DB พร้อมกัน
- Write-Behind: เขียน Cache ก่อน แล้ว Async เขียนลง DB ทีหลัง (เร็วแต่เสี่ยงข้อมูลหาย)
วิเคราะห์ระบบจริง
Apache Cassandra
Cassandra เป็น Wide-column Database แบบ Leaderless ออกแบบโดย Facebook ใช้ Consistent Hashing สำหรับ Partitioning, Gossip Protocol สำหรับ Cluster Membership, Vector Clocks สำหรับ Conflict Detection (ปัจจุบันใช้ Last-Write-Wins) และ Tunable Consistency Level เหมาะสำหรับ Write-heavy Workload ที่ต้องการ High Availability
Amazon DynamoDB
DynamoDB เป็น Managed NoSQL ของ AWS ใช้ Consistent Hashing + Virtual Nodes สำหรับ Partitioning, Sloppy Quorum สำหรับ Write Availability, Vector Clocks สำหรับ Conflict Resolution (แม้ปัจจุบันใช้ Last-Writer-Wins เป็น Default) และ Anti-entropy ด้วย Merkle Trees
CockroachDB
CockroachDB เป็น Distributed SQL Database ที่ใช้ Raft สำหรับ Consensus ทุก Range (Shard) มี Raft Group ของตัวเอง ให้ Serializable Isolation (Strong Consistency) ในขณะที่ยังกระจายข้อมูลได้ เหมาะสำหรับ Use Case ที่ต้องการ SQL + Distribution + Strong Consistency
Google Spanner
Spanner เป็น Globally Distributed Database ของ Google ที่ใช้ TrueTime API (นาฬิกา Atomic + GPS) เพื่อให้ External Consistency ทั่วโลก สามารถทำ Consistent Read ข้าม Data Center ได้โดยไม่ต้อง Lock เป็น Database เดียวที่ทำได้ระดับนี้ แต่ต้องมี Hardware พิเศษ
FLP Impossibility
FLP Impossibility (Fischer, Lynch, Paterson 1985) พิสูจน์ว่า ในระบบ Asynchronous ที่มีแม้เพียง Node เดียวที่อาจ Crash ไม่มี Consensus Algorithm ใดที่รับประกันว่าจะ Terminate ได้ในทุกกรณี
ในทางปฏิบัติ เราหลีกเลี่ยงข้อจำกัดนี้โดยใช้ Timeout (ทำให้ระบบไม่ใช่ Asynchronous แท้จริง), Randomization (เช่น Random Election Timeout ใน Raft) หรือ Failure Detector
Byzantine Fault Tolerance (BFT)
ปัญหาที่กล่าวมาทั้งหมดสมมติว่า Node ที่ล่มจะ "หยุดทำงาน" (Crash Fault) แต่ Byzantine Fault คือกรณีที่ Node อาจ "โกหก" ส่งข้อมูลผิดๆ หรือทำงานผิดปกติ (อาจจาก Bug, Hack หรือ Hardware ผิดพลาด)
Byzantine Generals Problem กล่าวว่า ต้องมี Node อย่างน้อย 3f+1 ตัวจึงจะทน Byzantine Fault ได้ f ตัว เช่น ต้องมี 4 Node จึงทน Node โกหก 1 ตัว
BFT ถูกใช้ใน Blockchain (Bitcoin ใช้ Proof of Work, Ethereum 2.0 ใช้ Casper), Aerospace Systems (ระบบ Avionics ของเครื่องบิน) และ Financial Systems บางตัว ในระบบ IT ทั่วไป ไม่ค่อยใช้ BFT เพราะ Overhead สูงมากและ Crash Fault ก็เพียงพอแล้ว
สรุป
Distributed Systems เป็นหัวข้อที่กว้างและลึก การเข้าใจพื้นฐานเหล่านี้จะช่วยให้คุณออกแบบระบบที่ Scale ได้ ทนต่อความล้มเหลว และเลือก Trade-off ที่เหมาะสมกับ Use Case ของคุณ
สิ่งสำคัญที่ต้องจำคือ: ไม่มี Silver Bullet ในโลก Distributed Systems ทุกอย่างมี Trade-off CAP Theorem บอกว่าต้องเลือกระหว่าง Consistency กับ Availability, PACELC บอกว่าแม้ไม่มี Partition ก็ต้องเลือกระหว่าง Latency กับ Consistency
เริ่มต้นศึกษาจากระบบที่คุณใช้อยู่ทุกวัน: ถ้าใช้ Redis ศึกษา Replication และ Sentinel ของมัน ถ้าใช้ Kafka ศึกษา Partition และ Consumer Group ถ้าใช้ PostgreSQL ศึกษา Streaming Replication การเข้าใจ Distributed Systems จะทำให้คุณเป็นนักพัฒนาที่ดีขึ้นอย่างมาก และเป็นทักษะที่ขาดไม่ได้ในการสัมภาษณ์ System Design
