1. Scale theo chiều dọc vs ngang
Vertical scaling (scale up)
Mua server mạnh hơn — CPU nhiều core hơn, RAM nhiều hơn, NVMe nhanh hơn.
- Ưu: đơn giản, không thay đổi app, không trade-off CAP.
- Nhược: trần (server lớn nhất ~1TB RAM), giá phi tuyến (server 2× mạnh có thể đắt 5×), single point of failure.
Postgres 1 node có thể xử lý hàng terabyte data + 50k QPS — đừng vội scale ngang trước khi vertical đến giới hạn.
Horizontal scaling (scale out)
Thêm nhiều server, chia tải.
- Ưu: scale gần như vô hạn (Google quản tỉ tỉ row), HA tự nhiên.
- Nhược: phức tạp (replication, sharding, distributed transaction), CAP trade-off, app phải biết shard.
Thứ tự thực dụng: Vertical → Read Replica → Caching → Sharding. Đa số app dừng ở bước 2-3.
2. Replication — sao chép dữ liệu
Mục đích chính:
- High Availability (HA) — primary chết, promote replica làm primary mới.
- Read scaling — phân tán read sang nhiều replica.
- Backup live — replica có thể dùng làm backup không cản primary.
- Geo-distribution — replica ở data center xa user → low latency.
2.1. Cách Postgres replicate
Postgres dùng WAL streaming:
- Primary ghi mọi thay đổi vào WAL (như đã học Ch6).
- Replica connect TCP, đọc WAL stream.
- Replica apply WAL vào data file local → state đồng bộ.
# postgresql.conf trên primary:
wal_level = replica # hoặc 'logical' để hỗ trợ logical replication
max_wal_senders = 10
archive_mode = on # archive WAL cho PITR
archive_command = 'cp %p /archive/%f'
# pg_hba.conf:
host replication replicator 10.0.0.0/8 scram-sha-256
# Trên replica, copy base backup từ primary:
pg_basebackup -h primary-host -D /var/lib/pg/data -U replicator -X stream -P
# postgresql.conf trên replica:
primary_conninfo = 'host=primary-host user=replicator password=...'
hot_standby = on # cho phép read trên replica
2.2. Logical vs Physical Replication
| Physical (streaming) | Logical | |
|---|---|---|
| Cách hoạt động | Replicate WAL bytes y nguyên | Replicate "changes" qua publication/subscription |
| Granularity | Toàn DB | Bảng/schema cụ thể |
| Cross-version | Không (cần cùng version) | Có (Postgres 12 → 16) |
| Read-only replica | Yes (hot standby) | Replica có thể write |
| Use case | HA, full read scale | Migration, multi-region partial, ETL |
3. Replication Topologies
3.1. Primary-Replica (Master-Slave)
Phổ biến nhất. Đơn giản, đa số use case OK.
3.2. Cascading Replication
Giảm tải WAL stream trên primary khi có nhiều replica. Postgres 9.2+ hỗ trợ.
3.3. Multi-Region
4. Sync / Async / Semi-Sync
4.1. Postgres setup
# postgresql.conf trên primary:
# Async (default) — không cần config gì thêm
# Sync — chờ ít nhất 1 replica ack:
synchronous_standby_names = 'FIRST 1 (replica1, replica2)'
synchronous_commit = on
# Quorum: chờ 2 trong 3 replica:
synchronous_standby_names = 'ANY 2 (replica1, replica2, replica3)'
4.2. Trade-off
| Async | Sync | |
|---|---|---|
| Latency | Thấp | Cao (chờ replica) |
| Throughput | Cao | Giới hạn bởi replica chậm nhất |
| Data loss khi primary crash | Có thể (RPO > 0) | Không (RPO = 0) |
| Khi nào dùng | Web app thường | Tài chính, đặt vé |
5. Read Replicas — sử dụng đúng
App route read sang replica, write sang primary:
// Pattern thực dụng (PgBouncer, ProxySQL, hoặc app-level):
const writeDb = new Pool({ host: 'primary-host', ... });
const readDb = new Pool({ host: 'replica-host', ... });
async function getUser(id) {
return await readDb.query('SELECT * FROM users WHERE id = $1', [id]);
}
async function updateUser(id, data) {
return await writeDb.query('UPDATE users SET ... WHERE id = $1', [id, ...]);
}
5.1. Replication Lag — vấn đề thực tế
Replica luôn chậm hơn primary vài ms đến vài giây (tùy load). Async lag có thể giây/phút khi primary cao tải.
-- Trên replica, kiểm tra lag:
SELECT now() - pg_last_xact_replay_timestamp() AS lag;
-- → INTERVAL '00:00:00.450' (450ms)
5.2. Read-your-writes problem
User POST /comment, app ghi primary thành công, redirect → GET /post/X. Read replica chưa kịp apply → user thấy comment chưa có. Bug UX.
Cách giải:
- Sticky session — sau write, route N giây tiếp theo của session đó về primary.
- Replication lag aware — query "có lag < 100ms?" trước, nếu cao thì fall back primary.
- Synchronous replication — đảm bảo không lag → đắt.
- Cache write trong session — UI optimistic.
5.3. Stale read tolerance
Phân loại read theo "tolerance lag":
- OK stale 1 phút: dashboard, recommendation, analytics → replica.
- OK stale < 1s: feed, profile → replica.
- NEED fresh: balance, inventory check trước checkout, "your order" page → primary.
6. Failover — primary chết, làm gì?
6.1. Manual failover
# Trên replica đã chọn:
pg_ctl promote -D /var/lib/postgresql/data
# Replica trở thành primary mới, chấp nhận writes.
# App đổi connection string sang IP của primary mới
# Replica khác cần re-config để follow primary mới
6.2. Automatic failover — Patroni / Stolon / pg_auto_failover
Tools này tự phát hiện primary down (qua etcd/consul), promote replica, update DNS/HAProxy. Toàn bộ ~15-60 giây.
6.3. Split-brain — bug nguy hiểm
Nếu network partition giữa primary và monitor → 2 node nghĩ mình là primary → cả 2 nhận writes → diverge.
Cách giải: fencing — kill primary cũ trước khi promote. Hoặc dùng quorum-based consensus như Raft (Patroni dùng etcd Raft).
6.4. RTO & RPO
- RTO (Recovery Time Objective) — bao lâu để hệ trở lại: failover Patroni ~30s, manual ~5-15 phút, restore từ backup ~giờ.
- RPO (Recovery Point Objective) — bao nhiêu data có thể mất: sync replication = 0, async = vài giây, backup hourly = 1 giờ.
7. Multi-Master / Active-Active
Nhiều node cùng chấp nhận writes. Phức tạp hơn nhiều primary-replica vì cần resolve conflict.
- BDR (Bi-Directional Replication) trên Postgres — commercial.
- Cassandra, DynamoDB — multi-master native.
- CockroachDB, Spanner — distributed, mỗi node nhận write nhưng có consensus protocol.
7.1. Conflict resolution strategies
- Last-Write-Wins (LWW) — timestamp lớn nhất thắng. Đơn giản, có thể mất data.
- Vector clock — theo dõi version đa node, app tự merge.
- CRDT (Conflict-free Replicated Data Type) — kiểu dữ liệu tự merge tự động (Counter, OR-Set).
- Quorum write — chờ majority ack trước khi commit.
Kinh nghiệm: tránh multi-master khi có thể. 99% app không cần. Multi-region read replicas + 1 primary đủ.
10. Consistent Hashing — giảm đau khi resharding
Vấn đề với hash(key) % N: đổi N (thêm/bớt shard) → 90% data phải di chuyển.
Consistent hashing giải bằng vòng tròn hash:
10.1. Lợi ích
- Thêm node mới → chỉ ~1/N data di chuyển (không phải 90%).
- Bớt node → chỉ 1/N data tái phân phối.
10.2. Virtual nodes (vnodes)
Vấn đề: với N nhỏ, phân bố không đều. Giải bằng virtual nodes — mỗi node thật map vào nhiều điểm trên vòng. Cassandra mặc định 256 vnode/node.
DynamoDB, Redis Cluster, Cassandra, Riak đều dùng consistent hashing.
11. Resharding — khi N đổi
Resharding là nghiệp vụ phức tạp nhất. Lý do cần:
- Thêm shard vì data tăng.
- Bớt shard vì chi phí.
- Hot shard cần split.
- Tenant lớn cần shard riêng (split off).
11.1. Approach: split & migrate online
- Setup new shard (empty).
- Bật dual-write: app ghi cả old shard và new shard cho key thuộc range mới.
- Backfill old data: copy old → new (bg job).
- Verify data parity (checksum).
- Cut over reads: app đọc new shard.
- Tắt writes vào old shard cho key đó.
- Cleanup old shard data.
Quá trình có thể mất ngày/tuần cho TB data. Mỗi bước có rủi ro.
11.2. Pre-sharding
Mẹo: ngay từ đầu, tạo nhiều shard logical (vd: 4096 shard) trên ít node thật. Khi cần scale, di chuyển shard logical sang node mới — không cần đổi shard map (chỉ đổi shard → node mapping).
Citus (Postgres extension), Vitess (MySQL), MongoDB sharding đều dùng pattern này.
12. Bài tập
- Setup Postgres primary + 1 replica với streaming replication trên Docker. Demo write trên primary, read trên replica.
- Mô phỏng replication lag bằng
SELECT pg_sleep(...). Đo lag quapg_last_xact_replay_timestamp(). - Trình bày plan failover cho hệ Postgres + 2 replica. Chọn tool (Patroni / pg_auto_failover) và lý giải.
- Cho 100M user. Đề xuất chiến lược shard + count shard ban đầu. Tại sao chọn hash thay vì range?
- Vấn đề "celebrity user" trong Twitter (1 user 100M follower) gây hot shard. Giải pháp?
- Khi nào tách 1 monolithic Postgres ra microservice DB (database per service)? Khi nào KHÔNG?
13. Quiz
Quiz cuối Chương 10
Sự khác biệt chính giữa Replication và Sharding:
Async replication trade-off:
"Read your own writes" với async replica đối mặt với:
Hash sharding với hash(id) % N có vấn đề gì khi đổi N?
Range sharding pitfall lớn nhất:
Split-brain trong replication là gì?
Vấn đề lớn nhất sau khi shard:
Pre-sharding (4096 shard logical trên ít node thật) có lợi gì?
Hoàn thành Chương 10. Tiếp theo: Chương 11 — Caching, Search, OLTP vs OLAP →