โหมดมืด
บทที่ 2 — Data Layer (Storage + Cache)
ก่อนอ่าน — ต้องรู้อะไรมาก่อน?
บทนี้ต่อจาก บทที่ 1 — Scalability — ควรเข้าใจ:
- Horizontal vs Vertical scaling (บทที่ 1)
- Connection pool (บทที่ 1)
- SQL basics — SELECT, INSERT, JOIN, INDEX (ดู database book)
- Key-Value store (Redis basic)
หลังจบบท คุณจะ:
- เลือก storage ตาม use case (SQL, NoSQL, Cache, Object storage)
- ใช้ caching strategies ที่เหมาะกับงาน
- ทำ sharding + replication เมื่อ scale
- เข้าใจ CAP theorem + consistency trade-off
1. ปรัชญา — "Storage = Foundation ของ system"
ระบบใหญ่ ๆ ทุกระบบ — ปัญหาส่วนใหญ่อยู่ที่ data layer:
- "DB ช้า"
- "Cache stampede" (สะตัม-พีด — ปัญหา cache miss พร้อมกัน ดูบทที่ 8 ของ distributed-systems)
- "Data inconsistent ระหว่าง region"
- "ทำ migration (ย้าย/แก้โครงสร้างข้อมูล) ผิด → ระบบล่ม"
กฎทอง: ออกแบบ storage ผิด = แก้ทีหลังเจ็บปวดสุด (เทียบกับ app code ที่ refactor ได้ง่ายกว่า)
ก่อนเลือก storage ถามตัวเอง:
1. Structured หรือ unstructured data?
2. Strong consistency หรือ eventual?
3. Read-heavy หรือ write-heavy?
4. ขนาด: GB? TB? PB?
5. Query pattern: simple K-V? Complex JOIN? Full-text? Graph?
6. Access pattern: random หรือ sequential?
7. Latency requirement: ms? second? minute?2. Storage Choice — Decision Tree
ลองดู use case → tool ที่เหมาะ (จัดกลุ่มตาม use-case ไม่ใช่กองรวมชื่อ DB):
Structured + ACID needed (transactions)?
✅ → SQL (Postgres 17, MySQL 8.4 LTS)
✅ → Distributed SQL ถ้า global: CockroachDB 24.x (ค็อก-โรช-ดีบี) / TiDB 8.x
Document-heavy (nested, schema-flex)?
✅ → MongoDB 8.0 / DocumentDB / Postgres JSONB
Simple K-V + ultra fast?
✅ → Redis 8.0 / Valkey / DynamoDB / Memcached
Wide-column (write-heavy, time-bucketed)?
✅ → Cassandra 5.0 (แคส-ซาน-ดร้า) / ScyllaDB (สกิล-ลา-ดีบี — C++ rewrite ของ Cassandra)
Time-series (metrics, logs)?
✅ → TimescaleDB (ไทม์-สเกล-ดีบี) / InfluxDB (อิน-ฟลักซ์-ดีบี) / Prometheus
Search (full-text, fuzzy)?
✅ → Elasticsearch / Algolia / Postgres FTS
Analytics (OLAP, complex queries)?
✅ → ClickHouse (คลิก-เฮาส์) / BigQuery / Snowflake (สโนว์-เฟลค) / Redshift
Graph (social, recommendation)?
✅ → Neo4j (นีโอ-โฟร์-เจ) / Dgraph / Postgres + recursive CTE
(recursive CTE = Common Table Expression แบบเรียกตัวเอง — ใช้ทำ
tree/graph traversal ใน SQL โดยไม่ต้องดึงข้อมูลทุกระดับมาวนใน app)
Files / blobs?
✅ → S3 / GCS / Azure Blob
Multiple จาก list ข้างบน?
✅ → Polyglot persistence (ใช้หลายตัว)🧭 ตารางสรุปคำอ่าน สำหรับ Thai reader (จำคำอ่านไว้ก่อน รายละเอียดอยู่ใน section ถัด ๆ ไป):
ชื่อ คำอ่าน ใช้ทำอะไร Cassandra แคส-ซาน-ดร้า Wide-column NoSQL, write-heavy ScyllaDB สกิล-ลา-ดีบี Cassandra rewrite ด้วย C++, latency ต่ำกว่า CockroachDB ค็อก-โรช-ดีบี Distributed SQL (Postgres wire-protocol) TiDB ไท-ดีบี Distributed SQL (MySQL-compatible) FoundationDB ฟาวเดชั่น-ดีบี Distributed K-V ที่ Apple ใช้ TimescaleDB ไทม์-สเกล-ดีบี Time-series บน Postgres InfluxDB อิน-ฟลักซ์-ดีบี Time-series specialized ClickHouse คลิก-เฮาส์ Columnar OLAP, ultra-fast aggregation Snowflake สโนว์-เฟลค Cloud data warehouse Neo4j นีโอ-โฟร์-เจ Graph DB ที่ดังสุด Qdrant ควอ-ดรานท์ Vector DB (Rust, popular) Weaviate วี-เอ-เวียท Vector DB (Go, native hybrid)
Vector DB — Section แยก (ไม่ใช่บรรทัดเดียวใน decision tree)
⭐ ปี 2026 hot — แต่ Vector DB คือเครื่องมือเฉพาะทาง AI/RAG dev ต้องมี context ก่อน เลยแยก section ใหญ่ไว้ข้างล่าง (§ Vector DB ลึก)
Vectors (AI/ML embedding)?
✅ → pgvector (Postgres extension — default เริ่มต้น)
✅ → Pinecone / Qdrant (ควอ-ดรานท์) / Weaviate (วี-เอ-เวียท)
✅ → Milvus + DiskANN (large-scale, memory-constrained)
✅ → Turbopuffer (เทอร์โบ-พัฟ-เฟอร์) / LanceDB (serverless/embedded, 2024+)Vector DB ลึก — สำคัญในยุค AI/RAG (2024+)
⚠️ (ขั้นต่อยอด · AI/RAG dev) — basic ที่ไม่ทำ AI app ตอนนี้ ข้าม section นี้ได้; กลับมาอ่านเมื่อเริ่มทำ RAG หรือ semantic search
Vector DB เป็น component มาตรฐานใน production ของ AI/RAG apps ปี 2024-2026 — แต่หลายตำราพูดสั้น เพราะเป็นเรื่องใหม่ ต่อไปนี้คือสิ่งที่ engineer ต้องรู้:
text
Vector DB ทำหน้าที่:
1. เก็บ embeddings (vectors) ของ documents / images / users
2. ตอบ "k nearest neighbors" (KNN) ตาม cosine similarity / dot product
3. รองรับ filtering (vector + metadata)Dimensions — เลือกขนาดยังไง
text
OpenAI text-embedding-3-small: 1536 dim (default), 512/1024 (reducible)
→ ราคา $0.02 / 1M tokens (ไม่ใช่ $0.13 — นั่นคือ text-embedding-ada-002 รุ่นเก่า)
OpenAI text-embedding-3-large: 3072 dim (default)
Cohere embed-v3: 1024 dim
Voyage AI voyage-3: 1024 dim (2024, top MTEB ranking)
Nomic Embed v1.5: 768 dim (open-source, fully reproducible 2024)
SBERT / nomic-embed: 384-768 dim (smaller, faster)
Trade-off:
- น้อย dim (384-768) → เร็ว, memory น้อย, accuracy ต่ำกว่า
- มาก dim (1536-3072) → accuracy สูง, ช้า + memory เยอะ
Cost example (1M docs):
- 384 dim × 4 bytes × 1M = 1.5 GB raw
- 1536 dim × 4 bytes × 1M = 6 GB raw
- 3072 dim × 4 bytes × 1M = 12 GB raw
→ Production: choose smallest dim ที่ accuracy acceptableHNSW vs IVF — Algorithm choice
🚀 Senior-only callout — basic ที่ไม่ทำ AI ข้ามได้ หัวข้อนี้เต็มไปด้วย knob ที่ต้อง tune (M, efConstruction, nlist, nprobe) — ถ้ายังไม่ deploy vector DB จริง รู้แค่ "มี HNSW = เร็วใน RAM" และ "IVF/DiskANN = disk-friendly" พอ
📖 กดดูรายละเอียด HNSW vs IVF vs DiskANN
text
HNSW (Hierarchical Navigable Small World):
- กราฟแบบลำดับชั้น — search ที่ top level (sparse) → drill down
- Memory-heavy (ทั้งกราฟใน RAM)
- Build slow, query fast (sub-ms for millions)
- Default ใน pgvector, Pinecone, Qdrant, Weaviate
- Tuning: M (connections), efConstruction (build quality), ef (query quality)
IVF (Inverted File Index):
- Cluster vectors → search เฉพาะ cluster ใกล้
- Disk-friendly (cluster on disk, RAM index)
- Build fast, query slower than HNSW
- ใช้ใน Faiss, Milvus (large-scale)
- Tuning: nlist (clusters), nprobe (clusters to search)
DiskANN (Microsoft 2019):
- Disk-based ANN — accuracy ใกล้ HNSW แต่ใช้ RAM น้อยกว่ามาก
- ใช้ใน Azure AI Search, Milvus (ปัจจุบันเป็น default ของ Milvus)
- ดีกว่า IVF สำหรับ memory-constrained workloads ที่ scale > 100M vectors
Choice:
- < 10M vectors → HNSW (faster query, RAM sufficient)
- 10-100M → benchmark HNSW vs DiskANN
- > 100M vectors → DiskANN / IVF (memory feasible)Hybrid Search — Vector + Keyword
text
Pure vector search มีปัญหา:
- "exact match" หา product code → vector หาไม่เจอ
- Keyword "SKU-12345" → BM25/full-text เหมาะกว่า
Production RAG pattern:
1. Hybrid: vector + BM25 (keyword) parallel
2. Re-rank: combine scores (RRF — Reciprocal Rank Fusion)
3. Use both signals → ดีกว่า single mode
Tools:
- Elasticsearch 8+: built-in hybrid (kNN + BM25)
- pgvector + Postgres tsvector
- Weaviate: native hybrid mode
- Pinecone: sparse-dense hybrid (since 2023)
- Turbopuffer (cloud-native, 2024) และ LanceDB (embedded, 2023+)
→ กำลังมาสำหรับ serverless vector workloadsQuantization — ลด memory 4-8x
🚀 Senior-only callout — basic ข้ามได้ Quantization คือ optimization สำหรับ vector DB ขนาด > 100M vectors ถ้ายังเริ่มต้น (< 10M) pgvector default + HNSW เพียงพอ ไม่ต้อง tune ส่วนนี้
📖 กดดูรายละเอียด Quantization
text
ปัญหา: 1B vectors × 1536 dim × 4 bytes = 6 TB
Solution: compress vectors
Methods:
- Scalar quantization: float32 → int8 (4x smaller)
- Product Quantization (PQ): split into subvectors → cluster each
- Binary quantization: float → 1 bit (32x smaller, lossy)
Trade-off:
- Memory ลด 4-32x
- Accuracy drop 0-5% (acceptable for most)
- Latency: same or faster (cache-friendly)
ใช้ใน:
- Pinecone (auto quantization)
- Qdrant (configurable)
- pgvector 0.7+ (bit + int8 support)💡 กฎทอง 2026: เริ่ม pgvector (Postgres extension) — ครอบคลุม 90% use cases; ย้ายไป Pinecone/Qdrant/Weaviate เมื่อ scale > 10M vectors หรือต้องการ managed service
Default Choice — Postgres ⭐
Postgres ครอบคลุม 99% ของ use case:
- SQL + ACID
- JSONB → document-style flex schema
- Full-text search (tsvector)
- pgvector → AI embeddings
- TimescaleDB extension → time-series
- PostGIS → geo data
- Logical replication → CDCDan McKinley (อดีต CTO Etsy): "Pick boring technology" — เริ่มจาก Postgres ก่อนเสมอ. มี reason ชัดถึงไป NoSQL
ดู Database book สำหรับลึก
3. SQL Database Scaling — ลำดับขั้น
เมื่อ SQL DB เริ่มไม่ไหว ทำตามลำดับนี้ (ไม่ใช่กระโดด):
3.1 Step 1: Connection Pooling
ปัญหา:
100 app instances × 50 connections each = 5,000 connections
Postgres default: max_connections = 100
→ Reject!ทางแก้: PgBouncer / pgpool ทำหน้าที่ multiplex
[App × 100] → [PgBouncer × 2] → [Postgres]
(5000 connections (max 200 connections)
in client side)3.2 Step 2: Read Replicas
ปัญหา: read traffic เยอะกว่า write — primary รับไม่ไหว
ทางแก้: replicate ไป read-only DB
- App writes → primary only
- App reads → distribute among replicas
- Replication lag: 100ms – 1 วินาที (ปกติ)
Spring Boot — multiple datasources
java
@Bean
@Primary
public DataSource primaryDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://primary:5432/db")
.build();
}
@Bean
public DataSource readReplicaDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://replica:5432/db")
.build();
}
// Routing แบบ AbstractRoutingDataSource
// หรือใช้ @Transactional(readOnly = true) → route ไป replicaระวัง: replication lag — ถ้า write แล้ว read ทันที จาก replica อาจได้ค่าเก่า (เรียกว่า "read after write" consistency issue)
3.3 Step 3: Vertical Scale Primary
Primary → upgrade CPU, RAM
- AWS RDS: db.r6g.16xlarge = 64 vCPU, 512 GB RAM
- Handle ~5K-50K writes/sec (ขึ้นกับ workload — short row + sync replicas → ปลาย 5K,
bulk insert + relaxed durability → ปลาย 50K)
- Cost: ~$5-10K/month3.4 Step 4: Transaction Isolation Levels — เลือกให้ถูกก่อน scale
ก่อนกระโดดไป sharding — เข้าใจ isolation levels ก่อน เพราะส่งผลต่อ correctness + performance ของ SQL DB ทุกตัว (DDIA Ch.7 อุทิศ ~50 หน้าให้เรื่องนี้):
| Level | ป้องกัน anomaly อะไร | Default ใน |
|---|---|---|
| Read Uncommitted | ไม่ป้องกันอะไร | — (ไม่ค่อยใช้) |
| Read Committed | Dirty read | Postgres, Oracle, SQL Server* |
| Repeatable Read (Postgres = Snapshot Isolation) | + Non-repeatable read (Postgres SI ยังไม่กัน phantom — ต้อง Serializable) | — |
| Repeatable Read (MySQL InnoDB = next-key locks) | + Non-repeatable read + Phantom (via gap locks) | MySQL InnoDB |
| Serializable | + Write skew, Lost update | — (opt-in) |
| Serializable Snapshot Isolation (SSI) | Serializable ที่ performance ใกล้ Repeatable Read | Postgres opt-in (SERIALIZABLE) |
📌 กลไกต่างกันแม้ระดับเดียวกัน:
- Postgres REPEATABLE READ = snapshot isolation (MVCC) — กัน non-repeatable แต่ ไม่กัน phantom ในความหมาย serializable (ไม่กัน write skew)
- MySQL InnoDB REPEATABLE READ = next-key locks (gap locks) — กัน phantom ด้วย locking
- SQL Server = default Read Committed แบบ lock-based; เปิดโหมด
READ_COMMITTED_SNAPSHOTได้ → MVCC-style ใกล้ Postgres
Anomalies — เกิดเมื่อไหร่
text
Dirty read:
T1: UPDATE balance SET amount = 100 (ยัง COMMIT)
T2: SELECT amount → เห็น 100 (ผิด — อาจ ROLLBACK)
→ ป้องกันโดย Read Committed ขึ้นไป
Non-repeatable read:
T1: SELECT amount → 50
(T2 UPDATE + COMMIT)
T1: SELECT amount → 100 (เปลี่ยน!)
→ ป้องกันโดย Repeatable Read ขึ้นไป
Phantom read:
T1: SELECT COUNT(*) FROM users → 100
(T2 INSERT + COMMIT)
T1: SELECT COUNT(*) FROM users → 101
→ MySQL InnoDB ป้องกันด้วย gap lock; Postgres ต้อง Serializable
Write skew:
Doctor on-call: requires >= 1 doctor
T1: เช็คเงื่อนไข → OK → SET myself OFF
T2: เช็คเงื่อนไข (พร้อม T1) → OK → SET myself OFF
→ ทั้งคู่ commit → 0 doctor on-call!
→ ป้องกันโดย Serializable เท่านั้น
Lost update:
T1: read counter=5 → +1 → write 6
T2: read counter=5 → +1 → write 6
→ ควรเป็น 7 แต่กลายเป็น 6 (lost update)
→ ป้องกันโดย Serializable หรือ SELECT...FOR UPDATEMVCC ใน Postgres
text
MVCC = Multi-Version Concurrency Control
- เก็บข้อมูลหลาย version
- Reader อ่าน snapshot ตอน transaction เริ่ม
- Writer ไม่ block reader, reader ไม่ block writer
Postgres:
- Default isolation = Read Committed
- Each statement sees new snapshot
- Snapshot tracking via xid (transaction ID)
- VACUUM = clean up old versions (sometimes critical for performance)
Trade-off:
✅ ไม่มี read lock → high concurrency
❌ Bloat: dead tuples สะสม → VACUUM งานหนัก
❌ Long-running transaction → block VACUUM → bloatSerializable Snapshot Isolation (SSI) — Postgres's killer feature
text
SSI ของ Postgres (since 9.1, 2011) = ดีที่สุดในวงการ:
- Performance ใกล้ Repeatable Read
- Correctness = Serializable
- ตรวจ "dangerous pattern" ที่อาจเกิด write skew → abort 1 transaction
- 📖 Reference paper: Cahill, Röhm, Fekete — "Serializable Isolation
for Snapshot Databases" (SIGMOD 2008)
Trade-off vs 2PL (MySQL InnoDB SERIALIZABLE = locking-based 2PL ไม่ใช่ SSI):
✅ SSI: optimistic — high throughput on low contention
❌ 2PL: pessimistic — high lock overhead
ใช้เมื่อ:
- Write skew possible (multi-row constraints)
- Counter-style operations
- Financial calculations
วิธีใช้:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- code
COMMIT;
-- ระวัง: client ต้อง retry ตอน serialization_failure (error 40001)💡 กฎทอง: Default Read Committed สำหรับงานทั่วไป → ใช้ Serializable เฉพาะ critical paths (payment, inventory, voting) — ระวัง retry logic เพราะ Serializable อาจ abort
📌 Checklist สำหรับ basic ที่ใช้ Spring Boot + JPA/Hibernate:
- ใช้ default (Read Committed ของ Postgres) สำหรับ CRUD ทั่วไป → ครอบคลุม 90% use cases
- เปลี่ยนเป็น Serializable เมื่อเจอ write skew (เช่น on-call doctor case, voting, inventory atomic decrement)
- SSI = optional — basic ไม่ต้องเข้าใจ details ก็ทำงานได้
3.5 Step 5: Sharding (Horizontal Write Scale)
ปัญหา: vertical scale ถึงเพดาน — write QPS > single DB throughput
ทางแก้: shard — แบ่ง data ไป primary หลายตัว
Single Primary → multiple primaries (shards)
Shard by user_id:
- Shard 1: user_id 1 - 1M
- Shard 2: user_id 1M - 2M
- Shard 3: user_id 2M - 3Mรายละเอียด sharding ที่ section ถัดไป
4. Sharding — เจาะลึก
Sharding = "split data ข้าม DB หลายตัว"
4.1 ทำไมต้อง shard
✅ Data เกินขนาด single DB (เช่น > 5 TB)
✅ Write QPS เกิน throughput ของ single DB
✅ Hot table (orders, events) ต้องกระจายระวัง: Sharding ทำให้ระบบซับซ้อน 10x — ใช้เป็น last resort หลังลอง vertical + read replica + cache แล้ว
4.2 Sharding Strategies — 4 แบบ
Strategy A: Range-based
User ID 1-1M → Shard 1
User ID 1M-2M → Shard 2
User ID 2M-3M → Shard 3✅ Simple — easy to understand
✅ Range query ดี (เช่น "users created last month")
❌ Hot shard — user ใหม่ทั้งหมดไปอยู่ shard ล่าสุด → unbalancedใช้กับ: time-series (ทำได้ดี เพราะอ่านเฉพาะ recent)
Strategy B: Hash-based ⭐ (Default)
hash(user_id) % N → shard
User ID 42 → hash(42) % 4 = 2 → Shard 2
User ID 43 → hash(43) % 4 = 0 → Shard 0
User ID 100 → hash(100) % 4 = 1 → Shard 1✅ Uniform distribution — balance ดี
✅ Simple
❌ Re-sharding (เพิ่ม shard) ยาก — ต้อง rehash ทุก keyStrategy C: Consistent Hashing
Map shards บน hash ring (0 to 2^32)
Each key → ring position → nearest shard
เพิ่ม shard:
- Only 1/N data ต้อง move
- ไม่ rehash ทุก key✅ Smooth re-sharding (เพิ่ม shard ไม่กระทบหนัก)
✅ ใช้ใน: DynamoDB, Cassandra, Riak
❗ ระวัง: Redis Cluster ใช้ "fixed 16384 hash slots" ไม่ใช่ consistent hashing
(slot-based sharding — คนละ approach กับ ring-based)เทคนิคเสริม: ใช้ "virtual nodes" — 1 physical shard map เป็น 100+ virtual node บน ring → balance ดีกว่า
Strategy D: Directory-based
มี lookup table: key → shard
key1 → shard 3
key2 → shard 1
key3 → shard 2
App ถาม lookup ก่อนทุก query✅ Flexible — เก็บ shard mapping ที่ไหนก็ได้
❌ Lookup table = single point of failure
❌ Extra hop ทุก query4.3 ปัญหาที่ Sharding ทำให้ปวดหัว
Cross-shard query
"List all orders from last week"
→ ต้องถาม shard ทุกตัว แล้ว merge
→ ช้ามากทางแก้:
- Denormalize (เก็บ summary ที่ shard เดียว)
- Query each shard + merge in app
- Use search index (Elasticsearch) สำหรับ cross-shard
Cross-shard transaction
"Transfer money from user A (shard 1) to user B (shard 2)"
→ 2-phase commit (2PC) ซับซ้อน + ช้าทางแก้:
- Saga pattern (event-driven, eventually consistent)
- Single-shard design (ถ้าทำได้)
Hot key
Celebrity user (1 user → 90% of traffic)
→ ทุก request ไป shard เดียว
→ Shard overloadทางแก้:
- Sub-shard hot keys (split user ออก)
- Replicate hot keys ไปทุก shard
- Application-level cache
Re-sharding
ตอน scale up — เพิ่ม shard จาก N → N+1
→ Data ต้อง move
→ ระวัง downtime + consistencyทางแก้:
- Consistent hashing
- Dual-write (เขียนทั้ง old + new ระหว่าง migration)
- Backfill + cutover
Unique ID across shards
Auto-increment ใช้ไม่ได้ (shard 1 + shard 2 ต่างคน คนละ counter)ทางแก้:
- UUID (128-bit random)
- Snowflake ID (Twitter — timestamp + worker + sequence)
- ULID (sortable UUID)
5. Replication — Reliability + Read Scale
replication คือการเก็บสำเนา DB ไว้หลายชุด เพื่อสองเป้าหมาย: scale การอ่าน (กระจาย read ไป replica) และ reliability (replica เป็น standby เมื่อ primary ล่ม) ส่วนนี้เทียบรูปแบบหลัก — primary-replica แบบ async (default, อ่านอาจ stale) กับ multi-primary (เขียนได้หลายที่ แต่ต้องแก้ conflict):
5.1 Primary-Replica (Async) ⭐ Default
- Replication lag: 10ms – 1 second (ปกติ)
- Consistency: eventually consistent reads
✅ Read scale (อ่านจาก replica)
✅ DR (replica เป็น standby)
❌ Replica อาจ stale (ช้ากว่า primary)5.2 Multi-Primary (Active-Active)
Region A primary ←─sync→ Region B primary
Both can read + write
Conflict resolution required (last-write-wins / CRDT / app logic)✅ Low latency global writes
✅ No failover (always active)
❌ Conflict resolution — complex
❌ Eventual consistency5.3 Synchronous Replication
Write → wait for replica confirm → return success
✅ No replication lag — replica ทันที
❌ Slower writes (depend on slowest replica)
❌ Replica down → write blockTrade availability เพื่อ consistency — ใช้สำหรับ critical data (banking core)
5.4 Snapshot + WAL Shipping
1. Full snapshot ทุกคืน
2. Write-Ahead Log (WAL) ส่งต่อเนื่องไป standby
3. Standby apply WAL → up to date
Used for:
- DR (disaster recovery)
- Point-in-time recovery (กู้ data ไปที่เวลาก่อนหน้า)
- Read-only replica (analytical workload)6. CAP Theorem — เลือกได้แค่ 2 ใน 3
CAP = ข้อเท็จจริงพื้นฐานของ distributed system
6.1 3 Properties
C — Consistency
ทุก read ได้ data ล่าสุดที่ write
A — Availability
ทุก request ตอบ success (ไม่ error)
P — Partition tolerance
ระบบทำงานได้แม้ network partition เกิด6.2 ในระบบ distributed จริง — P เป็น "ต้องมี"
Network partition จะเกิดเสมอ — เพราะ:
- Hardware fail
- Network slow / drop packet
- Cloud region disconnect
→ ในเหตุการณ์ partition ต้องเลือก: C หรือ A?6.3 CP vs AP
CP systems (เลือก consistency):
- Single-leader DB (Postgres primary)
- Spanner, CockroachDB
- ZooKeeper, etcd
→ ในเวลา partition: refuse to serve (return error)
→ "ดีกว่า return ผิด ดีกว่า return อะไรก็ไม่ได้"
AP systems (เลือก availability):
- Cassandra (default config = AP; แต่ถ้าตั้ง QUORUM consistency level
+ LWT (lightweight transactions, Paxos-based) จะ behavior ใกล้ CP
→ shorthand "Cassandra = AP" technically ไม่แม่นแบบ Kleppmann)
- DynamoDB
- Riak
→ ในเวลา partition: ตอบทุกคน (อาจ data เก่า)
→ "ดีกว่า return อะไรก็ได้ ดีกว่า refuse"6.4 PACELC — Extension สมัยใหม่
🚀 โซนขั้นสูง — มือใหม่ข้ามได้ PACELC เป็นส่วนต่อยอดของ CAP สำหรับคนที่อยากเข้าใจลึกขึ้น ถ้ายังไม่อินกับ CAP ดี ข้ามหัวข้อนี้ไปก่อนได้
CAP บอกแค่ตอน partition — แต่ตอนปกติ (no partition) ก็มี trade-off:
If Partition (P): Availability หรือ Consistency?
Else (E): Latency หรือ Consistency?
Examples:
PA/EL — Cassandra (favor availability + low latency)
PC/EC — Postgres; MongoDB modern default (since 5.0, writeConcern majority
+ readConcern majority) = PC/EC
— legacy MongoDB w:1 = PA/EL (config-dependent!)
PA/EC — uncommon classification (typical RDBMS sync replication = PC/EC
ทั้งคู่; PA/EC จะแปลว่า "ยอม inconsistency ตอน partition แต่ sync ตอนปกติ"
ซึ่งหา system จริงยาก)
PC/EL — Some banking systems📌 MongoDB PACELC depends on configuration — อย่าจำว่า "MongoDB = PC/EC" หรือ "AP" แบบ static. ดู PACELC ในเล่ม distributed-systems/01-consistency-cap สำหรับ deep dive
Insight: บ่อยครั้ง app เลือก eventual consistency (PA/EL) เพราะ latency สำคัญกว่า — Twitter feed late 1 วินาทีไม่ตาย แต่ slow page = user หาย
7. Consistency Models — มีหลายระดับ
Strong consistency:
- Read ได้ data ล่าสุดทันที
- Postgres single-master, Spanner
Eventual consistency:
- Reads อาจ stale ชั่วคราว
- All replicas converge เวลาผ่านไป
- Cassandra, DNS, social feed
Read-your-writes:
- User เห็น write ของตัวเองทันที
- คนอื่นอาจ stale
- ใช้สำหรับ user profile
Monotonic reads:
- หลังเห็น v2, ไม่เห็น v1 (ไม่ถอยหลัง)
- ใช้ session affinity
Session consistency:
- Strong ภายใน session, eventual ข้าม session
Causal consistency:
- Write ที่เกี่ยวข้องกัน เห็นตามลำดับ
- เช่น "comment ตอบ post" — เห็น post ก่อน comment เสมอ7.1 เลือก consistency ตาม use case
| Use case | Consistency ที่ใช้ |
|---|---|
| Banking transfer | Strong |
| Social post visible | Eventual (delay ไม่กี่วินาที OK) |
| User own profile | Read-your-writes |
| View count | Eventual (approximate) |
| Cart ใน e-commerce | Strong (ห้ามหาย items) |
| Like count | Eventual (approximate) |
| Inventory ตอน checkout | Strong (ห้าม oversell) |
8. Caching — Why + How
ศัพท์ cache เยอะมาก — กางไว้ก่อนตรงนี้ (เดี๋ยวเจอตัวจริงในหัวข้อถัด ๆ ไป) ยังไม่ต้องจำ แค่รู้ว่า "ภาษาคน" แต่ละคำแปลว่าอะไร:
อังกฤษ ภาษาคน ใช้ตอนไหน cache ที่พักข้อมูลที่เร็วกว่า DB (อยู่ใน RAM) อยากอ่านข้อมูลเดิมซ้ำ ๆ ให้เร็ว cache hit / miss เจอใน cache / ไม่เจอ (ต้องไปถาม DB) วัดว่า cache ช่วยได้แค่ไหน cache-aside app เช็ค cache เองก่อน ถ้าไม่มีค่อยถาม DB แล้วเก็บลง cache แบบที่นิยมสุด ใช้เป็น default write-through เขียนลง cache + DB พร้อมกันทุกครั้ง อยากให้ cache ตรงกับ DB เสมอ (ยอม write ช้าลง) write-back เขียนลง cache ก่อน แล้วค่อย sync ลง DB ทีหลัง อยาก write เร็วมาก (ยอมเสี่ยงข้อมูลหายถ้า cache พังก่อน sync) eviction "ไล่" ข้อมูลออกจาก cache เมื่อเต็ม cache มีพื้นที่จำกัด ต้องเลือกทิ้งของเก่า LRU ไล่ตัวที่ "ไม่ได้แตะนานสุด" ออกก่อน วิธี eviction ที่ใช้บ่อยสุด TTL อายุของข้อมูลใน cache (หมดอายุแล้วทิ้ง) กันข้อมูลเก่าค้างนานเกินไป stampede / thundering herd cache หมดอายุพร้อมกัน → request นับพันถล่ม DB พร้อมกัน ต้องระวังกับ key ยอดนิยม hot key key ที่ถูกอ่านบ่อยมากผิดปกติ (เช่น โพสต์ดารา) 1 key รับ traffic ทั้งหมด → เครื่องเดียวแบกไม่ไหว
8.1 ทำไมต้อง cache
DB read: 50ms (incl. network + disk)
Cache read: 1ms (in-memory)
→ Cache เร็วกว่า 50x
→ Cache ลด DB load
→ Cache ลด cost (DB compute + bandwidth)8.2 Caching Layers — ภาพรวม
1. Browser cache ← user's own machine
2. CDN edge cache ← regional
3. Reverse proxy cache ← Nginx, Varnish (at server)
4. App-level cache ← in-process LRU
5. Distributed cache ← Redis, Memcached (shared)
6. DB query cache ← built into DBใกล้ user = เร็วกว่า + ราคาถูกกว่า
9. Cache Strategies — 4 หลัก
มีหลายวิธีในการวาง cache ระหว่างแอปกับ DB และแต่ละแบบจัดการเรื่อง "ใครเป็นคนเติม cache" และ "เขียนผ่าน cache หรือไม่" ต่างกัน — cache-aside (แอปจัดการเอง ยอดนิยมสุด), read-through, write-through, write-back ส่วนนี้อธิบาย 4 แบบหลักพร้อม trade-off เพื่อเลือกให้เหมาะกับ workload:
9.1 Cache-Aside (Lazy Loading) ⭐ Default
โค้ดในหัวข้อนี้เป็น pseudo-code เน้นไอเดีย — มีทั้ง TypeScript และ Java (Spring) ปนกัน ไม่ต้องอ่านออกทุกบรรทัด จับ flow ตาม comment ก็พอ
typescript
async function getUser(id: number): Promise<User> {
// 1. เช็ค cache ก่อน — ถ้ามีก็คืนเลย (เร็ว)
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
// 2. ไม่เจอใน cache (cache miss) — ไปถาม DB
const user = await db.queryOne('SELECT * FROM users WHERE id = $1', id);
// 3. เก็บผลลง cache ไว้ใช้ครั้งหน้า (อายุ 3600 วินาที = 1 ชม.)
await redis.setex(`user:${id}`, 3600, JSON.stringify(user));
return user;
}
async function updateUser(user: User) {
await db.update(user); // เขียน DB ก่อน
await redis.del(`user:${user.id}`); // ลบ cache ทิ้ง (invalidate) — ครั้งหน้าจะโหลดของใหม่
}✅ Simple
✅ Cache เฉพาะที่ accessed (ไม่เปลือง memory)
❌ Cold cache penalty (request แรกช้า)
❌ Inconsistency window — between DB update และ cache invalidation9.2 Read-Through
App → Cache (intermediate layer) → DBApp ไม่รู้ DB ตรง ๆ — cache library handle miss
ตัวอย่าง: Spring @Cacheable
@Cacheable("users")
public User findById(Long id) {
return userRepo.findById(id).orElseThrow();
}✅ Simpler code (cache library จัดการ)
❌ Cache library lock-in9.3 Write-Through
typescript
async function updateUser(id: number, data: User) {
await db.update(id, data); // write DB
await redis.set(`user:${id}`, data); // write cache (same time)
}✅ Cache never stale (always sync with DB)
❌ Slower writes (sync 2 places)
❌ Write cache สำหรับ data ที่อาจไม่มีคนอ่าน9.4 Write-Back (Write-Behind)
typescript
async function updateUser(id: number, data: User) {
await redis.set(`user:${id}`, data); // write cache
queue.push({ type: 'write', id, data }); // async write DB
}
// Background worker: read queue → batch write DB✅ Fast writes (return ทันทีหลัง write cache)
❌ Risk data loss ถ้า cache crash ก่อน flush
❌ Complex — ใช้ใน OS page cache, write-back DB10. Cache Invalidation — "Hardest Problem"
Phil Karlton: "There are only two hard things in Computer Science: cache invalidation and naming things."
10.1 Strategy
1. TTL (time-based expire)
- cache.set(key, value, ttl=3600)
- Simple, may show stale
- ใช้กับ data ที่ stale นิดหน่อย OK
2. Explicit invalidation
- On write → cache.delete(key)
- Risk: forget to delete on some code path
- ดี: เร็ว, fresh
- แย่: bug-prone
3. Version-based key
- cache.get("user:42:v3")
- Bump version on schema/structure change
- ดี: invalidate ทั้งหมดทันที (เปลี่ยน version)
4. Write-through (สด always)
- Cache ไม่เคย stale
- Cost: slower writes10.2 Pattern จริง — Cache-aside ที่ดี
typescript
const TTL = 3600; // 1 hour fallback
async function getUser(id: number): Promise<User> {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.findById(id);
await redis.setex(`user:${id}`, TTL, JSON.stringify(user));
return user;
}
async function updateUser(user: User) {
await db.update(user);
await redis.del(`user:${user.id}`); // invalidate
}
async function deleteUser(id: number) {
await db.delete(id);
await redis.del(`user:${id}`); // invalidate
}Insight: TTL ทำหน้าที่ "safety net" — ถ้าลืม invalidate ที่ไหน TTL จะช่วย expire ในที่สุด
11. Cache Eviction — Cache เต็ม ต้องไล่ใครออก?
11.1 Algorithms
| Algorithm | คำอธิบาย | ใช้เมื่อ |
|---|---|---|
| LRU (Least Recently Used) ⭐ | ไล่ออกที่ไม่ access นานสุด | Default ทั่วไป |
| LFU (Least Frequently Used) | ไล่ออกที่ access น้อยสุด | Pattern เสถียร |
| FIFO (First In First Out) | ไล่ออกตามลำดับเข้า | Simple |
| TTL | ไล่ออกเมื่อหมดอายุ | Time-bounded data |
| ARC (Adaptive Replacement Cache) | LRU + LFU hybrid | Database (Postgres) |
11.2 Redis maxmemory-policy
maxmemory-policy options:
- noeviction → return error เมื่อเต็ม
- allkeys-lru → evict any key (LRU)
- volatile-lru → evict only keys with TTL set (LRU)
- allkeys-lfu → least frequently used
- allkeys-random → random
- volatile-random → random with TTL
- volatile-ttl → closest to expiry firstส่วนใหญ่ใช้
allkeys-lru— ถ้าทำ cache อย่างเดียว
12. Cache Anti-Patterns — สิ่งที่ทำผิดบ่อย
12.1 ❌ Cache Stampede (Thundering Herd)
stampede / thundering herd = "ฝูงสัตว์แตกตื่นวิ่งพร้อมกัน" — ในที่นี้คือ request จำนวนมหาศาลพุ่งเข้า DB พร้อมกันในจังหวะเดียว (ตอน cache key ยอดนิยมหมดอายุพอดี) จน DB รับไม่ไหว
ปัญหา:
Hot key (e.g., homepage data) expire
↓
1,000 requests มาพร้อมกัน
↓
ทุก request cache miss
↓
ทุก request hit DB พร้อมกัน
↓
DB overload — ระบบล่มทางแก้:
typescript
// Stale-while-revalidate
async function getWithRefresh<T>(
key: string,
loader: () => Promise<T>,
ttl: number = 3600
): Promise<T> {
const data = await redis.get(key);
if (data) {
const { value, expiresAt } = JSON.parse(data);
// Return stale + async refresh ถ้าใกล้ expire
if (Date.now() > expiresAt - 60_000) {
loader().then(fresh => {
redis.set(key, JSON.stringify({
value: fresh,
expiresAt: Date.now() + ttl * 1000
}));
});
}
return value;
}
// Cache miss
const fresh = await loader();
await redis.set(key, JSON.stringify({
value: fresh,
expiresAt: Date.now() + ttl * 1000
}));
return fresh;
}Strategies อื่น:
- Lock — only first request hits DB
- Probabilistic early expiration
- Randomize TTL (อย่า expire พร้อมกัน)
12.2 ❌ Thundering Herd ตอน Cache Restart
ปัญหา:
Redis restart → cache empty
ทุก request hit DB → DB overloadทางแก้:
- Pre-warm cache ตอน startup
- Lock + queue (rate limit DB)
- Gradual rollout
12.3 ❌ Negative Cache Miss
ปัญหา:
Query DB: "user:99999" → not found
ไม่ cache อะไร
Request ถัด ๆ มา hit DB อีก (เพราะ cache miss ยังเหมือนเดิม)ทางแก้:
- Cache "null" result ด้วย short TTL
typescript
const user = await db.findById(id);
if (!user) {
await redis.setex(`user:${id}`, 60, 'NULL'); // cache miss ~1 min
return null;
}12.4 ❌ Hot Key
ปัญหา:
Celebrity user (1 key = 90% of all reads)
→ 1 Redis instance overloadทางแก้:
- Replicate hot key ทุก Redis node
- Local cache (in-process) สำหรับ ultra-hot keys
- Sharding ไม่ช่วย (ยังเป็น 1 key)
13. Redis at Scale
Redis เริ่มจาก instance เดียวที่เร็วมาก แต่พอ traffic/ข้อมูลโตก็ต้อง scale — ส่วนนี้ไล่ขั้น: single instance → replication (scale read) → Sentinel (auto-failover) → Cluster (sharded + scale write) พร้อมรายชื่อ managed service และทางเลือก multi-threaded ที่เร็วกว่า:
13.1 Single Instance
Redis on 1 server:
- ~100K ops/sec
- ~30 GB RAM useful (เว้น overhead)
- Single point of failure13.2 Replication (Read Scale)
Master ──→ Replica 1
──→ Replica 2
Writes → master
Reads → replicas (มี lag)13.3 Sentinel (Auto-failover)
Master ──→ Replica 1
──→ Replica 2
Sentinel monitors:
- Master down → promote replica
- Update clients13.4 Redis Cluster (Sharded) ⭐
6 nodes (3 master + 3 replica):
Master 1 ─ Replica 1
Master 2 ─ Replica 2
Master 3 ─ Replica 3
Data sharded ข้าม masters ด้วย hash slot
Auto-failover + horizontal scale13.5 Tools
- Redis (self-host)
- AWS ElastiCache for Redis (managed)
- GCP Memorystore for Redis
- Upstash ⭐ — serverless Redis (per-request pricing)
- Dragonfly — Redis-compatible, multi-threaded (เร็วกว่า)
- KeyDB — Redis fork, multi-threaded
- Valkey — Redis fork (after license change 2024)14. Object Storage (S3, GCS, Azure Blob)
ไฟล์ใหญ่ ๆ อย่างรูป วิดีโอ backup ไม่ควรเก็บใน DB — object storage (S3 และเพื่อน) ออกแบบมาเพื่อสิ่งนี้: ถูกมาก, scale ไม่จำกัด, ทนทานระดับ 11 nines ส่วนนี้อธิบายลักษณะเด่นและ use case ที่เหมาะ/ไม่เหมาะ:
14.1 ลักษณะของ Object Storage
S3 (และเพื่อน):
- เก็บ file / blob
- Cheap ($0.023/GB/month)
- Scale infinite
- Durable (11 nines = 99.999999999%)
- Eventually consistent (read after write ใช้ได้ปกติ)14.2 Use Cases
✅ ดี:
- User uploads (image, video, document)
- Static website hosting
- Backup + archive
- Big data (parquet for analytics)
- Container artifact / Docker layer cache
- ML model storage
❌ ไม่ดี:
- Frequent small read/write (use EFS แทน)
- Transactional data (use DB)
- Filesystem (use EFS, Lustre)14.3 Pricing Tiers (S3 example, US-East ปี 2026)
S3 Standard: $0.023/GB/month
S3 Intelligent-Tier: auto-tier based on access
S3 IA (Infreq): $0.0125/GB/month
S3 Glacier Instant: $0.004/GB/month
S3 Deep Archive: $0.00099/GB/month (12-hour retrieval)
Egress: $0.09/GB (แพง! ใช้ CDN แทน)
Request: $0.005/1000 PUT, $0.0004/1000 GET14.4 Patterns
Pre-signed URL:
- Client uploads ตรงไป S3 (ข้าม app server)
- App generate short-lived signed URL
- ลด server bandwidth + cost
CloudFront / CDN ผ่าน S3:
- Cache S3 reads ที่ edge
- ลด egress cost
- เร็วกว่าสำหรับ user
Lifecycle policies:
- Move old data → cheaper tier auto
- Auto-archive หลัง 90 วัน
- Delete หลัง retention15. Polyglot Persistence — หลาย DB ในระบบเดียว
15.1 หลักการ
ไม่มี DB ใดที่ "ดีทุกอย่าง"
→ ใช้ DB ที่เหมาะ per use case15.2 ตัวอย่าง — E-commerce
Postgres — orders, users (ACID + JOIN)
Redis — sessions, cart, hot cache
Elasticsearch — product search (full-text)
S3 — product images, invoices
ClickHouse — analytics, reporting
DynamoDB — shopping cart (high QPS, simple K-V)
pgvector — recommendation embeddings15.3 Trade-off
✅ ใช้เครื่องมือที่เหมาะกับงาน
✅ Performance ดี per use case
❌ Operational complexity (หลาย DB ต้องดูแล)
❌ Data consistency ข้าม DB (eventually consistent)
❌ ค่าใช้จ่าย infrastructure เพิ่มกฎ: ไม่ start polyglot ตั้งแต่แรก — เริ่ม Postgres + Redis แล้วเพิ่มตามต้อง
16. Time-Series Data — ข้อมูลตามเวลา
16.1 ลักษณะของ Time-series
Properties:
- Append-only (ไม่ update)
- Time-ordered
- Highly compressible
- Query by time range
- Aggregation (avg, max, min) common
ตัวอย่าง:
- Metrics (CPU, memory, latency)
- Logs (events ตามเวลา)
- IoT (sensor reading)
- Financial (stock price)
- User activity tracking16.2 ทำไม specialized DB เร็วกว่า
Postgres สำหรับ time-series:
- Random read
- ทุก row มี timestamp index
- ไม่ compress ตามเวลา
TimescaleDB / InfluxDB:
- Compress data ตามเวลา (10-100x)
- Auto-partition by time
- Optimized aggregation
- Down-sample old data (sec → min → hour)16.3 Tools
| Tool | จุดเด่น |
|---|---|
| Prometheus | Pull-based metrics, 15-day default retention |
| InfluxDB | Push-based, time-series specialized |
| TimescaleDB ⭐ | Postgres extension — SQL ที่คุ้นเคย |
| ClickHouse | Columnar, ultra fast aggregation |
| VictoriaMetrics | Prometheus-compatible แต่เร็วกว่า |
| Mimir / Cortex / Thanos | Prometheus long-term storage |
17. Database Federation — แยกตาม Domain
แทนที่จะมี DB ก้อนเดียวให้ทุกอย่าง federation แบ่ง DB ตาม domain (user, order, product แยกกัน) — แต่ละทีมเป็นเจ้าของ DB ตัวเอง scale อิสระได้ (รากฐานของ "DB per service" ใน microservices) แต่แลกกับ cross-domain query ที่ต้องผ่าน API และความซับซ้อนในการดูแล:
Federation = split by feature/domain
User DB — users, auth
Order DB — orders, payments
Product DB — products, catalog
Analytics DB — events, aggregates
→ แต่ละทีมเป็นเจ้าของ DB ตัวเอง
→ Microservice pattern (DB per service)
→ Cross-DB joins ต้องผ่าน API✅ Independent scaling
✅ Team autonomy
❌ Cross-domain query ลำบาก
❌ Eventually consistent
❌ Operational complexity18. CQRS — Command Query Responsibility Segregation
🚀 โซนขั้นสูง — มือใหม่ข้ามได้ CQRS และ Event Sourcing (§20) เป็น pattern ของระบบใหญ่ที่ซับซ้อน รู้แค่ชื่อ + ไอเดียคร่าว ๆ ("แยกฝั่งเขียนกับฝั่งอ่านออกจากกัน") พอ ยังไม่ต้องลงลึกตอนนี้
18.1 หลักการ
แยก write model + read model
Write side:
- Normalized DB (Postgres)
- Strong consistency
- ACID transactions
Read side:
- Denormalized projection
- Different DB for query (Elasticsearch, materialized view)
- Fast reads
- Eventually consistent
Flow:
User submits → Write to primary
→ Emit event
→ Worker updates read projection
User reads ← Read from projection18.2 ตัวอย่าง
18.3 เมื่อไหร่ดี
✅ Read >> Write (10:1+)
✅ Complex read queries (search, aggregation)
✅ Different consistency needs (read OK eventually, write strong)
❌ Simple CRUD
❌ Small app19. Materialized Views — Pre-computed Queries
query ที่ซับซ้อน (join + aggregate หลายตาราง) ทำทุกครั้งที่อ่านนั้นช้า — materialized view คือการ "คำนวณไว้ล่วงหน้า" แล้วเก็บผลเป็นตารางจริง อ่านเร็วมากแต่ข้อมูลจะ stale ตามรอบ refresh เหมาะกับ dashboard/รายงานที่ไม่ต้องการความสดวินาทีต่อวินาที:
sql
CREATE MATERIALIZED VIEW user_stats AS
SELECT
user_id,
COUNT(*) AS post_count,
MAX(created_at) AS last_post
FROM posts
GROUP BY user_id;
-- Refresh manually หรือ scheduled
REFRESH MATERIALIZED VIEW CONCURRENTLY user_stats;✅ Read ใช้ pre-computed result (เร็วมาก)
❌ Data stale ตามเวลา refresh20. Event Sourcing — Store Events Not State
20.1 หลักการ
แทนที่จะเก็บ "current state"
เก็บ "all events" (immutable log)
Bank account ตัวอย่าง:
- Event: AccountOpened
- Event: Deposit($100)
- Event: Deposit($50)
- Event: Withdraw($30)
Current balance = replay events = $12020.2 ข้อดี-ข้อเสีย
✅ Full audit log (track ทุกการเปลี่ยนแปลง)
✅ Time-travel (state ที่เวลาใด ๆ ได้)
✅ ใช้กับ CQRS ดี
❌ Complex implementation
❌ Storage grows over time
❌ Replay slow ถ้า events เยอะ
→ Snapshot every N events20.3 เมื่อไหร่ใช้
✅ ดี:
- Banking, finance (ต้องการ audit)
- Multiplayer game state
- Order tracking
- Compliance (regulatory)
- Need full history21. Data Lake + Warehouse
ข้อมูลสำหรับวิเคราะห์ (analytics) เก็บคนละแบบกับ DB ของแอป — data lake เก็บข้อมูลดิบทุกฟอร์แมตราคาถูก (เหมาะ ML/exploration) ส่วน data warehouse เก็บข้อมูลที่ clean + เป็น columnar (เหมาะ BI/รายงาน) ปัจจุบันมี lakehouse ที่รวมทั้งสองเข้าด้วยกัน:
Data Lake:
- Raw data (any format — JSON, CSV, Parquet, binary)
- Cheap storage (S3, GCS)
- Schema-on-read (interpret ตอน query)
- Use: ML training, exploration
Data Warehouse:
- Structured + cleaned
- Columnar storage (fast analytics)
- Schema-on-write (validate ตอน insert)
- Use: BI, reporting, dashboards
Modern: Lakehouse (Databricks, Snowflake, Iceberg)
- Combine ทั้งคู่ดู Database book สำหรับ deep dive
22. Backup + Disaster Recovery
ข้อมูลคือสิ่งที่กู้คืนไม่ได้ถ้าหาย — backup จึงไม่ใช่ทางเลือก ส่วนนี้ครอบคลุมตั้งแต่ชนิดของ backup (full/incremental/differential), ความถี่ตามความสำคัญ, ที่เก็บ (คนละ region/air-gap) ไปจนถึง RPO/RTO ที่กำหนดว่ายอมเสียข้อมูล/downtime ได้แค่ไหน และย้ำว่าต้อง "ทดสอบ restore" จริง ไม่ใช่แค่ backup:
22.1 Backup Types
Full:
- Complete copy
- Slow, expensive
- ทุก week / month
Incremental:
- เฉพาะ change ตั้งแต่ backup ล่าสุด
- เร็ว ประหยัด
- ทุกวัน
Differential:
- Change ตั้งแต่ full backup ล่าสุด
- Middle ground22.2 Frequency
Critical data: ทุกชั่วโมง (เช่น order, payment)
Important: ทุกวัน (เช่น user data)
Archive: ทุก week + month22.3 Storage
Same region: fast restore
Different region: DR (disaster recovery)
Offline / air-gap: ransomware protection22.4 ทดสอบ Restore!
Backups are useless if you can't restore them
✅ Test restore ทุก quarter
✅ Document procedure
✅ มีคนรู้ขั้นตอน — ไม่ใช่แค่ engineer 1 คน22.5 RPO vs RTO
RPO (Recovery Point Objective):
- Max acceptable data loss
- "เรา accept loss 1 hour of data" → RPO = 1h
RTO (Recovery Time Objective):
- Max acceptable downtime
- "เรา accept down 1 hour" → RTO = 1hMatch strategy
RPO 0 + RTO 0 → Multi-region sync (แพงสุด)
RPO 1h + RTO 4h → Frequent backup + warm standby
RPO 24h + RTO 1day → Daily backup + restore on demand23. Real Example — Instagram-Like Photo Storage
มาดูว่า concept data layer ทั้งหมดประกอบกันเป็นระบบจริงยังไง — การเก็บรูปแบบ Instagram ใช้ object storage (S3) สำหรับไฟล์, Postgres เก็บ metadata, queue + worker ทำงานหนักทีหลังแบบ async (สร้าง thumbnail, index, fanout) ตัวอย่างนี้รวม object storage + DB + cache + async เข้าด้วยกัน:
[User uploads photo]
↓
[API Server]
↓ generate signed URL
[Upload directly to S3]
↓
[API insert row in Postgres:
photos (id, user_id, s3_key, status='pending')]
↓
[Push to queue: PhotoProcessingJob]
↓
[Return 200 to user] ← fast path, ~1 second
Workers (background, parallel):
1. Image Processor:
- Generate thumbnails (small, medium, large) → S3
- Extract EXIF metadata
- Update photos.status = 'ready'
2. Search Indexer:
- Index photo metadata → Elasticsearch
3. Fanout Worker:
- Find followers
- Insert into followers' feed Redis
[Read flow: browse feed]
↓
[App] → [API] → [Redis: user_feed cache] (precomputed)
↓ cache miss
[Postgres + sort by time]
↓
[Photo URLs → CDN → user browser]Stack ที่ใช้:
- Postgres (metadata)
- S3 (files)
- Redis (feed cache + sessions)
- Elasticsearch (search)
- Queue (Kafka / SQS)
- CDN (CloudFront)
24. Storage Cost Estimate — Real Numbers
ตัวอย่าง: 1M users, 100 photos each, avg 500KB:
Raw photos: 1M × 100 × 500KB = 50 TB
Thumbnails (3x): 1M × 100 × 50KB × 3 = 15 TB
Total: 65 TB
S3 cost: $0.023/GB × 65 TB = $1,495/month
CDN egress: $0.085/GB × estimate (varies)
DB (metadata):
- 100M rows × 500 bytes = 50 GB
- Postgres RDS db.r6g.xlarge: ~$200-500/month
Redis cache:
- 100GB hot data + replica
- ElastiCache: ~$500/month
Search:
- 100GB index
- Elasticsearch managed: ~$500/month
Total infrastructure: ~$3-5K/month for 1M usersInsight: estimate cost ก่อน commit — มันโตขึ้นเร็วกว่าที่คิด
25. Common Mistakes
| ❌ | ✅ |
|---|---|
| Choose DB by popularity, not use case | Match to query pattern |
| Sharding ตั้งแต่แรก | Vertical + cache first |
| 1 DB สำหรับทุกอย่าง | Polyglot — different tool per use case |
| ไม่มี cache | Cache layer สำหรับ read-heavy |
| Cache forever (no TTL) | Set reasonable TTL |
| Cache without invalidation | Invalidate on write |
| ลืม backup | Daily backup + test restore |
| ไม่มี read replicas | Add เมื่อ read-heavy |
| Strong consistency ทุกที่ | Use eventual ที่ OK |
| Auto-increment ID across shards | UUID หรือ Snowflake ID |
| ไม่จัดการ cache stampede | Locking หรือ stale-while-revalidate |
26. Cheat Sheet
สรุป data layer ทั้งบทเป็นแผ่นเดียวสำหรับเปิดดูเร็ว ๆ ตอนออกแบบจริง — เลือก storage ตามงาน (Postgres เป็น default, Redis cache, S3 ไฟล์ ฯลฯ) และลำดับการ scale ที่ควรทำตามขั้น เก็บไว้อ้างอิงตอน design:
Storage choice (decision):
- Default: Postgres
- Cache: Redis
- Files: S3
- Search: Elasticsearch / Postgres FTS
- Time-series: TimescaleDB / Prometheus
- Analytics: ClickHouse / BigQuery
- Vector: pgvector / Qdrant
Scale path (storage):
1. Single Postgres (vertical)
2. + Connection pool (PgBouncer)
3. + Read replicas
4. + Redis cache
5. + CDN (สำหรับ static)
6. + Sharding (ถ้า write-heavy)
7. + Specialized DB (search, analytics)
Cache rules:
- Cache reads, invalidate on write
- TTL = grace period สำหรับ invalidation miss
- Cache-aside pattern (most cases)
- Watch: stampede, hot keys, stale data
CAP:
- Most systems pick AP หรือ CP
- Choose based on user impact27. Checkpoint — ฝึกทำเอง
🛠️ Checkpoint 2.1 — Storage Choice
สำหรับแต่ละ scenario เลือก DB + อธิบายเหตุผล:
- User profile (1 row per user)
- Tweet feed (1M tweets/sec read)
- Product search
- Image storage
- Financial transactions
- Real-time leaderboard
- Application logs
- ML training data
- Recommendation embeddings
🛠️ Checkpoint 2.2 — Cache Strategy
ออกแบบ cache สำหรับ:
- User profile (read-heavy, low-update)
- Live sports score (frequent update)
- Trending hashtags (top 10 ทุกนาที)
- Product price (synced from external)
🛠️ Checkpoint 2.3 — Sharding Plan
Tweet table โต 10B rows:
- เลือก sharding strategy
- จัดการ cross-shard query (timeline of follower's tweets)
- Re-sharding strategy
🛠️ Checkpoint 2.4 — CAP Discussion
สำหรับแต่ละ scenario, CP หรือ AP?
- Banking transfer
- Twitter timeline
- DNS
- Stripe payment
- Real-time chat
- Google Doc collaboration
🛠️ Checkpoint 2.5 — Cost Estimate
ประมาณการ storage cost สำหรับ:
- Photo sharing app (10M users, 50 photos × 1MB)
- Chat app (100M users, 1000 messages × 1KB)
- Analytics (1B events/day, 500 bytes each, 1 year retention)
28. สรุปบท
✅ Start with Postgres — ครอบคลุม 99% ของ use case
✅ Polyglot persistence — different DB per need
✅ Connection pool (PgBouncer) ก่อน sharding
✅ Read replicas = easy read scale
✅ Sharding = hard write scale (last resort)
✅ Sharding strategy: hash-based + consistent hashing
✅ CAP: pick 2 of 3 (real systems: CP หรือ AP)
✅ Cache-aside = most common pattern
✅ Cache pitfalls: stampede, hot keys, stale, invalidation
✅ Redis Cluster สำหรับ distributed cache
✅ S3 + CDN สำหรับ files + static
✅ CQRS = separate read/write models สำหรับ complex apps
✅ Materialized views สำหรับ slow queries
✅ Event sourcing สำหรับ audit-heavy systems
✅ Backup + DR: define RPO/RTO, test restore
29. คำศัพท์เพิ่ม
| คำศัพท์ | ความหมาย |
|---|---|
| ACID | Atomicity, Consistency, Isolation, Durability |
| CAP | Consistency, Availability, Partition tolerance |
| PACELC | CAP + Latency vs Consistency ตอนปกติ |
| Sharding | แบ่ง data ไป DB หลายตัว |
| Partitioning | แบ่งภายใน DB เดียว |
| Replication | คัดลอก data ไป DB อื่น |
| Consistent hashing | hash ring — เพิ่ม shard ไม่ rehash ทุกอัน |
| Hot key | key ที่ access บ่อยมาก |
| Cache-aside | App check cache → DB ถ้า miss |
| Write-through | Write to cache + DB ในเวลาเดียว |
| Write-back | Write to cache → async write DB |
| Cache stampede | Hot key expire → ทุก request hit DB |
| TTL | Time To Live — เวลาที่ data อยู่ใน cache |
| LRU | Least Recently Used (eviction) |
| CQRS | Command-Query Responsibility Segregation |
| Materialized view | Pre-computed query result |
| Event sourcing | เก็บ events ไม่ใช่ state |
| RPO | Recovery Point Objective — max data loss |
| RTO | Recovery Time Objective — max downtime |
| Polyglot persistence | ใช้หลาย DB ในระบบเดียว |
| Data lake | Raw data + schema-on-read |
| Data warehouse | Structured + columnar (BI) |