Part 10 · System Design · 10.1.08

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.

Mental model: bắt đầu từ dữ liệu và invariants, xác định read/write pattern, consistency và latency; chỉ sau đó mới chọn storage engine, replication mode, partition key và index. Mọi tối ưu read thường đổi lại write cost, storage cost hoặc operational complexity.

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.

DimensionCâu hỏi cần trả lờiẢnh hưởng tới lựa chọn
Transaction / invariantCó 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 / indexTop 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 patternRead-heavy, write-heavy, append-only hay update-in-place?Read replica, index count, batching và compaction có trade-off khác nhau.
Latency / consistencyCầ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 / operationsDữ 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ĩaCâu hỏi review
Replica lagReplica chậm hơn primary/source of truthQuery nào cho phép stale read? Query nào phải đọc writer?
Read-your-writesClient 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?
RPOMức dữ liệu có thể mất khi disaster/failoverSync/async replication đang đáp ứng RPO nào?
RTOThời gian phục hồi chấp nhận đượcFailover có tự động không, kiểm thử định kỳ không?
FencingNgăn writer cũ tiếp tục ghi sau failoverLàm sao đảm bảo chỉ writer hiện tại còn quyền commit?
Common trap: “Có 3 replicas nên dữ liệu an toàn.” Replication chủ yếu giải quyết availability/read scaling; backup, point-in-time recovery và restore test vẫn là bài toán riêng.

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ínhTrade-off / rủi ro
Hash partitioningThường phân phối đều hơn khi key có cardinality tốtRange query khó hơn vì các key gần nhau có thể nằm ở nhiều shard
Range partitioningGiữ locality, phù hợp range scan/order theo keyDễ hotspot nếu traffic dồn vào một range, ví dụ timestamp mới nhất
Directory-basedMapping linh hoạt giữa logical key và shardDirectory/metadata trở thành dependency cần HA, cache và consistency rõ ràng

3.1 Chọn partition key

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.

PatternMitigationCost phải trả
Celebrity / hot keyCache, salt/bucket, dedicated partitionInvalidation, fan-out read hoặc logic routing riêng
Heavy tenantIsolate tenant lớn sang shard riêngPlacement và balancing phức tạp hơn
Time-based write hotspotBucket theo time + secondary dimension/hashRange read phải query nhiều bucket rồi merge
Uneven growthAdaptive split/repartitionCầ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.

prepare → backfill → dual-read/write hoặc versioned routing → checkpoint → reconciliation → cutover → cleanup
Review question: Nếu job migrate dừng ở 63%, hệ thống có biết chính xác resume từ đâu và tránh duplicate/lost update không? Nếu không, migration design chưa đủ production-ready.

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

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.

Capacity trap: đừng chạy cluster sát 100% disk/IOPS. Rebalance, compaction, index build, node loss và restore đều cần headroom đúng lúc hệ thống đang chịu sự cố.

7. End-to-end design checklist

8. Câu hỏi tự kiểm tra

  1. 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ì?
  2. Replication và backup giải quyết hai failure class khác nhau như thế nào?
  3. Khi nào hash partitioning phù hợp hơn range partitioning? Khi nào range tốt hơn?
  4. Tại sao partition theo timestamp dễ tạo hotspot?
  5. Salt/bucket key giúp write distribution nhưng làm read phức tạp hơn ra sao?
  6. Vì sao cross-shard transaction/query thường đắt hơn single-shard?
  7. Tại sao index có thể cải thiện read nhưng làm write và storage tốn hơn?
  8. Capacity model cần tính thêm những gì ngoài raw data?
Tài liệu chính thức từ source BK: Microsoft Azure Architecture Center — Choose a Data Store · AWS Well-Architected — Plan for Disaster Recovery