Storage, replication và partitioning
Chọn datastore từ access patterns, invariants và yêu cầu vận hành; sau đó thiết kế replication, partitioning, indexes và lifecycle như một hệ thống thống nhất. SQL/NoSQL không phải quyết định theo slogan.
1. Storage choice: chọn theo workload, không theo nhãn SQL/NoSQL
Các chiều quyết định quan trọng gồm transaction/invariant, query/index, write/read pattern, data size, latency, consistency, retention, geo distribution và khả năng vận hành của team. Một relational database có thể đi rất xa khi kết hợp đúng indexes, replicas và partitioning; vì vậy đừng mặc định rằng “scale lớn” đồng nghĩa phải đổi sang NoSQL.
| Dimension | Câu hỏi cần trả lời | Ảnh hưởng tới lựa chọn |
|---|---|---|
| Transaction / invariant | Có cần atomic update nhiều bản ghi, uniqueness hoặc referential integrity không? | Càng nhiều invariant mạnh, càng cần boundary transaction rõ và tránh phân tán dữ liệu tùy tiện. |
| Query / index | Top query là point lookup, range scan, full-text, graph traversal hay analytical scan? | Access pattern quyết định data model và index; đừng model chỉ theo object domain. |
| Read / write pattern | Read-heavy, write-heavy, append-only hay update-in-place? | Read replica, index count, batching và compaction có trade-off khác nhau. |
| Latency / consistency | Cần strong consistency hay chấp nhận stale read? P99 latency mục tiêu là gì? | Routing read, quorum, cache và geo topology phụ thuộc các yêu cầu này. |
| Retention / geo / operations | Dữ liệu giữ bao lâu, ở region nào, backup/restore ra sao, team có vận hành được không? | Lifecycle, compliance, replication topology và operational burden phải được tính từ đầu. |
2. Replication: read scale và HA không đồng nghĩa zero data loss
Read replicas có thể tăng read capacity và hỗ trợ high availability, nhưng replica có thể lag. Vì vậy một request vừa ghi xong rồi đọc ngay từ replica có thể chưa thấy dữ liệu mới. Nếu yêu cầu read-your-writes, cần cơ chế routing hoặc session/version awareness để đọc từ nguồn đủ mới thay vì mặc định load-balance mọi read.
Replication không thay thế backup. Replica có thể sao chép cả lỗi logic, delete nhầm hoặc corruption. Failover cũng luôn có RPO/RTO, và khi nhiều node có khả năng trở thành writer cần nghĩ tới split-brain và fencing để chỉ một writer hợp lệ được quyền tiến hành ghi.
| Khái niệm | Ý nghĩa | Câu hỏi review |
|---|---|---|
| Replica lag | Replica chậm hơn primary/source of truth | Query nào cho phép stale read? Query nào phải đọc writer? |
| Read-your-writes | Client cần nhìn thấy write của chính mình ngay sau đó | Routing theo session, version/token hay sticky read được thực hiện ở đâu? |
| RPO | Mức dữ liệu có thể mất khi disaster/failover | Sync/async replication đang đáp ứng RPO nào? |
| RTO | Thời gian phục hồi chấp nhận được | Failover có tự động không, kiểm thử định kỳ không? |
| Fencing | Ngăn writer cũ tiếp tục ghi sau failover | Làm sao đảm bảo chỉ writer hiện tại còn quyền commit? |
3. Partitioning / sharding
Partitioning chia dataset theo một key hoặc rule để scale storage và throughput. Ba hướng phổ biến trong mental model của chương này là hash, range và directory. Không có chiến lược nào thắng trong mọi workload; key phải vừa có cardinality và distribution tốt, vừa hỗ trợ dominant queries hoặc ordering cần thiết.
| Strategy | Ưu điểm chính | Trade-off / rủi ro |
|---|---|---|
| Hash partitioning | Thường phân phối đều hơn khi key có cardinality tốt | Range query khó hơn vì các key gần nhau có thể nằm ở nhiều shard |
| Range partitioning | Giữ locality, phù hợp range scan/order theo key | Dễ hotspot nếu traffic dồn vào một range, ví dụ timestamp mới nhất |
| Directory-based | Mapping linh hoạt giữa logical key và shard | Directory/metadata trở thành dependency cần HA, cache và consistency rõ ràng |
3.1 Chọn partition key
- Cardinality: key có đủ nhiều giá trị để chia tải hay không.
- Distribution: traffic có đều hay một số key nhận phần lớn request.
- Query locality: dominant query có thể được phục vụ trong một shard hay phải fan-out.
- Ordering: nếu cần order theo tenant/user/time, partition key phải phản ánh scope ordering đó.
Cross-shard transaction và cross-shard query đều tăng cost. Khi một operation cần nhiều shard, latency tăng, failure surface rộng hơn, và transaction semantics khó giữ hơn. Vì vậy schema/data model nên cố gắng đặt các dữ liệu thường xuyên truy cập hoặc thay đổi cùng nhau vào cùng partition khi hợp lý.
4. Hotspot, skew và rebalance
Hotspot xuất hiện khi một partition nhận lượng request hoặc data vượt trội. Các pattern điển hình gồm celebrity key, tenant lớn hoặc time-based key khiến write mới dồn vào cùng một partition. Khi đó average throughput của cluster có thể vẫn thấp nhưng một shard đã chạm giới hạn.
Các biện pháp gồm salt/bucket key, isolate heavy tenant, adaptive partition hoặc cache. Trade-off quan trọng là sau khi trải một logical key ra nhiều bucket, read thường phải aggregate từ nhiều nơi và application logic trở nên phức tạp hơn.
| Pattern | Mitigation | Cost phải trả |
|---|---|---|
| Celebrity / hot key | Cache, salt/bucket, dedicated partition | Invalidation, fan-out read hoặc logic routing riêng |
| Heavy tenant | Isolate tenant lớn sang shard riêng | Placement và balancing phức tạp hơn |
| Time-based write hotspot | Bucket theo time + secondary dimension/hash | Range read phải query nhiều bucket rồi merge |
| Uneven growth | Adaptive split/repartition | Cần migration protocol và observability tốt |
4.1 Repartition / backfill an toàn
Repartition không chỉ là copy data. Khi hệ thống vẫn phục vụ traffic, cần protocol cho dual-read/write hoặc versioned routing, checkpoint tiến độ và reconciliation. Mục tiêu là biết record nào đã migrate, record nào chưa, write mới đi đâu, và cách phát hiện chênh lệch giữa old/new placement.
5. Indexes: tăng read, nhưng không miễn phí
Index giúp giảm cost truy vấn nhưng tăng write cost, storage footprint và maintenance. Mỗi index bổ sung có thể phải được update trong đường ghi, có thể làm backfill/index build nặng hơn và tiêu tốn cache/memory. Chỉ tạo index cho access pattern có giá trị rõ ràng; theo dõi index unused, write amplification và query plan.
5.1 Checklist thiết kế index
- Query nào thực sự cần index này và tần suất của nó là bao nhiêu?
- Index có hỗ trợ filter + sort theo thứ tự field phù hợp không?
- Chi phí write/storage tăng bao nhiêu?
- Index build/rebuild có ảnh hưởng production traffic không?
- Sau partitioning, index là local per shard hay cần global lookup?
6. Data lifecycle, retention và capacity
Lifecycle phải mô tả dữ liệu khi nóng, khi cũ và khi hết retention: archive, tier hoặc delete. Với storage engine dùng tombstone/compaction, việc delete logical chưa chắc giải phóng disk ngay. Với yêu cầu GDPR deletion, cần plan xuyên suốt primary data, replicas, indexes, caches, search systems, archive và backup policy thay vì chỉ xóa một row/document ở datastore chính.
Capacity planning không chỉ tính raw data. Phải cộng indexes, replicas, WAL/log, temporary migration/build overhead và headroom. Một dataset “1 TB” có thể cần nhiều hơn đáng kể trên disk sau khi nhân replication factor, index footprint và operational buffer.
7. End-to-end design checklist
- Storage choice: access pattern, invariants, consistency, retention, geo và team operations đã được ghi rõ.
- Replication: lag, read-your-writes, RPO/RTO, backup/restore và fencing có owner.
- Partitioning: key có cardinality/distribution tốt và dominant query không phải fan-out vô tội vạ.
- Hotspot: có metric per shard/key/tenant để phát hiện skew thay vì chỉ nhìn cluster average.
- Rebalance: backfill có version/checkpoint/reconciliation và rollback path.
- Indexes: mỗi index có workload justification và budget write/storage.
- Lifecycle: archive/tier/delete, tombstone/compaction và privacy deletion được tính vào design.
- Capacity: bao gồm data + indexes + replicas + WAL/log + headroom.
8. Câu hỏi tự kiểm tra
- Vì sao read replica có thể làm user không thấy dữ liệu vừa ghi, và cách xử lý read-your-writes là gì?
- Replication và backup giải quyết hai failure class khác nhau như thế nào?
- Khi nào hash partitioning phù hợp hơn range partitioning? Khi nào range tốt hơn?
- Tại sao partition theo timestamp dễ tạo hotspot?
- Salt/bucket key giúp write distribution nhưng làm read phức tạp hơn ra sao?
- Vì sao cross-shard transaction/query thường đắt hơn single-shard?
- Tại sao index có thể cải thiện read nhưng làm write và storage tốn hơn?
- Capacity model cần tính thêm những gì ngoài raw data?