The primary reason for sharding a database is scalability. Sharding is a solution if the volume of data or the write throughput has become too great for a single node to handle, as it allows you to spread that data and those writes across multiple nodes.
In fact, sharding is one of the main tools we have for achieving horizontal scaling (a scale-out architecture), as discussed in “Shared-Memory, Shared-Disk, and Shared-Nothing Architectures”—that is, allowing a system to grow its capacity not by moving to a bigger machine, but by adding more (smaller) machines. If you can divide the workload such that each shard handles a roughly equal share, you can then assign those shards to different machines to process their data and queries in parallel.
While replication is useful at both small and large scale, because it enables fault tolerance and offline operation, sharding is a heavyweight solution that is mostly relevant at large scale. If your data volume and write throughput are such that a single machine can handle them (and a single machine can do a lot nowadays!), it’s often better to avoid sharding and stick with a single-shard database.
The reason for this recommendation is that sharding adds complexity. You typically have to decide which records to put in which shard by choosing a partition key; all records with the same partition key are placed in the same shard. This choice matters because accessing a record is fast if you know which shard it’s in, but if you don’t, you have to do an inefficient search across all shards. The sharding scheme is also difficult to change.
Sharding often works well for key-value data, where you can easily shard by key, but it’s harder with relational data, where you may want to search by a secondary index or join records that might be distributed across different shards. We will discuss this further in “Sharding and Secondary Indexes”.
Another problem with sharding is that a write may need to update related records in several shards. While transactions on a single node are quite common, ensuring consistency across multiple shards requires a distributed transaction. distributed transactions are available in some databases, but they are usually much slower than single-node transactions and may become a bottleneck for the system as a whole.
Some systems use sharding even on a single machine, typically running one single-threaded process per CPU core to make use of the parallelism in the CPU or to take advantage of a nonuniform memory access (NUMA) architecture in which some banks of memory are closer to one CPU than to others. For example, Redis, VoltDB, and FoundationDB use one process per core and rely on sharding to spread load across CPU cores in the same machine.
Sharding for Multitenancy
Software as a service (SaaS) products and cloud services are often multitenant, where each tenant is a customer. Multiple users may have logins on the same tenant, but each tenant has a self-contained dataset that is separate from those of other tenants. For example, in an email marketing service, each business that signs up is typically a separate tenant, since one business’s newsletter sign-ups, delivery data, etc., are separate from those of other businesses.
Sometimes sharding is used to implement multitenant systems. Either each tenant is given a separate shard, or multiple small tenants may be grouped together into a larger shard. These shards might be physically separate databases (which we previously touched on in “Embedded Storage Engines”) or separately manageable portions of a larger logical database. Using sharding for multitenancy has several advantages:
Resource isolation
If one tenant performs a computationally expensive operation, it is less likely that other tenants’ performance will be affected if they are running on different shards.
Permission isolation
If there is a bug in your access control logic, it’s less likely that you will accidentally give one tenant access to another tenant’s data if those tenants’ datasets are stored physically separately from each other.
Cell-based architecture
You can apply sharding not only at the data storage level, but also for the services running your application code. In a cell-based architecture, the services and storage for a particular set of tenants are grouped into a self-contained cell, and different cells are set up such that they can run largely independently from each other. This approach provides fault isolation: a fault in one cell remains limited to that cell, and tenants in other cells are not affected.
Per-tenant backup and restore
Backing up each tenant’s shard separately makes it possible to restore a tenant’s state from a backup without affecting other tenants, which can be useful if the tenant accidentally deletes or overwrites important data.
Regulatory compliance
Data privacy regulations such as the GDPR and CCPA give individuals the right to access and request deletion of personal information that businesses store about them. If each person’s data is stored in a separate shard, this translates into simple data export and deletion operations on their shard.
Data residence
If a particular tenant’s data needs to be stored in a particular jurisdiction to comply with data residency laws, a region-aware database can allow you to assign that tenant’s shard to a particular region.
Gradual schema rollout
Schema migrations (previously discussed in “Schema flexibility in the document model”) can be rolled out gradually, one tenant at a time. This reduces risk, as you can detect problems before they affect all tenants, but it can be difficult to do transactionally.
The main challenges around using sharding for multitenancy are as follows:
-
It assumes that each individual tenant is small enough to fit on a single node. If that is not the case, and you have a single tenant that’s too big for one machine, you will need to additionally perform sharding within that tenant, which brings us back to the topic of sharding for scalability.
-
If you have many small tenants, creating a separate shard for each one may incur too much overhead. You could group several small tenants together into a bigger shard, but then you have the problem of how you move tenants from one shard to another as they grow.
-
If you ever need to support features that connect data across multiple tenants, these become harder to implement if you need to join data across multiple shards.