Pattern · seen in 4 breakdowns across 4 companies

Application-Layer Sharding

Definition

Application-layer sharding moves the decision of where data lives — which shard, which cluster, which storage node — into the application code that reads and writes the data, rather than relying on the underlying storage system's internal sharding. The application knows which tenant or entity each piece of data belongs to and uses that knowledge to route reads and writes deterministically. The underlying storage system sees only the operations it's told to perform; it doesn't make routing decisions on the application's behalf.

The motivating insight is control. When sharding is the storage system's responsibility, the application is at the mercy of the storage system's coordination logic, scaling characteristics, and operational boundaries. When sharding is the application's responsibility, the application can use information the storage system doesn't have (tenant priority tiers, traffic patterns, retention policies, locality requirements) to make routing decisions that better match its actual needs. The application also gains the ability to evolve the sharding strategy without depending on the storage system's migration tooling, which is often the largest operational constraint when scale outgrows the initial design.

The canonical implementation is a mapping layer — a lookup from tenant identifier to physical destination — backed by a fast cache and a slower persistent source of truth. Reads consult the cache; writes go to the destination resolved through the cache. The storage system itself is treated as a pool of independent capacity units (clusters, partitions, nodes) that the application orchestrates. This decouples capacity scaling from sharding strategy: adding capacity becomes 'add another unit to the pool'; changing sharding becomes 'reroute the mapping layer.'

The pattern is most visible in systems that have explicitly rejected their storage system's built-in sharding. Discord routes messages to Elasticsearch clusters in application code rather than using Elasticsearch's own shard distribution. Vitess and Citus implement application-layer sharding for relational databases as a deliberate alternative to the database engine's native partitioning. Many large-scale services route requests at the application layer to specific Redis or memcached nodes rather than using cluster-mode sharding. The shared characteristic: the system reaches a scale or operational requirement where the storage system's sharding logic becomes a constraint rather than a feature.

When it applies

01Multi-tenant systems where tenants vary widely in size and behavior, and where the application can use that knowledge to make better routing decisions than a generic hash function would
02Systems where the storage layer's native sharding cannot be evolved without significant downtime or data migration, but the data volume or access pattern is likely to require sharding changes over time
03Architectures where failure isolation between groups of tenants matters more than uniform load distribution — application-layer routing lets you keep specific tenant cohorts together (for failure isolation) or apart (for noisy-neighbor protection)
04Operational contexts where the team needs to perform cluster-level operations (upgrades, replacements, capacity additions) without coordinating with the storage system's internal rebalancing — application-layer routing lets you drain a cluster by changing the mapping, not by waiting for the storage system to rebalance
05Systems where the routing logic needs information the storage system doesn't have access to (tenant priority, regulatory locality requirements, customer-specific configuration)

Tradeoffs

The application is now responsible for maintaining the mapping layer and ensuring its consistency. Every routing decision must consult the mapping; every change to the mapping must be propagated carefully. This introduces a new class of bugs (mapping inconsistency, stale cache reads) that don't exist when the storage system handles routing internally.
The application's complexity grows. Code that would otherwise be 'INSERT into messages' becomes 'look up the destination, route to the correct cluster, handle the case where the lookup fails, retry against the persistent source of truth.' This complexity is non-trivial and must be maintained as the system evolves.
Some storage-system features become harder to use. Built-in cross-shard queries, automatic rebalancing, and global indexes often assume the storage system controls sharding. Application-layer sharding either gives these up or rebuilds them at the application layer (which is significant additional engineering work).
The mapping layer itself becomes a critical dependency. If the persistent source of truth (often a relational database or a strongly-consistent KV store) is unavailable, the application cannot make routing decisions. The mapping cache provides resilience for hot reads but doesn't help for new tenants or for cache misses — both of which require the source of truth.

The same move, 4 ways

Every row is a production system that bet on this pattern — the note says how, in that system's own terms.

Discord
Discord Engineering
2025
Discord's foundational decision in 2017, kept through the 2025 redesign. The routing logic lives in application code, not inside Elasticsearch. This gives Discord control over which clusters and indices each message goes to, control that proved essential when the time came to evolve the architecture. The value is clearest in hindsight: the redesign was possible because sharding was already an application-level concern the team could rework, without waiting on Elasticsearch's internal coordination. Read the breakdown →
Figma
Figma Blog
2024
Figma shards its primary transactional store through DBProxy's query engine (shard keys scrambled with a hash, related tables grouped so common joins and transactions keep working), where Discord shards a search-index fleet. Same idea, different storage underneath: the application layer owns where data goes and how queries are routed, so the database itself can stay stock. Read the breakdown →
Notion
Notion Blog
2021
Notion names the approach outright: build your own partitioning scheme and route queries from application code, chosen over packaged tools like Citus and Vitess because their clustering logic is opaque and Notion wanted control over how its data was distributed. Three companies in the library have now made this same call over different starting points: Discord moving off Elasticsearch, Figma adding a query-routing service in front of Postgres, and Notion routing directly in application code. The shared reason is the same each time: owning the routing is worth the ownership cost when the packaged option hides how your data is placed. Read the breakdown →
Pinterest
Pinterest Engineering Blog
2015
This is the earliest instance of the pattern, designed in 2012, years before the other systems here hit the same wall. Discord routes messages to Elasticsearch shards in application code (2017); Figma and Notion partitioned an existing single Postgres database under duress; Pinterest built the virtual-shards-over-machines architecture from scratch mid-hypergrowth. The shared skeleton is exact: many small logical shards (4,096 here), a config mapping ranges to machines, and capacity added by remapping. So are the shared renunciations: no cross-shard joins, foreign keys, or global indexes. Read the breakdown →

Often used together

Patterns sharing breakdowns with this one — derived from co-occurrence, threshold ≥2 shared.

Problems this pattern answers

The walls where its breakdowns live — each opens the cross-company comparison.