Tại sao cần sử dụng cụm phân mảnh (Sharded Cluster)?
MongoDB nổi bật với ba ưu điểm chính: mô hình linh hoạt, tính sẵn sàng cao và khả năng mở rộng. Trong đó, dữ liệu dạng tài liệu JSON hỗ trợ mô hình linh hoạt; các bộ sao chép (Replica Set) đảm bảo tính sẵn sàng; còn cụm phân mảnh (Sharded Cluster) là nền tảng cho khả năng mở rộng ngang (scale-out), giúp giải quyết những giới hạn về hiệu suất và dung lượng khi ứng dụng phát triển.
Khi hệ thống gặp phải một trong các tình huống sau, việc áp dụng Sharded Cluster là cần thiết:
- Dữ liệu vượt quá dung lượng lưu trữ của một máy đơn.
- Bộ nhớ RAM không đủ để giữ toàn bộ dữ liệu hoạt động thường xuyên, dẫn đến nhiều thao tác đọc ghi phải truy cập ổ cứng, làm giảm hiệu năng.
- Yêu cầu ghi dữ liệu (IOPS) vượt quá khả năng xử lý của một node duy nhất.
Bằng cách phân phối dữ liệu qua nhiều shard, Sharded Cluster cho phép hệ thống mở rộng quy mô theo chiều ngang, từ đó nâng cao hiệu suất và độ tin cậy.
Cấu trúc của Sharded Cluster
Một cụm phân mảnh bao gồm ba thành phần chính:
- Shard: Lưu trữ dữ liệu thực tế. Mỗi shard có thể là một máy chủ MongoDB độc lập hoặc một replica set để đảm bảo tính sẵn sàng.
- Mongos: Là cổng truy cập vào cụm. Tất cả các yêu cầu từ ứng dụng đều đi qua mongos, nó chịu trách nhiệm định tuyến, phân phối và tổng hợp kết quả mà người dùng không cần biết chi tiết nội bộ.
- Config Server: Lưu trữ siêu dữ liệu (metadata) của toàn bộ cụm như cấu hình phân mảnh, vị trí chunk, trạng thái cân bằng... Từ phiên bản 3.2 trở đi, config server có thể được triển khai dưới dạng replica set (CSRS), tăng độ tin cậy và khả năng phục hồi.
Chiến lược phân bố dữ liệu
Sharded Cluster hỗ trợ phân mảnh dữ liệu theo từng collection dựa trên một trường được chọn gọi là shard key. Hai chiến lược phổ biến là phân mảnh theo dải giá trị (range-based) và băm (hash-based).
Phân mảnh theo dải giá trị (Range-based Sharding)
Dữ liệu được chia theo khoảng giá trị của shard key. Ví dụ, nếu dùng trường age làm khóa, các giá trị sẽ được nhóm vào các đoạn như [18–30), [30–50), v.v., mỗi đoạn tương ứng với một chunk. Các chunk này được phân bổ lên các shard khác nhau.
Ưu điểm: Hiệu quả với các truy vấn phạm vi như tìm người dùng từ 25 đến 35 tuổi — chỉ cần truy vấn vào một vài chunk.
Nhược điểm: Nếu shard key có xu hướng tăng dần (ví dụ: _id tự sinh), mọi bản ghi mới sẽ đổ dồn vào cùng một chunk, gây tắc nghẽn ghi (write bottleneck).
Phân mảnh theo băm (Hash-based Sharding)
Giá trị shard key được băm thành một số nguyên 64-bit, sau đó phân bổ vào các chunk theo giá trị băm. Điều này giúp dữ liệu được rải đều hơn giữa các shard.
Ưu điểm: Cân bằng tải ghi rất tốt do dữ liệu được phân phối ngẫu nhiên.
Nhược điểm: Không tối ưu cho truy vấn phạm vi vì hệ thống phải kiểm tra tất cả các shard để tìm dữ liệu phù hợp.
Lựa chọn shard key hợp lý
Việc chọn shard key ảnh hưởng lớn đến hiệu năng. Cần đảm bảo:
- Khóa có đủ độ đa dạng về giá trị để tránh tạo ra chunk "khổng lồ" (jumbo chunk) — chunk không thể tách nhỏ do giá trị khóa quá ít.
- Cân nhắc giữa nhu cầu truy vấn (range hay point query) và tải ghi.
- Tránh dùng các trường có tính tuần tự cao như timestamp nếu không muốn gây mất cân bằng.
Vai trò của Mongos
Mongos đóng vai trò như bộ định tuyến thông minh. Nó đọc metadata từ config server để xác định vị trí dữ liệu và chuyển tiếp yêu cầu phù hợp.
Xử lý truy vấn
- Nếu truy vấn chứa shard key: Định tuyến trực tiếp đến shard đích.
- Nếu không chứa shard key: Phát tán truy vấn đến tất cả shard và gom kết quả lại (scatter-gather), có thể ảnh hưởng đến hiệu năng.
Xử lý ghi, cập nhật, xóa
- Thao tác ghi bắt buộc phải chứa shard key để xác định chunk đích.
- Cập nhật/xóa: Nếu điều kiện chứa shard key, định tuyến chính xác; nếu chỉ dùng
_id, mongos cần gửi đến mọi shard để tìm kiếm trừ khi_idcũng là shard key.
Xử lý lệnh quản trị
Các lệnh như listDatabases sẽ được gửi đến tất cả shard và config server, sau đó kết hợp kết quả trước khi trả về.
Config Server và các bộ sưu tập hệ thống
Toàn bộ cấu hình và trạng thái của cụm được lưu trong cơ sở dữ liệu config trên config server.
config.shards
Lưu danh sách các shard trong cụm. Có thể thêm/xóa shard động bằng lệnh addShard.
db.addShard("rs1/192.168.1.10:27017")
config.databases
Ghi nhận trạng thái phân mảnh của từng database. Mỗi database có một primary shard – nơi lưu trữ các collection chưa được phân mảnh.
db.databases.find()
// Kết quả mẫu:
{ "_id": "myapp", "partitioned": true, "primary": "shardA" }
Khi tạo database mới, hệ thống chọn shard có dung lượng thấp nhất làm primary. Có thể thay đổi bằng lệnh movePrimary để cân bằng tải.
config.collections
Liệt kê các collection đã được kích hoạt phân mảnh. Dùng lệnh shardCollection để bật tính năng này.
sh.shardCollection("myapp.users", { "city": 1 })
config.chunks
Theo dõi vị trí và phạm vi của từng chunk. Khi dữ liệu tăng, chunk sẽ tự động tách (split) khi đạt ngưỡng (mặc định 64MB). Hệ thống balancer sẽ di chuyển chunk giữa các shard để đảm bảo cân bằng.
db.chunks.find({ ns: "myapp.users" })
// Kết quả mẫu:
{ "min": { "city": "Hanoi" }, "max": { "city": "HoChiMinh" }, "shard": "shardB" }
config.settings
Lưu các cấu hình toàn cục như kích thước chunk, trạng thái balancer.
{ "_id": "chunksize", "value": 64 }
{ "_id": "balancer", "stopped": false }
Các bộ sưu tập phụ trợ khác
- config.tags: Hỗ trợ phân vùng dữ liệu theo tag (ví dụ: dữ liệu châu Á lưu ở shard riêng).
- config.changelog: Ghi lại lịch sử thay đổi như di chuyển chunk, thay đổi cấu hình.
- config.mongos: Danh sách các mongos đang hoạt động.
- config.locks: Quản lý khóa để tránh xung đột khi thực hiện các thao tác như di chuyển chunk.