Database Sharding
Xem thửKhi mua máy mạnh hơn không còn cứu được nữa
Sau vài năm, bảng orders của gdev-shop đã nặng 3 TB. Primary PostgreSQL chạy trên máy lớn nhất mà nhóm có thể mua, ổ đĩa gần đầy, và vào giờ cao điểm, số thao tác ghi đã chạm trần I/O. Nhóm đã làm đúng mọi thứ ở các bước rẻ hơn: index đầy đủ, cache, replica đọc, đưa việc ghi không gấp vào hàng đợi. Replica không giúp được vì vấn đề là ghi và dung lượng, mà mọi ghi vẫn phải đi qua một primary.
Đây là lúc bước cuối của thang mở rộng xuất hiện: chia dữ liệu ra nhiều database, gọi là sharding.
Sharding là gì?
Sharding là kỹ thuật chia các dòng của cùng một bảng ra nhiều database độc lập (gọi là shard), mỗi shard giữ một phần dữ liệu và chạy trên máy riêng. Mỗi shard thường tự có replica để chịu lỗi. Vì mỗi shard nhận ghi cho phần dữ liệu của nó, năng lực ghi và dung lượng tăng theo số shard.
Hãy phân biệt bốn khái niệm hay bị lẫn:
| Khái niệm | Làm gì | Giải quyết | Không giải quyết |
|---|---|---|---|
| Replication | Sao toàn bộ dữ liệu sang nhiều máy | Sẵn sàng, tải đọc | Ghi và dung lượng của một máy |
| Partitioning | Chia một bảng thành nhiều phần (có thể trong cùng một server) | Quản lý, tốc độ truy vấn, dọn dữ liệu cũ | Không tự mở rộng qua nhiều máy |
| Sharding | Chia dữ liệu ra nhiều server, mỗi server giữ một phần | Ghi và dung lượng vượt một máy | Tính sẵn sàng của từng shard (cần replica), truy vấn và giao dịch xuyên shard khó hơn |
| Backup | Chụp dữ liệu để khôi phục | Lỗi con người, hỏng dữ liệu | Gián đoạn ngắn, mở rộng |
Quan hệ giữa chúng: sharding là partitioning trải qua nhiều máy, và mỗi shard vẫn nên được replicate và backup.
Cách hoạt động
Ba thành phần cốt lõi:
- Shard key (khóa chia): cột dùng để quyết định dòng nằm ở shard nào, ví dụ
customer_id. - Hàm ánh xạ: từ giá trị khóa suy ra shard, ví dụ
hash(customer_id) mod 4. - Lớp định tuyến (router): nằm trong ứng dụng, trong một proxy, hoặc trong chính database, nhận truy vấn và đưa đến đúng shard.
Cách đọc hình
Luồng của một truy vấn có chứa shard key, ví dụ "lấy các đơn của khách 1042":
- Ứng dụng gửi truy vấn qua router.
- Router băm
customer_id, tínhhash(1042) mod 4để biết dữ liệu của khách này nằm ở shard nào, giả sử Shard 2. - Truy vấn chỉ được gửi đến đúng một shard, và kết quả quay lại ứng dụng.
Đây là kịch bản lý tưởng: truy vấn chạm đúng một shard nên nhanh và tải được chia đều. Mỗi shard chỉ xử lý khoảng một phần tư lượng ghi, và dữ liệu mỗi nơi chỉ bằng một phần tư.
Kịch bản xấu: truy vấn không có shard key, ví dụ "đơn nào có tổng tiền lớn hơn 10 triệu". Router không biết đơn nằm ở đâu, nên gửi đến cả bốn shard rồi gộp kết quả (scatter-gather), chậm hơn và tốn tài nguyên gấp bốn lần. Vì vậy chọn shard key khớp truy vấn chính là quyết định quan trọng nhất của sharding.
Bottleneck: shard nóng (một shard nhận nhiều hơn các shard khác) và router nếu không được nhân bản. Khi lỗi: một shard chết thì chỉ một phần dữ liệu không truy cập được (khoảng 25% khách), chứ không phải tất cả, miễn là mỗi shard có replica để thăng cấp.
Đang tải Animation…
Đang tải Animation…
Đang tải Animation…
Đang tải Animation…
Chọn shard key: quyết định khó đổi nhất
Shard key tốt cần:
- Phân bố đều: nhiều giá trị khác nhau, không có giá trị nào chiếm tỷ lệ lớn bất thường.
- Khớp với truy vấn chính: phần lớn truy vấn quan trọng nên chứa shard key để chạm đúng một shard.
- Ổn định: giá trị không đổi theo thời gian, vì đổi khóa nghĩa là phải di chuyển dòng sang shard khác.
Ví dụ:
customer_id: hợp với "xem đơn của tôi", phân bố khá đều. Nhưng nếu có một khách là đại lý đặt hàng gấp nghìn lần khách thường, shard của họ thành shard nóng.created_at(thời gian tạo): tệ nếu chia theo khoảng thời gian, vì mọi ghi mới đều rơi vào shard của thời điểm hiện tại, tức một shard nhận toàn bộ ghi, trong khi các shard cũ nằm rảnh.country: phân bố lệch mạnh (một vài nước chiếm phần lớn), dễ tạo shard nóng.
Cái giá của sharding
- Truy vấn xuyên shard: JOIN giữa dữ liệu nằm ở các shard khác nhau khó và chậm. Thường phải đặt các bảng liên quan cùng shard key (ví dụ
ordersvàorder_itemscùng chia theocustomer_id) hoặc chấp nhận gộp ở ứng dụng. - Giao dịch xuyên shard: không còn một
COMMITduy nhất cho dữ liệu ở nhiều shard. Cần các kỹ thuật phức tạp hơn, sẽ học ở chương hệ phân tán. - Tạo ID duy nhất: số tự tăng không còn đủ vì mỗi shard sinh riêng. Cần cơ chế sinh ID khác.
- Chia lại (resharding): khi thêm shard, một số dữ liệu phải di chuyển. Với hàm
mod Nđơn giản, đổi từ 4 lên 5 shard làm khoảng 80% khóa chuyển chỗ (chỉ khoảng 20% giữ nguyên), nên di chuyển dữ liệu rất lớn. Đây là động lực của consistent hashing, được học ở chương Load Balancing, và của các cách chia theo khoảng có thể tách dần. - Độ phức tạp vận hành: nhiều database cần sao lưu, nâng cấp, giám sát, đồng bộ schema.
Khi nào mới nên shard?
Sharding là bước cuối cùng trong thang mở rộng. Hãy shard khi đã tối ưu truy vấn và index, đã cache, đã dùng replica đọc, đã scale up hợp lý, và số đo chỉ ra ghi hoặc dung lượng của một máy thực sự là giới hạn không thể vượt qua. Một số bước thay thế rẻ hơn cần thử trước: lưu trữ dữ liệu cũ sang kho khác (archive), tách bảng nóng ra database riêng theo nghiệp vụ, hoặc dùng partitioning trong cùng một server.
Những hiểu lầm phổ biến
- "Sharding và replication là một." Replication sao toàn bộ dữ liệu, sharding chia dữ liệu. Sharding vẫn cần replication cho từng shard.
- "Có sharding thì hệ thống tự scale vô hạn." Chỉ khi shard key phân bố đều và truy vấn chạm đúng một shard. Shard nóng và truy vấn xuyên shard làm mất lợi ích.
- "Cứ shard sớm cho chắc." Cái giá về phức tạp rất lớn và khó đảo ngược. Nên để cuối.
- "Có thể đổi shard key sau." Đổi shard key tương đương di chuyển phần lớn dữ liệu.
Must Know, Good to Know, Advanced
- Must Know: định nghĩa; phân biệt với replication, partitioning, backup; shard key tốt; truy vấn chạm một shard so với scatter-gather; sharding là bước cuối.
- Good to Know: shard nóng, JOIN và giao dịch xuyên shard, vấn đề resharding với mod N.
- Advanced: consistent hashing, tách shard động, directory-based sharding. Giới thiệu ở các lesson sau.
Tự kiểm tra
- Với bảng
orders, bạn chọncustomer_idhaycreated_atlàm shard key? Lập luận bằng truy vấn chính và phân bố ghi. - Truy vấn "đơn có tổng tiền lớn hơn 10 triệu" chạy thế nào trong hệ thống 8 shard, và cách làm nào để tránh?
- Vì sao đổi từ 4 lên 5 shard bằng
mod Nlàm di chuyển nhiều dữ liệu như vậy?
Đáp án gợi ý
- Chọn
customer_id. Truy vấn chính thường lọc theo khách ("đơn của tôi"), nên mọi đơn của một khách nằm trong một shard và truy vấn chỉ chạm một shard. Phân bố ghi cũng tương đối đều giữa nhiều khách.created_atlàm mọi đơn mới dồn vào shard mới nhất (shard nóng), và truy vấn "đơn của một khách" phải quét nhiều shard. - Truy vấn lọc theo tổng tiền không chứa shard key, nên phải gửi tới cả 8 shard rồi gộp kết quả (scatter-gather): chậm và tốn tài nguyên theo số shard. Cách tránh: dùng một kho đọc riêng cho báo cáo (replica tổng hợp, kho phân tích) thay vì chạy trên các shard sản xuất, hoặc duy trì bảng tổng hợp.
- Với
mod N, vị trí của gần như mọi khóa phụ thuộc vào N. Đổi từ 4 lên 5 làm kết quảhash(key) mod Nthay đổi cho phần lớn khóa (khoảng bốn phần năm), nên rất nhiều dữ liệu phải chuyển shard. Consistent hashing sinh ra để giảm đúng lượng di chuyển này.
Nếu chỉ nhớ 3 điều
- Sharding chia dữ liệu ra nhiều server để vượt giới hạn ghi và dung lượng của một máy; replication không làm được điều này.
- Shard key quyết định tất cả: phân bố đều, khớp truy vấn chính, ổn định theo thời gian.
- Sharding là bước cuối vì cái giá lớn: truy vấn và giao dịch xuyên shard, resharding, vận hành phức tạp.