Introduce sharding rules to MongoDB collections #673
Labels
enhancement 🌟
New feature or request
hard 🧑🔬
Difficult to deal with or require research
protocol changed 📝
Whether the protocol has changed
sdk ⚒️
What would you like to be added:
Support MongoDB Sharding feature in Yorkie. The previous issue about this is already at #530, which contains specification and initial discussion about it.
This issue is more about sharing the sharding related discussions and implements up to date, with the goal of getting more contributors involved in the work.
Why is this needed:
The Yorkie cluster stores most of data in MongoDB. Therefore, the most of loads are concentrated on DB clusters rather than application servers. For the case of real-world service scenarios, it is necessary to support sharding for distributing data and query loads across horizontally-scalable db clusters.
1. Background Knowledge
1.1. What is the Sharding?
When large data should be stored or high query throughput is required, data can be divided into several partitions and distributed to several nodes. This method is called as ”Sharding” or “Partitioning”. Sharding enables horizontal scaling since each shard only has to manage a subset of the data for a sharded collection. As a result, the data and the query loads can be distributed evenly to nodes.
1.2. Sharded Cluster Components
A MongoDB sharded cluster consists of the following components.
For a production deployment, consider the following to ensure data redundancy and system availability.
config1
,config2
,config3
shard1-1
,shard1-2
,shard1-3
shard2-1
,shard2-2
,shard2-3
shard3-1
,shard3-2
,shard3-3
mongos1
,mongos2
1.3. Shard Key Selection
It is important to choose a good shard key to distribute data evenly across shards and achieve high efficiency and performance of operations.
1.3.1. Shard Key Selection Criteria
It is notable that only one of the following criteria does not, on its own, guarantee even distribution of data across the sharded cluter. Therefore, it is necessary to consider overall criteria to decide appropriate shard keys.
1.3.2. Issues with Poorly Selected Shard Keys
If the selected shard keys has low cardinality or high value frequency, or is monotonically changing, the cluster may experience the following potential issues.
maxKey
(orminKey
) as the upper (or lower) bound. Before the MongoDB internal eventually rebalancing data, at any moment the cluster directs insert operations only to a single shard, there would be an insert throughput bottleneck.mongos
routes to only the shards that contain the relevant data.1.3.3. Solutions for Issues with Poorly Selected Shard Keys
maxKey
(orminKey
)).1.4. Sharding Approach Selection
There are mainly two sharding approches, hashed sharding and ranged sharding.
I believe storing related data in the same chunk has trade-off between the performance of ranged query and the creation of hot chunks. There is no silver bullet for this, so a shard approach should be determined under the consideration of the above various factors.
1.5. Specifying Unique Constraints
1.5.1. Unique Constraints on Indexes
The unique constraint on indexes ensures that only one document can have a value for a field in a collection. MongoDB can enforce a uniqueness constraint via the
unique: true
option on a ranged shard key index.However, unique constraints cannot be specified on a hashed index due to potential hash collisions on the keys. Instead, creating an additional non-hashed secondary index with unique constraints is possible, then MongoDB can use that non-hashed index to enforce uniqueness on the chosen field.
1.5.2. Unique Constraints for Sharded collections
For a ranged sharded collection, MongoDB doesn't support unique indexes across shards because insert and indexing operations are local to each shard, except only the following indexes.
unique
parameter, the collection must have a unique index that is on the shard key._id
index; however, the_id
index only enforces the uniqueness constraint per shard if the_id
field is not the shard key or the prefix of the shard key._id
index enforces the unique constraints globally in unsharded clusters, if the_id
field is not the shard key or the prefix of the shard key,_id
index only enforces the uniqueness constraint per shard and not across shards.Through the use of a unique index on the shard key, MongoDB enforces uniqueness on the entire key combination and not individual components of the shard key. For example, with the shard key
{x: 1, y: 1}
, the entire combination(x, y)
has an unique constraint, but each individual(x)
or(y)
does not.1.6. Data Partitioning with Chunks
The data is partitioned into chunks owned by a specific shard. A chunk consists of a range of sharded data. The balancer automatically migrates data evenly between shards. Initially, for empty collections, a single chunk or more than one chunks are created depending on the sharding approach. After the initial chunk setup, the balancer migrates the chunks across the shards when necessary.
The default range size (=chunk size) is
128MB
. The size is adjustable between1
and1024MB
. Setting the chunk size for a specific collection is also possible via theconfigureCollectionBalancing
option. Consider the implication that small ranges leads to a more even distribution at the expense of more frequent migrations like overheads of the networking and the query routing layer.1.7. Sharded Cluster Balancer
1.7.1. Balancer
The balancer is a background process that manages data migrations. If the difference in amount of data for a single collection between the largest and smallest shard exceed the migration thresholds, the balancer begins migrating data across the cluster to ensure an even distribution. The migration threshold is three times of the configured range size. For the default range size of
128MB
, two shards must have a data size difference for a given collection of at least384MB
for a migration to occur.1.7.2. Relationship between Chunk Split and Chunk Migration
Starting in MongoDB 6.0, a sharded cluster only splits chunks when chunks must be migrated. This means the chunk size may exceed the configured chunk size. For example, you might see a
1TB
chunk on a shard even though you have set the chunk size to256MB
. It is because larger chunks reduce the number of chunks on a shard and improve performance by reducing the time to update the shard metadata.However, by default, MongoDB cannot move a range if the number of documents in the range is greater than
2
times the result of dividing the configured range size by the average document size. Therefore, it is better to prevent chunks being jumbo at an early stage.2. Considerations
2.1. Relations between Collections
users
,projects
documents
,clients
changes
,snapshots
,syncedseqs
2.2. Goals
10,000
1
million100
million2.3. Unique Constraint Requirements
Documents
:(project_id, key)
withremoved_at: null
Clients
:(project_id, key)
Changes
:(doc_id, server_seq)
Snapshots
:(doc_id, server_seq)
Syncedseqs
:(doc_id, client_id)
2.4. Main Query Patterns
Project-wide collections
Project-wide collections contain range queries with a
project_id
filter.Clients
Documents
Document-wide collections
Document-wide collections mostly contain range queries with a
doc_id
filter.Changes
Snapshots
3. Sharding Rules
3.1. Selected Shard Keys and Approaches
Select shard keys based on the query patterns and properties (cardinality, frequency) of keys.
project_id
, rangeddoc_id
, rangedIn addition, every unique constraint can be satisfied because each has the shard key as a prefix.
Documents
:(project_id, key)
withremoved_at: null
Clients
:(project_id, key)
Changes
:(doc_id, server_seq)
Snapshots
:(doc_id, server_seq)
Syncedseqs
:(doc_id, client_id)
3.2. Changes of Reference Keys
Since the uniqueness of
_id
isn't guaranteed across shards, reference keys to indicate a single data in collections should be changed.Documents
:_id
->(project_id, _id)
Clients
:_id
->(project_id, _id)
Changes
:_id
->(project_id, doc_id, server_seq)
Snapshots
:_id
->(project_id, doc_id, server_seq)
Syncedseqs
:_id
->(project_id, doc_id, client_id)
Considering that MongoDB ensures the uniqueness of
_id
per shard,Documents
andClients
can be identified with the combination ofproject_id
and_id
. According to these changes, the reference keys of document-wide collections also become changed.3.3. Relations between Collections
4. Kubernetes Deployment
It is desirable to provide a Helm charts for providing handy deployment of sharded clusters in Kubernetes.
5. Performance Optimization
5.1. Effectiveness of Query Execution
After deploying a sharded cluster in production, measuring exact performance improvement compared to standalone is necessary.
It also is possible to check if the selected shard key is appropriate by checking query execution plans. The following result shows that the query is executed through index scans.
5.2. Measuring Performance in Production
It is necessary to evaluate performance of sharded clusters in production.
There are many aspects to assess, including query performance, rebalancing performance, frequency of hot chunks.
5. Risks
5.1. Limited Scalability due to High value frequency of
project_id
When there are the limited number of projects, it's likely for data to be concetrated on the small number of chunks.
This may limit scalability of clusters, which means adding more shards becomes ineffective and meaningless.
5.1.1. Solution: Change the Shard Key
As previously said, using a composite shard key can resolve this issue. Specifically, use
(project_id, key)
as project-wide collections' shard key instead ofproject_id
.It's now possible to split large chunks by
key
values, and migrate the splitted chunks to newly added shards.However, these changes make both
actor_id
andowner
able to duplicate, and the duplication can devastate the consistency of document. The reason for this is that bothactor_id
andowner
are currently usingclient_id
as a value, which can now duplicate in the same project, contrary to the case in the previous sharding rules.In the previous sharding rules, every client in the same project is located in the same shard, which prevents duplication
of
client_id
with the unique constraint per shard.There are three approaches to resolve this issue:
client_key + client_id
as a value.5.2. Duplicate MongoDB ObjectID
Both
client_id
anddoc_id
use MongoDB ObjectID as a value.When there are duplicate ObjectIDs, it works well due to the changed reference keys, until the MongoDB balancer migrates a chunk with the duplicate ObjectID. Such migrations are expected to create an error that doesn't harm the consistency of documents, but brings out a temporary failure of the cluster. This conflict should be manually handled by administrators.
However, the possiblity of duplicate ObjectIDs is extremely low in practical use cases due to its mechanism.
ObjectID uses the following format:
Duplicate ObjectIDs can be generated, when more than
16,777,216
documents/clients are created in a single second by a single machine and process. Considering Google processes over99,000
searches every single second, it is unlikely to occur.We can see more details in the following references.
When we have to meet that amount of traffic, consider the following options:
doc_key
andclient_key
and use(project_id, key)
as a reference key.The text was updated successfully, but these errors were encountered: