Patterns

Shard by shop: Shopify

Shopify split its MySQL fleet into pods by shop, then routed each request to the pod that owns it.

Shopify ran one large MySQL database until 2015, when buying a bigger server stopped being an option. The team sharded by shop_id, then went further: each shard became a pod with its own database, cache, and app servers. A load balancer component called Sorting Hat reads the shop from the request and adds a header that sends it to the right pod.

The system#

The pod is the unit of isolation. A pod holds a subset of shops, its own MySQL shard, Redis, and Memcached, and it can run anywhere. App servers and job workers are shared across pods, so a flash sale in one pod can use capacity from the rest of the region. In 2024 the platform peaked at 489 million requests a minute at the edge, 117 million a minute at app servers, and 14.8 trillion database queries over the weekend.

The board#

Drag to pan. Hold Ctrl or Cmd and scroll to zoom.

At 4.00k Users the Simulation solves this Design to Apdex 1.00, p99 308 ms, 0.1% errors and $46/s Burn. The board splits shops across shards as the pods do, but it does not give each pod its own app servers. The caption is Quorum's result, not the company's numbers.
WhereSettingValueWhy
Load BalancerPolicyleast-connThe load balancer sends each request to a copy with room, as Sorting Hat sends each shop to its pod.
App ServerInstance sizelargeLarge, because the monolith does the work for every shop.
App ServerCopies4The stateless app servers are shared across pods, so the board keeps them in one group.
CacheEnginememcachedMemcached scales with cores, which suits a cache shared by many app servers.
CacheCache strategyasideThe app fills the cache on a miss and writes to the database, so the cache holds what shoppers read.
CacheTTL (seconds)60A minute keeps catalog reads hot without long staleness.
CacheInstance sizemediumMedium, sized for the hot catalog and shop rows.
DatabaseShards4Each shard is another MySQL database, and the shops divide across them as they do across pods.
DatabaseShard keytenantA tenant key keeps one shop’s rows together, which is the pod boundary. A huge shop can still make its shard the busy one, which is why Shopify gives big merchants their own pod.
DatabaseEnginemysqlMySQL, the engine Shopify runs, with cheaper writes and online schema changes.
DatabaseInstance sizemediumMedium for each shard, so a hot shard shows.
QueueDead-letter queueOnA job that keeps failing is parked, not retried forever.
QueueMax length10000A full queue refuses work instead of hiding a growing backlog.
WorkerInstance sizesmallSmall, because a job is one shop’s work at a time.
WorkerCopies3Shared workers drain the queue for every pod.

Research this Design needs: Load balancing, Caching, Message queues, Background jobs, Checkout, Sharding, Caching, Message queues, Background jobs.

The Database has four shards on a tenant key, which is the pod boundary. The Load Balancer stands for Sorting Hat, the Cache is the per-pod Redis and Memcached, and the Queue and Workers are the shared job tier. The Payment Provider answers checkouts.

Why it works#

A tenant key keeps one shop's rows on one shard, so most queries stay local and a failure stays inside one pod. The trade is the hot tenant: one huge shop can still make its shard the busy one, which is why Shopify gives big merchants a dedicated pod. Shard the database covers the general trade.

Where the game stops#

The board shards the database but keeps one set of app servers, because Quorum has no pod-level app tier. It also has no Sorting Hat, so the shard key does the routing that the header does at Shopify. The caption is Quorum's result, not Shopify's traffic.

Sources#