System Design — Bài 8: Scale dữ liệu: replication, partition và sharding
Dữ liệu nhiều chưa tự động có nghĩa phải shard. Có hệ thống cần partition theo tháng để xóa lịch sử, có hệ thống chỉ cần thêm index, và có hệ thống thực sự vượt tài nguyên một node. Bài này giúp phân biệt các nhu cầu trước khi nhận thêm chi phí định tuyến và giao dịch phân tán.
Khóa học System Design: mục lục 21 bài — bạn đang đọc bài 8.
Đo capacity của một database
Không tồn tại một con số QPS chung cho mọi database. Một truy vấn theo khóa đã có trong bộ nhớ khác hoàn toàn truy vấn tổng hợp nhiều bảng. Ghi một dòng cũng khác giao dịch cập nhật nhiều dòng có tranh chấp.
Benchmark phải mô tả kích thước dữ liệu, tỉ lệ đọc ghi, phân bố khóa, index, mức durability và replication. Tăng tải từng bước, quan sát throughput hoàn thành cùng p95, p99, tỉ lệ lỗi và hàng đợi. Capacity hữu ích là mức còn đạt mục tiêu, không phải đỉnh throughput khi latency đã không chấp nhận được.
Sau đó thử lúc mất một node hoặc dữ liệu không còn vừa bộ nhớ. Nếu chỉ đo dữ liệu nhỏ, cache nóng và khóa phân bố đều, kết quả sẽ lạc quan hơn workload có hot key thực tế.
Thang leo giải pháp — đi từ rẻ tới đắt
Bắt đầu từ query và index: có đang đọc thừa hoặc gọi lặp không? Tiếp theo xem dữ liệu cũ có cần nằm trên đường truy vấn nóng không, rồi cân nhắc cache cho kết quả phù hợp. Những thay đổi này có thể giải quyết bài toán mà chưa cần thay kiến trúc.
Replica thường giúp chia tải đọc và hỗ trợ độ khả dụng. Nhưng nếu ghi vẫn đi qua một primary, thêm replica không tự tăng khả năng xử lý ghi của primary. Đọc từ replica có thể thấy dữ liệu cũ, nên luồng vừa ghi rồi đọc cần chính sách phù hợp.
Đây là thứ tự cân nhắc, không bắt buộc áp dụng mọi bậc. Khi giới hạn thực sự là dung lượng hoặc tốc độ ghi của một node, sharding mới trở thành ứng viên rõ ràng. Phương án nào cũng cần kiểm chứng sau thay đổi.
Khi nào nên partition, khi nào nên shard
Partition chia dữ liệu thành các phần logic theo một quy tắc. Nhiều partition có thể vẫn nằm trên một máy. Lợi ích có thể là bỏ qua phần không liên quan khi truy vấn, xóa dữ liệu cũ hoặc bảo trì dễ hơn. Sharding đưa các phần dữ liệu sang nhiều node để phân phối tài nguyên.
Ví dụ lịch sử theo tháng có thể partition để bỏ tháng cũ nhanh, dù tổng tải vẫn vừa một server. Ngược lại, khi một node không còn đủ CPU, I/O hoặc dung lượng sau tối ưu, ta mới cân nhắc chia sang nhiều node.
Chi phí cần tính trước là định tuyến, rebalance, backup, unique constraint và giao dịch xuyên shard. Một query không có shard key có thể phải hỏi nhiều nơi. Khả năng hỗ trợ cụ thể tùy hệ quản trị; chia dữ liệu không tự bảo đảm truy vấn nhanh hơn.
Range hay Hash, và chọn partition key
Range chia theo khoảng nên tự nhiên với truy vấn thời gian hoặc giá trị liên tiếp. Điểm yếu là ghi mới có thể dồn vào khoảng cuối. Hash rải khóa qua nhiều phần tốt hơn trong nhiều trường hợp, nhưng truy vấn khoảng sẽ khó gom dữ liệu hơn.
Một khóa tốt nên giúp phần lớn thao tác đi tới ít partition, có đủ độ phân biệt và không tạo nhóm quá lớn. Cardinality cao vẫn chưa đủ: 1 triệu user nhưng một user tạo phần lớn traffic thì khóa theo user vẫn có hot spot.
Vì vậy phải xét cả cách dữ liệu phân bố và cách thao tác phân bố. Với dữ liệu theo thời gian có thể kết hợp một khóa nghiệp vụ và time bucket, nhưng cách kết hợp cần phù hợp truy vấn. Không có lựa chọn hash hay range luôn thắng.
Bốn bài tập partition key
Với đơn hàng, nếu thường đọc lịch sử theo khách, user_id là ứng viên hợp lý. Nhưng xem chi tiết chỉ có order_id thì cần tìm được shard, chẳng hạn mang thêm thông tin định tuyến hoặc có lookup phù hợp.
Với chuyển tiền, một giao dịch liên quan cả bên gửi và bên nhận. Shard theo một bên làm truy vấn phía kia hoặc transaction trở nên khó hơn. Khi tải còn đáp ứng được, một database cùng index hai chiều là phương án đáng cân nhắc.
Với metric thiết bị, cần vừa query theo thiết bị vừa giới hạn thời gian và retention, nên khóa thiết bị kết hợp bucket thời gian có ý nghĩa. Với chat, channel là điểm gom tự nhiên, nhưng channel lớn cần giới hạn kích thước partition. Trong mỗi bài, hãy nói cả truy vấn được lợi và truy vấn phải trả giá.
Làm dứt điểm, và các lựa chọn thay thế
Số shard cần dựa vào capacity đã đo, tăng trưởng dự kiến và cách rebalance. Có thể dùng logical shard để tách định tuyến dữ liệu khỏi số máy vật lý, giúp thay đổi máy mà không phải đổi toàn bộ quy tắc ánh xạ.
Database tự phân vùng hoặc middleware có thể giảm phần ứng dụng phải tự làm, nhưng không xóa độ phức tạp. Cần hiểu cách failover, giao dịch xuyên node, truy vấn phụ và quy trình nâng cấp.
Đặc biệt, contention là nhiều thao tác tranh cùng dữ liệu. Thêm node không làm một tài khoản cực nóng tự hết tranh chấp. Cần cân nhắc cập nhật có điều kiện, khóa dòng hoặc tuần tự hóa theo key; optimistic retry dưới contention cao có thể tạo thêm tải. Scale dữ liệu và kiểm soát tranh chấp là hai việc liên quan nhưng khác nhau.
Consistent Hashing — vòng hash & virtual node
Với hash của key lấy dư theo số node, thêm node làm mẫu số thay đổi và nhiều key đổi vị trí. Consistent hashing đặt key và các điểm đại diện node trên cùng một vòng. Theo quy ước duyệt thuận chiều kim đồng hồ, key thuộc node đầu tiên gặp sau vị trí hash của nó.
Khi thêm một điểm node, chủ yếu khoảng liền trước điểm đó đổi chủ. Với phân bố cân bằng, thêm node thứ N cộng một làm dịch chuyển trung bình khoảng một phần N cộng một số key. Virtual node cho mỗi máy nhiều điểm trên vòng để giảm lệch phân bố và phản ánh khác biệt capacity.
Đây chỉ là cơ chế ánh xạ. Di chuyển dữ liệu an toàn, sao chép và xử lý request trong lúc chuyển vẫn cần thiết kế. Virtual node cũng không tự tách một key quá nóng. Redis Cluster dùng không gian slot cố định, một cơ chế liên quan nhưng không phải vòng consistent hashing vừa mô tả.
Kiểm tra shard key bằng hai đường truy vấn
Order shard key: hash(user_id)
GET /users/42/orders -> định tuyến tới một shard
GET /orders/9001 -> cần user_id hoặc lookup định tuyến
Payment: liên quan sender_id VÀ receiver_id
-> Bắt đầu một DB + index hai chiều nếu capacity đáp ứng
-> Khi shard: mô tả giao dịch xuyên shard và recovery
Thêm replica: chia tải đọc (có lag)
Thêm shard: chia dữ liệu/tài nguyên (có chi phí phối hợp)Cardinality cao chưa đảm bảo tải đều. Một user hoặc một channel có thể chiếm phần lớn traffic. Consistent hashing giảm lượng key phải chuyển khi thay đổi node, nhưng không tự chia được một hot key.
Thuật ngữ cần nhớ
QPS — Queries Per Second: Số truy vấn mỗi giây; trong bài ước lượng cần nói rõ đang đếm truy vấn dữ liệu hay lượt gọi đọc. Trung bình và đỉnh là hai giá trị khác nhau.
TPS — Transactions Per Second: Số giao dịch mỗi giây; phải định nghĩa ranh giới giao dịch nghiệp vụ hoặc giao dịch dữ liệu trước khi so sánh.
SLO — Service Level Objective: Mục tiêu mức dịch vụ, ví dụ 99,9% request thành công hoặc 99% request dưới 250 ms trong cửa sổ đo xác định.
Bài tập tự thực hành
Lịch sử giao dịch cần tra theo cả hai tài khoản; chọn partition key.
Gợi ý kiểm tra lời giải
Nêu đánh đổi truy vấn hai chiều; có thể giữ một DB và hai index nếu capacity đáp ứng.
Tự kiểm tra sau bài học
Nêu query nào đi một shard và query nào phải scatter.
Định nghĩa hành vi đọc sau ghi khi replica chậm.
Có kế hoạch rebalance, backup và xử lý khóa nóng.
Tiếp tục lộ trình
Bài trước — Chọn database từ access pattern: ACID, index, B+Tree và LSM
Bài tiếp — Caching và Redis: chiến lược ghi, rate limit và các bẫy thường gặp
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.