Sharding tăng khả năng mở rộng bằng cách chia dữ liệu trên nhiều node, nhưng nó thêm routing, rebalancing và chế độ lỗi mới khiến hệ thống khó suy đoán hơn.

Sharding (còn gọi là phân vùng ngang) là việc biến cái nhìn của ứng dụng về một cơ sở dữ liệu thành dữ liệu được chia trên nhiều máy, gọi là shard. Mỗi shard chỉ chứa một phần các dòng, nhưng cùng nhau chúng tạo thành tập dữ liệu đầy đủ.
Một mô hình tư duy hữu ích là phân biệt cấu trúc logic và vị trí vật lý.
Với ứng dụng, bạn muốn chạy truy vấn như thể đó là một bảng đơn. Ở bên dưới, hệ thống phải quyết định nên nói chuyện với shard nào.
Sharding khác với replication. Replication tạo bản sao của cùng dữ liệu trên nhiều node, chủ yếu để tăng sẵn sàng và khả năng đọc. Sharding chia dữ liệu sao cho mỗi node chứa các bản ghi khác nhau.
Cũng khác với scaling theo chiều dọc, nơi bạn giữ một DB nhưng chuyển sang máy mạnh hơn (CPU/RAM/đĩa nhanh hơn). Vertical scaling có thể đơn giản hơn, nhưng có giới hạn thực tế và chi phí tăng nhanh.
Sharding tăng khả năng chứa và throughput, nhưng không tự làm cho cơ sở dữ liệu “dễ” hoặc làm mọi truy vấn nhanh hơn.
Vì vậy sharding nên được hiểu như một cách để mở rộng lưu trữ và throughput—không phải một bản nâng cấp miễn phí cho mọi khía cạnh hành vi cơ sở dữ liệu.
Sharding hiếm khi là lựa chọn đầu tiên. Các đội thường hướng tới nó sau khi hệ thống thành công chạm tới giới hạn vật lý—hoặc khi đau đầu vận hành xuất hiện quá thường xuyên để bỏ qua. Động lực ít là “chúng tôi muốn sharding” hơn là “chúng tôi cần cách tiếp tục mở rộng mà không để một DB trở thành điểm lỗi đơn lẻ và chi phí lớn”.
Một node DB đơn có thể cạn chỗ theo nhiều cách khác nhau:
Khi những vấn đề này xuất hiện thường xuyên, vấn đề thường không phải một truy vấn xấu—mà là một máy chịu quá nhiều trách nhiệm.
Sharding cơ sở dữ liệu phân tán dữ liệu và lưu lượng qua nhiều node để năng lực tăng bằng cách thêm máy thay vì nâng cấp dọc. Nếu làm tốt, nó còn có thể cô lập workload (một spike của tenant không làm hỏng latency của người khác) và kiểm soát chi phí bằng cách tránh phải dùng các instance đắt tiền ngày càng lớn.
Các pattern lặp lại bao gồm p95/p99 latency tăng dần trong giờ cao điểm, replication lag dài hơn, backup/restore vượt quá cửa sổ chấp nhận được, và các thay đổi schema “nhỏ” trở thành các sự kiện lớn.
Trước khi quyết định, các đội thường đã thử các lựa chọn đơn giản hơn: indexing và sửa truy vấn, caching, read replicas, partitioning trong một DB đơn, archive dữ liệu cũ, và nâng cấp phần cứng. Sharding có thể giải quyết vấn đề scale, nhưng nó cũng thêm phối hợp, độ phức tạp vận hành và chế độ lỗi mới—vì vậy tiêu chuẩn phải cao.
Một cơ sở dữ liệu sharded không phải là một thứ duy nhất—nó là một hệ nhỏ của các phần hợp tác. Lý do sharding có thể cảm thấy “khó suy luận” là vì tính đúng đắn và hiệu năng phụ thuộc vào cách các phần này tương tác, không chỉ engine DB.
Một shard là một tập con của dữ liệu, thường lưu trên server hoặc cluster riêng. Mỗi shard thường có:
Với ứng dụng, một setup sharded thường cố gắng trông như một DB logic duy nhất. Nhưng ở bên dưới, một truy vấn vốn là “một lần lookup bằng chỉ mục” trên single-node có thể trở thành “tìm shard đúng, rồi thực hiện lookup”.
Một router (đôi khi gọi coordinator, query router, hoặc proxy) là cảnh sát giao thông. Nó trả lời câu hỏi thực tế: yêu cầu này thì shard nào xử lý?
Có hai mẫu thông dụng:
Router làm giảm độ phức tạp ở app, nhưng nó cũng có thể trở thành điểm nghẽn hoặc điểm lỗi mới nếu không thiết kế cẩn thận.
Sharding dựa vào metadata—nguồn sự thật mô tả:
Thông tin này thường sống trong một dịch vụ config (hoặc một DB “control plane” nhỏ). Nếu metadata lỗi thời hoặc không nhất quán, router có thể gửi traffic đến chỗ sai—dù mỗi shard có thể vẫn khỏe mạnh.
Cuối cùng, sharding phụ thuộc vào các process nền giữ cho hệ thống sống theo thời gian:
Những job này dễ bị bỏ qua khi mới bắt đầu, nhưng lại là nơi nhiều bất ngờ trong production xảy ra—vì chúng thay đổi hình dạng hệ thống khi hệ thống vẫn đang phục vụ traffic.
Shard key là trường (hoặc tổ hợp trường) hệ thống dùng để quyết định shard nào sẽ lưu một hàng/document. Lựa chọn này quyết định hiệu năng, chi phí, và cả những tính năng nào sẽ “dễ” sau này—vì nó kiểm soát liệu các yêu cầu có thể routing tới một shard hay phải fan-out.
Khóa tốt thường có:
user_id thay vì country).Ví dụ phổ biến là shard theo tenant_id trong app đa-tenant: hầu hết đọc/ghi cho một tenant nằm trên một shard, và số tenant đủ lớn để phân tán tải.
Một số khóa gần như đảm bảo rắc rối:
Ngay cả khi một khóa low-cardinality tiện cho lọc, nó thường biến truy vấn thông thường thành scatter-gather, vì các hàng phù hợp nằm khắp nơi.
Khóa shard tốt cho cân bằng tải không luôn là tốt nhất cho truy vấn sản phẩm.
user_id), và một số truy vấn “toàn cục” (ví dụ báo cáo admin) sẽ chậm hơn hoặc cần pipeline riêng.region), bạn dễ gặp hotspot và dung lượng không đều.Hầu hết đội thiết kế quanh trao đổi này: tối ưu khóa shard cho các thao tác phổ biến, nhạy latency—và xử lý phần còn lại bằng index, denormalization, replicas, hoặc bảng analytics chuyên dụng.
Không có một cách “tốt nhất” để shard. Chiến lược bạn chọn định hình mức độ dễ routing, phân phối dữ liệu đều, và loại pattern truy cập sẽ gây rắc rối.
Với range sharding, mỗi shard sở hữu một lát liên tục của không gian khóa—ví dụ:
Routing đơn giản: nhìn vào khóa, chọn shard.
Nhược điểm là hotspot. Nếu người dùng mới luôn nhận ID tăng dần, shard “cuối” sẽ trở thành nút cổ chai ghi. Range sharding cũng nhạy với tăng trưởng không đồng đều (một range nổi tiếng, range khác ít dùng). Ưu điểm: truy vấn theo range ("tất cả order từ 1–31 Oct") có thể hiệu quả vì dữ liệu được nhóm vật lý.
Hash sharding chạy khóa shard qua một hàm băm và dùng kết quả để chọn shard. Thường thì cách này phân phối dữ liệu đều hơn, giúp tránh vấn đề “mọi thứ đổ về shard mới nhất”.
Đổi lại: truy vấn theo range sẽ tốn. Một truy vấn như “customers với ID giữa X và Y” không còn ánh xạ tới ít shard nữa; nó có thể chạm nhiều shard.
Một chi tiết thực tế mà đội thường đánh giá thấp là consistent hashing. Thay vì ánh xạ trực tiếp theo số lượng shard (khi thêm shard thì mọi thứ bị xáo trộn), nhiều hệ thống dùng hash ring với “virtual nodes” để khi thêm capacity chỉ di chuyển một phần khóa.
Directory sharding lưu một ánh xạ rõ ràng (bảng lookup/dịch vụ) từ key → vị trí shard. Đây là cách linh hoạt nhất: bạn có thể đặt tenant cụ thể trên shard dành riêng, di chuyển một khách hàng mà không di chuyển mọi người, và hỗ trợ shard có kích thước không đều.
Nhược điểm là một phụ thuộc thêm. Nếu directory chậm, lỗi thời, hoặc không khả dụng, routing sẽ bị ảnh hưởng—dù các shard vẫn khỏe.
Hệ thống thực tế thường kết hợp các cách. Khóa ghép (ví dụ tenant_id + user_id) giữ tenant được cô lập đồng thời phân tán tải trong tenant đó. Sub-sharding tương tự: đầu tiên route theo tenant, sau đó hash trong nhóm shard của tenant để tránh một tenant lớn chiếm đóng một shard duy nhất.
Cơ sở dữ liệu sharded có hai “đường đi” truy vấn rất khác nhau. Hiểu bạn đang ở đường nào giải thích phần lớn các bất ngờ về hiệu năng—và tại sao sharding có thể cảm thấy không thể đoán.
Kết quả lý tưởng là routing truy vấn tới đúng một shard. Nếu yêu cầu chứa shard key (hoặc thứ router có thể ánh xạ), hệ thống có thể gửi thẳng đến đúng nơi.
Đó là lý do các đội chú ý làm cho các đọc thông dụng “nhận biết shard key”. Một shard nghĩa là ít hops mạng hơn, thực thi đơn giản hơn, ít khóa hơn và ít phối hợp hơn. Latency chủ yếu là DB làm việc, không phải cluster tranh chấp ai xử lý.
Khi truy vấn không thể routing chính xác (ví dụ, lọc theo trường không phải shard key), hệ thống có thể broadcast nó tới nhiều hoặc tất cả shard. Mỗi shard chạy truy vấn cục bộ, rồi router (hoặc coordinator) hợp nhất kết quả—sắp xếp, loại trùng, áp dụng limit, và kết hợp các aggregate partial.
Fan-out khuếch đại tail latency: ngay cả khi 9 shard trả nhanh, một shard chậm có thể giữ cả request làm con tin. Nó cũng nhân nhân tải: một request người dùng có thể trở thành N request tới các shard.
Join giữa các shard tốn kém vì dữ liệu mà lẽ ra “nằm trong” DB giờ phải chuyền giữa các shard (hoặc tới coordinator). Ngay cả các aggregate đơn giản (COUNT, SUM, GROUP BY) cũng có thể cần kế hoạch hai pha: tính kết quả partial trên mỗi shard, rồi hợp nhất.
Phần lớn hệ thống mặc định dùng index cục bộ: mỗi shard chỉ index dữ liệu của chính nó. Chúng rẻ để duy trì, nhưng không giúp routing—vì vậy truy vấn vẫn có thể scatter.
Index toàn cục có thể cho phép routing mục tiêu bằng các trường không phải shard key, nhưng chúng thêm chi phí ghi, phối hợp, và các vấn đề scale/consistency riêng.
Ghi là nơi sharding ngừng cảm thấy như “chỉ là mở rộng” và bắt đầu thay đổi cách bạn thiết kế tính năng. Một ghi chạm một shard có thể nhanh và đơn giản. Một ghi chạm nhiều shard có thể chậm, dễ lỗi, và khó đảm bảo chính xác.
Nếu mỗi request có thể routing tới đúng một shard (thường qua shard key), DB có thể dùng cơ chế transaction bình thường. Bạn có tính nguyên tử và cách ly trong shard đó, và hầu hết vấn đề vận hành trông giống như các vấn đề single-node quen thuộc—chỉ lặp lại N lần.
Khi cần cập nhật dữ liệu trên hai shard trong một hành động logic (ví dụ chuyển tiền, chuyển order giữa khách, cập nhật aggregate lưu chỗ khác), bạn rơi vào lãnh thổ giao dịch phân tán.
Giao dịch phân tán khó vì cần phối hợp giữa máy có thể chậm, phân đoạn mạng, hoặc khởi động lại bất cứ lúc nào. Các giao thức kiểu two-phase commit thêm lượt vòng, có thể block khi timeout, và làm cho lỗi trở nên mơ hồ: shard B có áp dụng thay đổi trước khi coordinator chết không? Nếu client retry, có double-apply không? Nếu không retry, có mất dữ liệu không?
Một vài chiến thuật phổ biến giảm tần suất cần giao dịch đa-shard:
Trong hệ thống sharded, retry là điều không tránh khỏi. Hãy làm các ghi idempotent bằng cách dùng ID thao tác ổn định (ví dụ idempotency key) và lưu dấu "đã áp dụng" trong DB. Như vậy nếu timeout và client retry, lần 2 sẽ là no-op thay vì double charge, đơn hàng trùng lặp, hoặc counter không nhất quán.
Sharding chia dữ liệu ra nhiều máy, nhưng không loại bỏ nhu cầu dư thừa. Replication là thứ giữ shard khả dụng khi một node chết—và cũng là thứ làm câu hỏi “hiện giờ cái gì đúng?” trở nên khó trả lời.
Hầu hết hệ thống replicate trong mỗi shard: một primary (leader) nhận ghi, và một hoặc nhiều replica sao chép thay đổi đó. Nếu primary chết, hệ thống promote replica (failover). Replica cũng có thể phục vụ đọc để giảm tải.
Đổi lại là thời gian. Replica có thể chậm vài mili giây—hoặc vài giây. Khoảng cách này là bình thường, nhưng quan trọng khi người dùng mong đợi “tôi vừa cập nhật, nên tôi phải thấy ngay”.
Trong setup sharded, bạn thường có tính nhất quán mạnh trong một shard và những đảm bảo yếu hơn xuyên các shard, đặc biệt với thao tác đa-shard.
Với sharding, “nguồn sự thật duy nhất” thường có nghĩa: với mỗi mảnh dữ liệu có một nơi có thẩm quyền để ghi (thường là leader của shard đó). Nhưng trên phạm vi toàn cục, không có một máy nào có thể xác nhận tức thì trạng thái mới nhất của mọi thứ. Bạn có nhiều chân lý cục bộ cần được giữ đồng bộ qua replication.
Ràng buộc trở nên khó khi dữ liệu cần kiểm tra nằm trên các shard khác nhau:
Những lựa chọn này không chỉ là chi tiết triển khai—chúng xác định cái gọi là “đúng” cho sản phẩm của bạn.
Rebalancing giữ cho DB sharded dùng được khi thực tế thay đổi. Dữ liệu tăng không đều, khóa shard cân bằng drift thành skew, bạn thêm node để tăng capacity, hoặc cần retire phần cứng. Mọi điều đó có thể biến một shard thành nút cổ chai—dù thiết kế ban đầu có hoàn hảo.
Không giống DB đơn, sharding đóng vị trí dữ liệu vào logic routing. Khi bạn di chuyển dữ liệu, bạn không chỉ copy bytes—bạn thay đổi nơi truy vấn phải tới. Điều đó có nghĩa rebalancing không chỉ về storage mà còn về metadata và client.
Hầu hết đội hướng tới workflow online tránh việc “dừng thế giới” lớn:
Thay đổi shard map là sự kiện phá vỡ nếu client cache quyết định routing. Hệ thống tốt đối xử metadata routing như cấu hình: version hóa, refresh thường xuyên, và rõ ràng điều gì xảy ra khi client chạm vào khóa đã di chuyển (redirect, retry, hoặc proxy).
Rebalancing thường gây sụt hiệu năng tạm thời (ghi thêm, cache churn, tải copy nền). Di chuyển từng phần là phổ biến—một vài range migrate trước—vì vậy bạn cần observability rõ ràng và kế hoạch rollback (ví dụ, lật map về, drain dual-write) trước khi cutover.
Sharding giả định công việc sẽ trải đều. Điều bất ngờ là một cụm có thể trông “đều” trên giấy (số hàng giống nhau mỗi shard) nhưng hành xử rất không đều ở production.
Hotspot xảy ra khi một phần nhỏ không gian khóa nhận phần lớn traffic—nghĩ tới tài khoản celebrity, sản phẩm phổ biến, tenant chạy batch nặng, hoặc khóa theo thời gian mà “hôm nay” thu hút mọi ghi. Nếu những khóa đó map tới một shard, shard đó thành nút cổ chai dù shard khác nhàn rỗi.
“Skew” không chỉ một thứ:
Chúng không luôn trùng nhau. Một shard ít dữ liệu có thể vẫn nóng nhất nếu nó sở hữu các khóa được yêu cầu nhiều nhất.
Bạn không cần tracing phức tạp để nhận skew. Bắt đầu với dashboard per-shard:
Nếu latency một shard tăng theo QPS trong khi shard khác ổn định, bạn có hotspot.
Sửa chữa thường đổi đơn giản lấy cân bằng:
Sharding không chỉ thêm nhiều server—nó thêm nhiều cách để mọi thứ sai, và nhiều nơi để kiểm tra khi lỗi xảy ra. Nhiều sự cố không phải là “DB chết”, mà là “một shard chết”, hoặc “hệ thống không đồng ý dữ liệu ở đâu”.
Một vài pattern lặp lại:
Trên DB một node, bạn tail một log và kiểm tra metrics. Trên hệ thống sharded, bạn cần observability theo sát một request qua các shard.
Dùng correlation ID trong mọi request và truyền từ lớp API qua routers tới mỗi shard. Kết hợp với distributed tracing để một truy vấn scatter-gather hiện rõ shard nào chậm hoặc lỗi. Metrics nên được tách per shard (latency, queue depth, error rate), nếu không shard nóng bị che trong trung bình cụm.
Lỗi sharding thường xuất hiện như bug về tính đúng đắn:
"Restore database" trở thành "restore nhiều phần theo thứ tự đúng". Bạn có thể cần restore metadata trước, rồi từng shard, rồi xác minh ranh giới shard và routing khớp với điểm thời gian khôi phục. Kế hoạch DR nên bao gồm diễn tập chứng minh bạn có thể lắp ráp một cluster nhất quán—không chỉ phục hồi các máy đơn lẻ.
Sharding thường được xem như "công tắc mở rộng", nhưng nó cũng là sự tăng vĩnh viễn độ phức tạp hệ thống. Nếu bạn có thể đạt mục tiêu hiệu năng và độ tin cậy mà không chia dữ liệu qua node, thường bạn sẽ có kiến trúc đơn giản hơn, debug dễ hơn, và ít cạnh rủi ro vận hành hơn.
Trước khi shard, thử các lựa chọn giữ DB logic duy nhất:
Một cách thực tế giảm rủi ro sharding là prototype plumbing (ranh giới routing, idempotency, workflow migration, và observability) trước khi cam kết DB production.
Ví dụ, với Koder.ai bạn có thể nhanh chóng dựng một dịch vụ nhỏ thực tế từ chat—thường là UI admin React kèm backend Go với PostgreSQL—và thử API nhận biết shard key, idempotency key, và hành vi cutover trong môi trường an toàn. Vì Koder.ai hỗ trợ chế độ planning, snapshot/rollback, và xuất source, bạn có thể lặp các quyết định thiết kế liên quan sharding (như routing và hình dạng metadata) rồi mang mã và runbook sang stack chính khi tự tin.
Sharding phù hợp hơn khi dataset hoặc throughput ghi rõ ràng vượt quá giới hạn single-node và mẫu truy vấn của bạn có thể sử dụng shard key đáng tin cậy (ít cross-shard join, ít truy vấn scatter-gather).
Nó không phù hợp khi sản phẩm cần nhiều truy vấn ad-hoc, giao dịch nhiều thực thể thường xuyên, ràng buộc uniqueness toàn cục, hoặc khi đội không thể chịu khối lượng vận hành (rebalancing, resharding, ứng cứu sự cố).
Hãy hỏi:
Dù bạn tạm hoãn sharding, hãy thiết kế đường di chuyển: chọn identifier không cản trở shard key tương lai, tránh hard-code giả định single-node, và diễn tập cách bạn di chuyển dữ liệu với downtime tối thiểu. Thời điểm tốt nhất để lên kế hoạch resharding là trước khi bạn cần nó.
Sharding (phân mảnh ngang) chia một tập dữ liệu logic thành nhiều máy khác nhau ("shards"), mỗi shard lưu các bản ghi khác nhau.
Replication, trái lại, giữ các bản sao của cùng dữ liệu trên nhiều node—chủ yếu để tăng tính sẵn sàng và khả năng đọc.
Vertical scaling là nâng cấp một máy chủ cơ sở dữ liệu (thêm CPU/RAM/đĩa nhanh hơn). Về mặt vận hành thì đơn giản hơn, nhưng bạn rồi sẽ gặp giới hạn cứng (hoặc chi phí tăng cao).
Sharding mở rộng theo chiều ngang bằng cách thêm máy, nhưng đổi lại là routing, rebalancing và các thách thức về tính đúng đắn xuyên-shard.
Các đội shard khi một node trở thành nút thắt lặp lại, ví dụ:
Sharding phân phối dữ liệu và lưu lượng để tăng năng lực bằng cách thêm node.
Một hệ thống sharded thường bao gồm:
Hiệu năng và tính đúng đắn phụ thuộc vào việc các thành phần này duy trì nhất quán.
Shard key là trường (hoặc tổ hợp trường) dùng để quyết định một hàng lưu ở shard nào. Nó quyết định phần lớn liệu yêu cầu trúng một shard (nhanh) hay nhiều shard (chậm).
Các khóa tốt thường có độ phân biệt cao, phân phối đều, và phù hợp với mẫu truy cập phổ biến (ví dụ tenant_id hoặc user_id).
Các khóa "xấu" thường gồm:
Chúng gây hotspot hoặc biến truy vấn thông thường thành scatter-gather.
Ba chiến lược phổ biến:
Nếu truy vấn có shard key (hoặc thứ router có thể ánh xạ), router gửi đến một shard—đường nhanh.
Nếu không, hệ thống có thể fan out đến nhiều/đủ shard (scatter-gather). Một shard chậm có thể kéo toàn bộ độ trễ, và một truy vấn người dùng biến thành N truy vấn shard.
Ghi trên một shard có thể dùng transaction bình thường trên shard đó.
Ghi xuyên-shard cần phối hợp phân tán (thường kiểu two-phase commit), làm tăng độ trễ và khiến lỗi trở nên mơ hồ. Các biện pháp giảm thiểu:
Trước khi shard, thử các lựa chọn giữ nguyên một DB logic:
Sharding phù hợp khi bạn vượt giới hạn single-node và hầu hết truy vấn quan trọng có thể routing bằng shard key với ít cross-shard joins/transactions.