Skip to content

บทที่ 2 — Data Layer (Storage + Cache)

← บทที่ 1 | สารบัญ | บทที่ 3 →


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

บทนี้ต่อจาก บทที่ 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 acceptable

HNSW 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 workloads

Quantization — ลด 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 → CDC

Dan 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/month

3.4 Step 4: Transaction Isolation Levels — เลือกให้ถูกก่อน scale

ก่อนกระโดดไป sharding — เข้าใจ isolation levels ก่อน เพราะส่งผลต่อ correctness + performance ของ SQL DB ทุกตัว (DDIA Ch.7 อุทิศ ~50 หน้าให้เรื่องนี้):

Levelป้องกัน anomaly อะไรDefault ใน
Read Uncommittedไม่ป้องกันอะไร— (ไม่ค่อยใช้)
Read CommittedDirty readPostgres, 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 ReadPostgres 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 UPDATE

MVCC ใน 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 → bloat

Serializable 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 ทุก key

Strategy 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 ทุก query

4.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 consistency

5.3 Synchronous Replication

Write → wait for replica confirm → return success

✅ No replication lag — replica ทันที
❌ Slower writes (depend on slowest replica)
❌ Replica down → write block

Trade 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 caseConsistency ที่ใช้
Banking transferStrong
Social post visibleEventual (delay ไม่กี่วินาที OK)
User own profileRead-your-writes
View countEventual (approximate)
Cart ใน e-commerceStrong (ห้ามหาย items)
Like countEventual (approximate)
Inventory ตอน checkoutStrong (ห้าม oversell)

8. Caching — Why + How

ศัพท์ cache เยอะมาก — กางไว้ก่อนตรงนี้ (เดี๋ยวเจอตัวจริงในหัวข้อถัด ๆ ไป) ยังไม่ต้องจำ แค่รู้ว่า "ภาษาคน" แต่ละคำแปลว่าอะไร:

อังกฤษภาษาคนใช้ตอนไหน
cacheที่พักข้อมูลที่เร็วกว่า DB (อยู่ใน RAM)อยากอ่านข้อมูลเดิมซ้ำ ๆ ให้เร็ว
cache hit / missเจอใน cache / ไม่เจอ (ต้องไปถาม DB)วัดว่า cache ช่วยได้แค่ไหน
cache-asideapp เช็ค 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 herdcache หมดอายุพร้อมกัน → request นับพันถล่ม DB พร้อมกันต้องระวังกับ key ยอดนิยม
hot keykey ที่ถูกอ่านบ่อยมากผิดปกติ (เช่น โพสต์ดารา)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 invalidation

9.2 Read-Through

App → Cache (intermediate layer) → DB

App ไม่รู้ 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-in

9.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 DB

10. 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 writes

10.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 hybridDatabase (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 failure

13.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 clients

13.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 scale

13.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 GET

14.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 หลัง retention

15. Polyglot Persistence — หลาย DB ในระบบเดียว

15.1 หลักการ

ไม่มี DB ใดที่ "ดีทุกอย่าง"
→ ใช้ DB ที่เหมาะ per use case

15.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 embeddings

15.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 tracking

16.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จุดเด่น
PrometheusPull-based metrics, 15-day default retention
InfluxDBPush-based, time-series specialized
TimescaleDBPostgres extension — SQL ที่คุ้นเคย
ClickHouseColumnar, ultra fast aggregation
VictoriaMetricsPrometheus-compatible แต่เร็วกว่า
Mimir / Cortex / ThanosPrometheus 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 complexity

18. 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 projection

18.2 ตัวอย่าง

18.3 เมื่อไหร่ดี

✅ Read >> Write (10:1+)
✅ Complex read queries (search, aggregation)
✅ Different consistency needs (read OK eventually, write strong)

❌ Simple CRUD
❌ Small app

19. 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 ตามเวลา refresh

20. 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 = $120

20.2 ข้อดี-ข้อเสีย

✅ Full audit log (track ทุกการเปลี่ยนแปลง)
✅ Time-travel (state ที่เวลาใด ๆ ได้)
✅ ใช้กับ CQRS ดี
❌ Complex implementation
❌ Storage grows over time
❌ Replay slow ถ้า events เยอะ
   → Snapshot every N events

20.3 เมื่อไหร่ใช้

✅ ดี:
- Banking, finance (ต้องการ audit)
- Multiplayer game state
- Order tracking
- Compliance (regulatory)
- Need full history

21. 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 ground

22.2 Frequency

Critical data:    ทุกชั่วโมง (เช่น order, payment)
Important:        ทุกวัน (เช่น user data)
Archive:          ทุก week + month

22.3 Storage

Same region:       fast restore
Different region:  DR (disaster recovery)
Offline / air-gap: ransomware protection

22.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 = 1h

Match strategy

RPO 0 + RTO 0      → Multi-region sync (แพงสุด)
RPO 1h + RTO 4h    → Frequent backup + warm standby
RPO 24h + RTO 1day → Daily backup + restore on demand

23. 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 users

Insight: estimate cost ก่อน commit — มันโตขึ้นเร็วกว่าที่คิด


25. Common Mistakes

Choose DB by popularity, not use caseMatch to query pattern
Sharding ตั้งแต่แรกVertical + cache first
1 DB สำหรับทุกอย่างPolyglot — different tool per use case
ไม่มี cacheCache layer สำหรับ read-heavy
Cache forever (no TTL)Set reasonable TTL
Cache without invalidationInvalidate on write
ลืม backupDaily backup + test restore
ไม่มี read replicasAdd เมื่อ read-heavy
Strong consistency ทุกที่Use eventual ที่ OK
Auto-increment ID across shardsUUID หรือ Snowflake ID
ไม่จัดการ cache stampedeLocking หรือ 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 impact

27. 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. คำศัพท์เพิ่ม

คำศัพท์ความหมาย
ACIDAtomicity, Consistency, Isolation, Durability
CAPConsistency, Availability, Partition tolerance
PACELCCAP + Latency vs Consistency ตอนปกติ
Shardingแบ่ง data ไป DB หลายตัว
Partitioningแบ่งภายใน DB เดียว
Replicationคัดลอก data ไป DB อื่น
Consistent hashinghash ring — เพิ่ม shard ไม่ rehash ทุกอัน
Hot keykey ที่ access บ่อยมาก
Cache-asideApp check cache → DB ถ้า miss
Write-throughWrite to cache + DB ในเวลาเดียว
Write-backWrite to cache → async write DB
Cache stampedeHot key expire → ทุก request hit DB
TTLTime To Live — เวลาที่ data อยู่ใน cache
LRULeast Recently Used (eviction)
CQRSCommand-Query Responsibility Segregation
Materialized viewPre-computed query result
Event sourcingเก็บ events ไม่ใช่ state
RPORecovery Point Objective — max data loss
RTORecovery Time Objective — max downtime
Polyglot persistenceใช้หลาย DB ในระบบเดียว
Data lakeRaw data + schema-on-read
Data warehouseStructured + columnar (BI)

← บทที่ 1 | บทที่ 3 → Communication Patterns