Resharding a live database  / field guide
Practitioner field guide · 29 August 2026

The cutover is the easy part

Thirteen production accounts of splitting a live database across more machines, from Notion, Figma, Slack, GitHub, Shopify, Discord, Etsy, Pinterest, Stripe, Adyen, Mercari, Monzo and AWS, plus six published incidents. The minute everybody rehearses, the one where writes stop, is measured in seconds and has never been blamed for an outage in this corpus. The damage comes from the shard key chosen nine months earlier and from the capacity added six months later.

26 primary sources 13 production systems 6 incidents Evidence through August 2026 Read: 24 min
01

The territory

The problem, stated without the word sharding: one machine holds the data and answers the queries, the business now needs several machines to do both, and the application is not permitted to stop while the data is spread out.

10s
Write unavailability on primaries during Figma's first physical shard split
~1s
Worst user-visible stall while Notion moved from 32 to 96 databases
10h
Monzo impact from adding six servers to a live cluster
0
Postmortems in this corpus that blame the cutover itself

Every account in this guide was written by someone who had already done the work, and they agree on the shape of the operation to a degree that is unusual. Route the traffic through something you control. Copy the data. Tail the changes the copy is racing. Compare the two copies until you believe them. Stop writes for a moment and move the pointer. Keep replication running backwards so you can undo it. Then, months later, delete the old copy. Notion, Figma, GitHub, Shopify, Slack, Stripe and Vitess all describe that sequence, in that order, with different words for each stage.

What they do not agree on is anything about the shard key, which is the decision that determines whether the resulting system is operable. Notion partitions by workspace because "each block belongs to exactly one workspace". Figma looked at the same idea and rejected it: Sammy Steele, who led the work, said on Postgres FM that at Figma "data moves quite frequently between orgs, which makes that kind of sharding model quite hard", so they picked a set of keys per table instead. Slack started with workspace and moved to channel to flatten the distribution. Etsy ended up with "more than 30 different IDs" used as sharding keys across its tables. Pinterest embedded the shard number in the object id itself, permanently.

That divergence matters because of what the incident record contains. Of the six published incidents reconstructed here, none is caused by the atomic table swap that everyone rehearses and fears. Two are caused by the shard key interacting with a read pattern nobody modelled (Slack, twice). Two are caused by adding capacity to an already-sharded store, which is the operation teams treat as routine once the hard migration is behind them (Monzo, DynamoDB). One is caused by the load of a migration rather than its logic, and ended with sixteen private repositories readable by strangers for seven minutes (GitHub, 2012). One is a failure of the verification tool rather than the system it verifies (Vitess VDiff v1).

The finding worth carrying

The cutover is short, reversible and well understood. The shard key is long-lived, irreversible in practice, and chosen before you have the operational data that would tell you whether it is right. Spend your design review on the key and your rehearsal budget on the capacity-addition procedure you will run twelve times afterwards, not on the swap.

Scope. This guide covers online redistribution of an operational transactional store across more machines: choosing the partition key, moving the data, verifying it, switching traffic, and adding capacity afterwards. It does not cover analytical stores and lakehouse partitioning, which have different constraints because nothing is reading and writing the same row concurrently; it does not cover multi-region placement or data residency; it does not cover caching strategy except where a cache miss caused an incident; and it treats schema change only where its correctness protocol turns out to be the same protocol as the data migration, which is section three.

Figure 1 · Three families of answer, and who is in each

rebalancing needs
a data move you
did not design for

cross-shard queries
become a query engine
you now maintain

The application must reach
more than one database

A · Routing in the application
shard id inside the object id

B · Routing in a proxy you build
parse, plan, route

C · Routing in a sharding platform
Vitess and equivalents

Pinterest 2012
Etsy 2010 to 2026

Figma DBProxy 2023
GitHub ProxySQL
Notion PgBouncer

Slack 2017-2020
GitHub in part
Etsy from 2026

rebalancing needs
a data move you
did not design for

cross-shard queries
become a query engine
you now maintain

The application must reach
more than one database

A · Routing in the application
shard id inside the object id

B · Routing in a proxy you build
parse, plan, route

C · Routing in a sharding platform
Vitess and equivalents

Pinterest 2012
Etsy 2010 to 2026

Figma DBProxy 2023
GitHub ProxySQL
Notion PgBouncer

Slack 2017-2020
GitHub in part
Etsy from 2026

The three families differ in where the routing decision lives, and that is the choice that determines who can change the shard map later. Reconstructed from Pinterest, Figma, Slack, GitHub and InfoQ's report of Etsy's migration.
Diagram source
02

How it is actually built

Six stages appear in every published account. The stage teams skip is the fifth, and it is the one that decides whether a bad cutover is an incident or a shrug.

Before any of the six stages, there is a precondition that every account treats as obvious and that no tutorial states plainly: you cannot move a database the application addresses directly. Figma built DBProxy, a Go service that parses SQL into an abstract syntax tree and routes on the extracted shard id. GitHub routed through ProxySQL. Notion drove its cutover by editing PgBouncer configuration. Slack put VTGate in front. Shopify keeps a routing table that maps a shop to a pod. Pinterest keeps a shard-to-host configuration table in ZooKeeper, and writes that "this config only changes when we need to move shards around or replace a host". Five organisations, five different technologies, one architectural move: insert a layer whose only job is to answer "which machine holds this key", and make that answer changeable at runtime. Everything downstream is a variation on editing that answer safely.

Figure 2 · The reference architecture: six stages and one precondition

The move

Precondition

mismatch

rollback

Indirection layer
key to machine, changeable at runtime

1 · Copy
bulk backfill, indexes deferred

2 · Tail
change stream from a captured position

3 · Compare
dark reads, diffs, verifiers

4 · Swap
stop writes, wait for lag zero, repoint

5 · Reverse
replicate new back to old

6 · Contract
delete the old copy, months later

The move

Precondition

mismatch

rollback

Indirection layer
key to machine, changeable at runtime

1 · Copy
bulk backfill, indexes deferred

2 · Tail
change stream from a captured position

3 · Compare
dark reads, diffs, verifiers

4 · Swap
stop writes, wait for lag zero, repoint

5 · Reverse
replicate new back to old

6 · Contract
delete the old copy, months later

Solid boxes appear in every account. The dashed box, the reverse channel, appears in Notion's second reshard, in Vitess as --enable-reverse-replication, and in Figma's config-flag rollback, and is absent from the accounts that took scheduled downtime. Reconstructed from Notion, Stripe, Shopify and Vitess.
Diagram source

The copier

A bulk reader that walks the source in batches and writes the target. The universal finding is that its throughput is an engineering choice rather than a property of the data. Discord replaced ScyllaDB's migrator with one written in Rust and reached "up to 3.2 million per second", turning three months into nine days. Notion found the cost was index maintenance, not volume: deferring index creation cut a copy "from 3 days to 12 hours". Adyen batches 100,000 rows and commits between batches.

Runs this way at: Discord, Notion, Adyen

The tailer

The copy takes hours or days, so live writes must be captured from a position taken before the copy started and replayed after it. Shopify streams the binlog and filters to one shop. Notion's first migration used an audit-log table populated by triggers; its second used Postgres logical replication. Stripe made the application write to both stores. All three are the same mechanism with different durability and different failure modes.

Runs this way at: Shopify, Notion, Stripe

The comparator

The stage that separates the confident accounts from the nervous ones. Notion ran dark reads against a follower and, notably, had "migration and verification logic implemented by different people". Stripe ran GitHub's Scientist in production so that live reads compared both stores continuously. Slack built "a parallel double-read diffing system". Shopify runs verifiers before, during and after, and specifies the algorithm in TLA+.

Runs this way at: Notion, Stripe, Slack

The swap

Stop writes, wait for the tailer to reach zero lag, change the routing, resume. GitHub's six-step version takes "only a few tens of milliseconds for our busiest database tables". Notion pauses PgBouncer, confirms catch-up, flips the replication direction and resumes. Vitess stops writes on the source primary and refuses to switch if replication lag exceeds a configured bound. The three descriptions are interchangeable.

Runs this way at: GitHub, Notion, Vitess

The reverse channel

After the swap, the new primary replicates back to the old one, so rollback is a routing change rather than a data recovery. Vitess ships this as a default (--enable-reverse-replication); Deepthi Sigireddi describes the intent as "keep the source in sync with the new shards so that if something goes wrong or we made a mistake, we can quickly fall back". Notion flipped the replication direction as step three of its per-database failover. Figma got the same property differently, by making the risky change a percentage-based config flag.

Runs this way at: Vitess, Notion, Figma

The divergence point: logical before physical

Figma's contribution is to split the operation in two. First make the data look sharded while it is still on one machine, using a view per shard (WHERE hash(shard_key) >= min AND < max), measured at "less than 10%" worst-case overhead. Roll that out by percentage, with a config rollback. Only then perform the physical split, which is the irreversible part. Nobody else in this corpus separates the two stages, and everybody else's riskiest step is bigger as a result.

Runs this way at: Figma

Figure 3 · The write cutover, in order, with the wait that decides its length

Target primarySource primaryRouting layerApplicationTarget primarySource primaryRouting layerApplicationsteady state, tailer lag ~secondstotal stall = drain time, not repoint timewrites1writes2set read-only, stop acceptingwrites3buffer or reject in-flight queries4drain remaining change stream5lag = 0 confirmed6routing map updated7reverse replication starts8writes resume9
Target primarySource primaryRouting layerApplicationTarget primarySource primaryRouting layerApplicationsteady state, tailer lag ~secondstotal stall = drain time, not repoint timewrites1writes2set read-only, stop acceptingwrites3buffer or reject in-flight queries4drain remaining change stream5lag = 0 confirmed6routing map updated7reverse replication starts8writes resume9
The stall the user sees is not the repoint, which is a metadata change; it is the wait for the tailer to drain. Notion reports about a second, GitHub tens of milliseconds, Figma ten seconds, and the difference is how much lag each was willing to carry into the swap. Sequence reconstructed from GitHub's six-step cutover, Notion's four-step failover and Vitess SwitchTraffic.
Diagram source

One caution about that sequence, from the source rather than the documentation. Vitess buffers queries during the switch so that applications see a pause instead of an error, but the buffering is best-effort in ways the docs do not emphasise. Issue 8462, opened by a Vitess maintainer in July 2021, states plainly that "buffering does not happen for queries in an existing transaction because that's never safe to do", that buffering engages only after a query has already failed, and that the mechanism "fails critically during re-sharding operations" because it is scoped per shard and cannot tell when a multi-shard reshard has finished. Issue 7059 is the user-visible consequence: an application receiving operation not allowed in state NOT_SERVING for roughly 65 requests during a cutover, from an engineer who wrote "I expected SQLNonTransientException won't be thrown to App". Plan for a small number of hard errors at the swap, and make sure the callers retry.

03

The decisions that matter

Six forks where the published accounts split, each with the condition that flips the answer. The first one is the only irreversible decision in the whole programme.

Figure 4 · Where the routing decision should live

No

Yes

Yes

No

No

Yes

Yes

Yes

No

Is the largest table
over ~1 TB or is vacuum
hurting availability?

Index, query and schema work first.
Percona 2009: bigger wins, lower cost

Does every hot table share
one natural containment key?

Do entities move
between tenants?

Per-table key sets grouped
into colocations. Figma

Single tenant key,
logical shards packed
onto few machines. Notion

Do you have a team that
can run a query router
for the next five years?

Build the proxy, keep control
of placement. Figma, GitHub

Adopt a sharding platform,
accept opaque placement.
Slack, Etsy

No

Yes

Yes

No

No

Yes

Yes

Yes

No

Is the largest table
over ~1 TB or is vacuum
hurting availability?

Index, query and schema work first.
Percona 2009: bigger wins, lower cost

Does every hot table share
one natural containment key?

Do entities move
between tenants?

Per-table key sets grouped
into colocations. Figma

Single tenant key,
logical shards packed
onto few machines. Notion

Do you have a team that
can run a query router
for the next five years?

Build the proxy, keep control
of placement. Figma, GitHub

Adopt a sharding platform,
accept opaque placement.
Slack, Etsy

Terminal nodes are actions, not opinions. The left branch is the one most teams reach for and the one Notion, Figma and Percona all warn about for different reasons.
Diagram source

Decision 1: one shard key for the whole database, or a set of keys per table?

Chosen
  • Notion: a single key, workspace id, because "each block belongs to exactly one workspace"
  • Figma: several keys, UserID, FileID, OrgID, grouped into colocations that share a layout
Rejected
  • Figma rejected the single org key outright
  • Slack moved off workspace, which concentrated whole customers on one shard
Flips when
  • Entities move between tenants. Steele: at Figma "data moves quite frequently between orgs, which makes that kind of sharding model quite hard"
  • Tenant sizes are heavy-tailed, so one key produces one shard you cannot split

Decision 2: can a row ever move to a different shard?

Chosen
  • Pinterest: no, ever. "Once a piece of data lands in a shard, it never moves outside that shard"
  • Capacity is added by opening new shard ranges, or by replicating a machine and splitting its range in the config
Rejected
  • Per-row rebalancing. Pinterest: "We hated moving data around, especially item by item, because it's prone to error"
Flips when
  • Tenants are the unit of load and their sizes diverge. Shopify built Ghostferry precisely to move one shop between pods, and reports imbalance improving from roughly 4x to 2x
  • Note the cost: Pinterest's rule removes an entire class of operation, and an entire class of incident

Decision 3: move the data with the application, or with the database?

Chosen
  • Stripe and Notion (2021): application-level dual writes plus a backfill
  • Notion (2023), Shopify, Vitess: replication or change data capture below the application
Rejected
  • Notion moved away from application dual writes for the second reshard, having found double-write throughput was "the primary bottleneck in our final switch-over" the first time
Flips when
  • Source and target are the same engine and version: use replication, it is faster and it cannot forget a code path
  • The data model changes shape, as at Stripe and Mercari: only the application knows how to write both shapes, so dual write is forced

Decision 4: build the router, or adopt a sharding platform?

Chosen
  • Figma built DBProxy; Notion drove PgBouncer directly; GitHub used ProxySQL for some moves
  • Slack and, from 2026, Etsy adopted Vitess
Rejected
  • Notion rejected Citus and Vitess because "the actual clustering logic is opaque, and we wanted control over the distribution"
  • Figma rejected CockroachDB, TiDB, Spanner and Vitess: "switching to any of these alternative databases would have required a complex data migration"
  • GitHub, an adopter, still says that "because of factors like deployment topology and read-your-writes support, we didn't choose Vitess as the tool to move database tables in every case"
Flips when
  • You need cross-shard queries and transactions as a routine capability rather than an exception
  • You have no team to own a query engine for five years. Etsy's platform migration took five years and about 2,500 pull requests with a platform to move onto

Decision 5: separate logical sharding from physical sharding, or do both at once?

Chosen
  • Figma: separate. Views make the data look sharded first, at "less than 10%" worst-case overhead, rolled out by percentage with a config rollback
  • Everyone else: one operation
Rejected
  • Figma explicitly rejected doing the "riskier distributed physical failover" before the low-risk application change had been proven in production
Flips when
  • Your engine cannot express the shard predicate cheaply. Measure it: Figma's number is an argument, not a general result
  • You are moving between engines, in which case there is no logical-only stage to run

Decision 6: how much downtime will you buy?

Chosen
  • Notion 2021: "five minutes of scheduled maintenance", and listed not optimising for zero downtime as one of three regrets
  • Notion 2023, GitHub, Figma: no scheduled window, stalls of one second, tens of milliseconds and ten seconds respectively
Rejected
  • Adyen's in-place repartitioning accepts an exclusive lock at the attach step, which is a scheduled window by another name
Flips when
  • You can genuinely obtain a window. Notion could in 2021 and could not in 2023, and the engineering difference between the two migrations is largely that
  • The cutover cost is dominated by the drain, so buying downtime buys you permission to carry lag into the swap, which is what makes the copier simpler
Summary of the six decisions, with the evidence for each.
DecisionChosen byRejected byStated reasonEvidence
Single tenant shard keyNotion, early SlackFigmaEntities move between tenants; tenant sizes are heavy-tailedPostgres FM 100, 2024
Rows never move shardPinterestShopifyItem-by-item movement is "prone to error"; but tenant balancing needs itPinterest, 2015
Application dual writeStripe, Mercari, Notion 2021Notion 2023Double-write throughput bottlenecked the switchoverNotion, 2021
Build the routerFigma, GitHub, NotionSlack, EtsyOpaque placement was unacceptable; migration cost of switching engines was notFigma, 2024
Logical before physicalFigmaAll othersMakes the reversible half of the change carry the rollout riskFigma, 2024
Deferred index buildNotion, FigmaDefault toolingIndexes make logical replication "really, really expensive"Notion, 2023
Verification by a second authorNotionUnstated elsewhereA verifier written by the migration's author shares its assumptionsNotion, 2021

The rule under all of it, which nobody names

Stripe's 2017 post describes a four-phase dual write: write both, backfill, move reads, move writes. Google's F1 paper describes a schema change protocol with intermediate states called delete-only and write-only, and proves that "many common schema changes can cause anomalies and database corruption" unless they are decomposed, with the whole argument resting on servers being "no more than one schema version behind". CockroachDB's October 2015 RFC reimplements exactly those states and rejects the alternative in one sentence: a global table lock "seems as difficult as the current proposal and much worse for the user experience".

These are the same protocol. In both cases the system has two representations of the truth, participants adopt the new one at different times, and correctness is preserved by inserting intermediate states such that no two participants are ever more than one state apart. Stripe's phase two exists for the same reason as F1's write-only state: a reader that has not yet moved must still find data that a writer that has already moved has written. I have not found a source that names this rule, so I will: the one-step rule. It is the reason you cannot collapse a migration from four deploys into two, no matter how much the schedule wants you to, and it is the question to ask of any migration plan you are reviewing. Which two components are more than one step apart, and for how long?

Figure 5 · The same protocol, twice: schema change and data migration

F1 delete-only, deletes maintain it

F1 write-only, Stripe phase 1 dual write

Stripe phase 2, backfill runs

Stripe phase 3, reads move

Stripe phase 4, old copy stops being written

Absent

DeleteOnly

WriteOnly

Backfilled

ReadsMoved

Public

Invariant: no two participants
more than one state apart

F1 delete-only, deletes maintain it

F1 write-only, Stripe phase 1 dual write

Stripe phase 2, backfill runs

Stripe phase 3, reads move

Stripe phase 4, old copy stops being written

Absent

DeleteOnly

WriteOnly

Backfilled

ReadsMoved

Public

Invariant: no two participants
more than one state apart

F1's states and Stripe's phases are the same decomposition of one unsafe transition into a sequence of safe ones. The invariant in both is that adjacent participants differ by at most one state. Sources: F1, VLDB 2013, CockroachDB RFC, 2015, Stripe, 2017.
Diagram source
04

What broke in production

Six published incidents, grouped into four classes. Note what is absent: none of them is the swap.

Class A: the shard key decides the read path, and the read path is not the one you modelled

Both of Slack's published database incidents are in this class, and they are the most transferable material in this guide, because the fault was designed in years before it fired and it fired without anyone touching the database.

Figure 6 · How a cache restart turned one shard key into a full-fleet scatter

Vitess keyspacesharded by userMemcachedApp serverClient bootVitess keyspacesharded by userMemcachedApp serverClient bootquery is by channel,data is sharded by user25% of cache nodes cycling=> nearly every user scattersload channel memberships1get channels2miss (node restarted, cacheempty)3scatter query to every shard4results after N shard round trips5boot completes, slowly6
Vitess keyspacesharded by userMemcachedApp serverClient bootVitess keyspacesharded by userMemcachedApp serverClient bootquery is by channel,data is sharded by user25% of cache nodes cycling=> nearly every user scattersload channel memberships1get channels2miss (node restarted, cacheempty)3scatter query to every shard4results after N shard round trips5boot completes, slowly6
The amplification is in the mismatch between the key the data is sharded on (user) and the key the query is expressed in (channel). The cache was hiding it. Reconstructed from Slack's incident report of 22 February 2022.
Diagram source
Postmortem

Slack, 22 February 2022: one missing cache entry queries every shard

AssumptionChannel membership sharded by user id is fine, because the cache absorbs the queries that are not expressed in that key.
What happenedConsul agent restarts on 25% of the fleet emptied memcached nodes at peak. "Since the data is sharded by user ID, even one channel missing from cache meant the application had to successfully run a query on every shard." Read load rose superlinearly with the miss rate.
Blast radiusBegan just after 06:00 Pacific; client boots degraded across the product; mitigation required throttling client boot requests.
FixA second copy of the data under a different key: they "modified the problematic scatter query to read from a table that is sharded by channel".
Design ruleFor every hot query, write down the key it filters on. Any query whose filter key is not the shard key is a fleet-wide scatter waiting for its cache to be cold, and your load test never runs cold.
Postmortem

Slack, October 2022: one tenant's bulk delete lands on one shard

AssumptionWork is spread across shards, so a large batch job is spread too.
What happenedA customer removed a large number of users; the forget-user job fanned out deletes. One shard held "6% of the user's subscription data" and took the concentrated write load. Replication lagged, then "the high volume of write load also led the Vitess tablets to run out of memory on the shard primary, which caused the kernel to OOM-kill the MySQL process".
Blast radiusAutomation entered "an infinite-loop of the primary failing, a replica being promoted to primary, a replacement replica being provisioned, trying (and failing) to catch-up".
FixManually provisioned larger replicas to break the loop; rewrote the job to query only relevant subscriptions; added throttling, circuit breaking and exponential backoff.
Design ruleSharding bounds steady-state load, not batch load. Every administrative job that iterates a tenant needs its own rate limit, because the shard map concentrates rather than spreads it.
Paper

DynamoDB: splitting a partition by size does not split its load

Assumption"The uniform distribution of throughput across partitions is based on the assumptions that an application uniformly accesses keys in a table and that splitting a partition for size equally splits performance."
What happenedTwo failure modes at once. "Hot partitions happened because customer workloads were not uniformly distributed"; and "throughput dilution happened when partitions that had been split to handle increased load ended up with so few keys that they could quickly max out their meager allocated capacity". Splitting made some tenants worse.
Blast radiusThrottling visible to customers who had provisioned enough aggregate capacity, across a decade of the service.
FixBursting, adaptive capacity, split for consumption rather than for size, and a global admission control service that splits or moves partitions before throttling occurs.
Design ruleSplit on the dimension that is saturating. If you split on bytes and your constraint is requests per second, each split halves the capacity available to the hot key without reducing its traffic.

Class B: adding capacity is the dangerous operation, not the migration

Both incidents in this class happened to teams who had already sharded successfully and were doing the routine thing afterwards. This is the class an architecture review never covers, because the review happens before the migration and this happens after it.

Postmortem

Monzo, 29 July 2019: six new nodes owned data they had not yet received

Assumption"New servers will start up and join the cluster, but otherwise remain inactive until we stream data to them." A single-node rehearsal had confirmed it.
What happenedOne configuration flag controlled two behaviours. The new servers "had joined the cluster, assumed responsibility for some parts of the data (certain partition keys to balance the load), but hadn't yet streamed it over". Reads that reached them returned nothing rather than failing.
Blast radiusCard transactions and customer tooling failed from 13:14; internal edge returned 404s at 13:29; root cause found at 14:13; most customers recovered by 15:08; full recovery at 23:00. Each new node took "approximately 8-10 minutes to remove safely".
FixCorrected the flag, added ownership and streaming metrics, wrote runbooks, and planned to split one large cluster into several smaller ones.
Design ruleRehearse capacity changes at the size you will run them. Monzo's test with one node could not break quorum; six nodes could, because "the data ownership had two or three members reallocated to the new nodes". The rehearsal was not smaller, it was a different event.
Postmortem

AWS DynamoDB, 20 September 2015: more partitions made the shard map too big to fetch

AssumptionRetrieving a storage server's membership from the metadata service is fast enough to be done on demand after a network event.
What happenedRapid adoption of secondary indexes, which carry "their own set of partitions", made membership lists "quickly double or triple". After a brief network disruption, many servers requested membership at once; responses "exceeded the retrieval and transmission time allowed by storage servers", so servers retried and removed themselves from service, which increased the load further.
Blast radiusRoughly five hours; error rates reached about 55% by 02:37 PDT; restored at 07:10 PDT; multiple dependent AWS services affected.
FixMore metadata capacity, monitoring on membership size rather than only latency, fewer membership requests, and segmentation of the metadata service into many instances.
Design ruleThe shard map is a data structure that grows with the number of shards, and something fetches it on the recovery path. Alert on its size, and make recovery not require fetching all of it at once.

Class C: the migration's load, not the migration's logic

Postmortem

GitHub, 10 to 11 September 2012: a schema migration triggered a failover cascade

AssumptionAn online schema migration is safe because it does not block; its resource cost was treated as background.
What happened"Monday's migration caused higher load on the database than our operations team has previously seen during these sorts of migrations. So high, in fact, that they caused Percona Replication Manager's health checks to fail on the master." The failover promoted a server with a cold buffer pool, which failed its own health checks. The next day a Pacemaker segfault produced a partition and two simultaneous master elections.
Blast radius"One hour and 46 minutes of downtime and another hour of significantly degraded performance." Worse: "16 of these repositories were private, and for seven minutes from 8:19 AM to 8:26 AM PDT on Tuesday, Sept 11th, were accessible to people outside of the repository's list of collaborators."
FixRemoved automated failover from the path, and treated the cluster manager's failure modes as first-class.
Design ruleA migration is a load test you did not schedule, run against your health checks. Before any long-running copy, ask what your failover automation will do if the copy makes the primary look unhealthy, and consider disabling it for the duration.
Decision record

Vitess VDiff v1: the safety net that could not finish

AssumptionComparing source and target is a routine step that can be run before the switch.
What happenedFor large tables the diff ran "hours and days" through a component intended as "a lightweight wrapper", requiring "a large amount of memory". Worse, it was not resumable: "If there is a network issue then we cannot resume the operation. A fresh snapshot has to be taken and we start VDiff from scratch."
Blast radiusNo outage. The damage is that teams switch traffic on partial verification, or delay the switch and carry two copies for longer.
FixVDiff2 runs on tablets, persists the last compared primary key, and supports stop, restart and resume.
Design ruleVerification must be resumable and must run where the data is. A comparator that restarts from zero after a network blip is a comparator that will be skipped under schedule pressure, which is exactly when you need it.

Class D: the absence

An absence, honestly stated

I did not find a public postmortem attributing data loss or corruption to the cutover itself: not to gh-ost's atomic swap, not to a Vitess SwitchTraffic, not to a PgBouncer repoint. I searched the failure vocabulary directly and through curated postmortem collections. Two readings are available. Either the swap is genuinely the well-solved part, which the design record supports: gh-ost's cut-over design enumerates each connection death and shows every one returns to the pre-cutover state, and notes that "replication only sees the RENAME". Or the failures exist and are not published, because a cutover that silently drops writes is discovered weeks later as a data-quality complaint, not as an outage. Both readings argue for the same control: an independent reconciliation job that runs after the swap and keeps running, rather than a diff that runs before it.

05

Numbers you can plan against

Everything quantitative in the corpus, with the organisation, the context and the date. The most useful column is the last one, because half of these will be wrong in three years and you should know which half.

Measured figures unless marked otherwise. Nothing here is a vendor benchmark.
MetricValueAtContextAs ofSource
Write stall at physical shard split10 sFigmaPartial availability on primaries only; replicas unaffected2024Figma
Write stall at cutovertens of msGitHubSix-step replication-topology cutover, busiest tables2021GitHub
Worst user-visible stall~1 sNotionPer-database failover, 32 to 96 databases2023Notion
Scheduled downtime, first sharding5 minNotionListed afterwards as a regret2021Notion
Logical shards / physical databases480 / 32Notion15 logical schemas per machine, so later moves re-pack rather than re-hash2021Notion
Databases after second reshard96NotionTrigger was shards "exceeding 90% CPU utilization at peak"2023Notion
CPU and IOPS after reshard~20%NotionDown from over 90% at peak2023Notion
Backfill time, with vs without indexes3 d → 12 hNotionDeferring index creation during logical replication2023Notion
Backfill time, first sharding3 daysNotionProduction backfill via audit-log double write2021Notion
Query overhead of shard views<10%FigmaWorst case; "minimal" in most cases2024Figma
Time to shard the first table~9 monthsFigmaIncludes building DBProxy; first sharded tables live September 20232024Figma
Database stack growth~100×FigmaSince 20202024Figma
Peak query rate after migration2.3M QPSSlack2M reads, 300K writes; median 2 ms, p99 11 ms2020Slack
Migration duration to 99% of traffic~3 yearsSlackStarted 2017; 0 QPS to 2.3M QPS on the new platform2020Slack
Query rate before partitioning950K QPSGitHubSingle cluster: 900K on replicas, 50K on the primary2019GitHub
Query rate after, and per-host load1.2M QPS, load halvedGitHubSame tables across several clusters2021GitHub
Sharded fleet scale1,000 shards / 425 TB / 1.7M rpsEtsyApplication-level sharding from ~2010, reported secondhand2026InfoQ on Etsy
Platform migration effort5 years, ~2,500 PRsEtsyMoving shard routing into Vitess vindexes; 6,000 queries touched2026InfoQ on Etsy
Bulk copy throughput3.2M records/sDiscordCustom Rust migrator; three months of work reduced to nine days2023Discord
Node count after re-platforming177 → 72DiscordCassandra to ScyllaDB; read p99 40-125 ms to 15 ms2023Discord
Shard imbalance, before and after balancing~4× → ~2×ShopifyRatio of most-used to least-used shard, in a simulation of their rebalancer2021Shopify
Single-table repartition scale~100 TBAdyenOne table across shards; batches of 100,000 rows2024Adyen
Failed requests observed during a cutover~65Vitess userNOT_SERVING errors despite buffering; single reported instance2020Vitess issue 7059
Incident duration, capacity addition~10 hMonzo13:10 to 23:00; majority restored by 15:082019Monzo
Incident duration, metadata overload~5 hAWS DynamoDBPeak error rate ~55%2015AWS
Peak request rate, auto-sharded store89.2M rpsDynamoDB2021 Prime Day; upper bound on what automatic partitioning achieves2022USENIX ATC
Read these carefully

Measured: every row above comes from the operator of the system, except Etsy's, which is InfoQ reporting Etsy's own account because Etsy's blog is not machine-fetchable. Derived: nothing. Unknown: currency. No account in this corpus publishes what a resharding programme cost in money. The honest planning figure is calendar time with a team attached: nine months to the first sharded table at Figma, three years at Slack, five years at Etsy. If you need a budget line, price those durations against a team of four to eight engineers and treat anything faster as a claim to verify.

Stale risk: the QPS and node-count rows age fastest and are useful only as ratios. The stall durations and the backfill-versus-index result are mechanism, and should still hold. The Etsy row is a 2026 snapshot of a system built in 2010, which is its own lesson about how long a shard key lives.

06

The evidence wall

Every source behind this page, graded. Filter by kind. The full ledger, one row per claim with the supporting quote, ships beside this file as sources.md.

Postmortem Slack2022-04

Slack's Incident on 2-22-22

A cache restart on 25% of the fleet turned every client boot into a query against every shard, because channel membership is sharded by user id and the query filters by channel.

Carry forwardEnumerate hot queries by their filter key; any mismatch with the shard key is a latent fleet-wide scatter.
slack.engineering/slacks-incident-on-2-22-22
Postmortem Slack2023-11

The Query Strikes Again

One customer's bulk user removal concentrated writes on the shard holding 6% of that tenant's data, OOM-killed the primary, and put replica replacement into an infinite loop.

Carry forwardAdministrative batch jobs need their own rate limits; the shard map concentrates them rather than spreading them.
slack.engineering/the-query-strikes-again
Postmortem Monzo2019-09

We had issues with Monzo on 29th July

Six new nodes took ownership of partition ranges before the data had streamed to them, so reads succeeded and returned nothing. One flag governed two behaviours; the single-node rehearsal could not reproduce it.

Carry forwardRehearse capacity additions at production cardinality; a smaller rehearsal is a different event, not a safer one.
monzo.com/blog/2019/09/08/why-monzo-wasnt-working-on-july-29th
Postmortem AWS2015-09

Summary of the Amazon DynamoDB Service Disruption

Secondary indexes multiplied partition counts, membership lists doubled or tripled, and after a network blip the metadata fetch itself exceeded the time storage servers allowed.

Carry forwardMonitor the size of the shard map, not only the latency of fetching it, and never require a full fetch on the recovery path.
aws.amazon.com/message/5467D2
Postmortem GitHub2012-09

GitHub availability this week

A zero-downtime schema migration produced load beyond anything the team had seen, failed the primary's health checks, and started a failover cascade that ended in a split brain and sixteen private repositories briefly readable by strangers.

Carry forwardAsk what your failover automation will do while a long copy is running, and consider taking it out of the loop for the duration.
github.blog/2012-09-14-github-availability-this-week
Eng blog Notion2021-10

Herding elephants: lessons learned from sharding Postgres

480 logical shards on 32 machines, keyed by workspace id, migrated with an audit-log double write, dark reads and five minutes of scheduled downtime. Names three regrets, including sharding too late.

Carry forwardPack many logical shards onto few machines so the next capacity step is re-packing rather than re-hashing.
notion.com/blog/sharding-postgres-at-notion
Eng blog Notion2023-07

The Great Re-shard

32 to 96 databases with no scheduled downtime, using Postgres logical replication, dark reads, a PgBouncer pause and a replication-direction flip. Deferring index creation cut the copy from three days to twelve hours.

Carry forwardThe copy is bounded by index maintenance, not by bytes. Build indexes after the data lands.
notion.com/blog/the-great-re-shard
Eng blog Figma2024-03

How Figma's Databases Team Lived to Tell the Scale

The only account that separates logical sharding (views, percentage rollout, config rollback) from physical sharding (the irreversible failover), and the only one that publishes the resulting stall: ten seconds on primaries.

Carry forwardMake the reversible half of the change carry the rollout risk, and measure the overhead of the abstraction before committing to it.
figma.com/blog/how-figmas-databases-team-lived-to-tell-the-scale
Eng blog Slack2020-12

Scaling Datastores at Slack with Vitess

Three years from 0 to 2.3M QPS on Vitess, a shard-key change from workspace to channel, and a parallel double-read diffing system to prove semantics matched.

Carry forwardBudget a platform migration in years, and expect to become a contributor to the platform you adopted.
slack.engineering/scaling-datastores-at-slack-with-vitess
Eng blog GitHub2021-09

Partitioning GitHub's relational databases to handle scale

Vertical partitioning of a 950K QPS cluster, with a six-step write cutover taking tens of milliseconds, and a candid note that Vitess was not the right tool for every move.

Carry forwardWhen the move is a replication-topology change rather than a data copy, the cutover cost approaches zero.
github.blog/engineering/infrastructure/partitioning-githubs-relational-databases-scale
Eng blog Stripe2017-02

Online migrations at scale

The canonical four-phase dual write, with backfill run offline over database snapshots and continuous verification using Scientist against live production reads.

Carry forwardVerification belongs in the live read path, not in a one-off batch diff, because that is where the disagreement shows up.
stripe.com/blog/online-migrations
Eng blog Shopify2021-09

Shard Balancing: Moving Shops with Zero-Downtime at Terabyte-scale

Ghostferry batch-copies one tenant's rows while tailing the binlog, then cuts over when the queue is seconds deep. Verifiers run before, during and after; the algorithm is written in TLA+.

Carry forwardIf tenants are the unit of load, per-tenant movement is the balancing primitive, and it needs verification at three points, not one.
shopify.engineering/mysql-database-shard-balancing-terabyte-scale
Eng blog Discord2023-03

How Discord Stores Trillions of Messages

Hot partitions degraded a quorum-replicated cluster; the fix was a re-platform with dual writes and a custom Rust migrator at 3.2M records per second, nine days instead of three months.

Carry forwardMigration throughput is a build decision. If the copy will take months, the copier is the thing to optimise, not the schedule.
discord.com/blog/how-discord-stores-trillions-of-messages
Eng blog Pinterest2015

Sharding Pinterest: How we scaled our MySQL fleet

The strict alternative: the shard id lives in the object id, and "once a piece of data lands in a shard, it never moves outside that shard". Capacity comes from opening ranges or splitting a machine's range after replication.

Carry forwardForbidding row movement removes a class of operation and its incidents, at the cost of never being able to rebalance a hot tenant.
medium.com/pinterest-engineering/sharding-pinterest
Eng blog Adyen2024-11

Efficiently repartitioning large tables in PostgreSQL

The in-machine analogue: batches of 100,000 rows, detach and attach at the end, and an explicit warning that rows changing during the load need triggers or change data capture.

Carry forwardRepartitioning inside one machine has the same six stages and the same race; the exclusive lock at the end is a scheduled window by another name.
adyen.com/knowledge-hub/efficiently-repartitioning-large-tables-in-postgresql
Eng blog Mercari2024-11

Designing a Zero Downtime Migration with Strong Data Consistency, Part IV

Enumerates three named race conditions in a dual write and builds a consistency checker on a non-locking read-only transaction. Hundreds of billions of records in scope.

Carry forwardWrite down the race conditions of your dual write explicitly. If you cannot enumerate them, you have not designed it.
engineering.mercari.com/en/blog/entry/20241113
Eng blog Amazon Science2022-10

Lessons learned from 10 years of DynamoDB

The clearest published statement that splitting by size does not split load, with the two named failure modes: hot partitions and throughput dilution.

Carry forwardSplit on the saturating dimension. Splitting on bytes when the constraint is requests makes the hot key worse.
amazon.science/blog/lessons-learned-from-10-years-of-dynamodb
Eng blog Percona2009-11

Shard early, shard often

The dissent, and it predates every other source here. Morgan Tocker argues the gains from query, index and schema work are an order of magnitude larger than the gains from sharding, and that sharding first makes tuning harder.

Carry forwardThe first question in a sharding review is whether the workload has been tuned, and the answer is usually no.
percona.com/blog/2009/11/16/shard-early-shard-often
Source gh-ostrepo

gh-ost doc/cut-over.md

Why the naive rename leaves "a brief period of time where your table just does not exist", and how a two-connection blocking swap removes that window while keeping the operation atomic from replication's point of view.

Carry forwardThe correct swap is an established solved problem. If your plan has a window where the table does not exist, you have chosen the wrong algorithm.
github.com/github/gh-ost/blob/master/doc/cut-over.md
Decision record gh-ost2016-06

Issue 82: safe, blocking, atomic, pure-MySQL cut-over

The design argument itself: each connection death is enumerated and shown to return to the pre-cutover state. "If both C10 and C20 die, no problem." Replication sees only the RENAME.

Carry forwardA cutover design is finished when you can name what happens if each participant dies at each step, and every answer is a safe state.
github.com/github/gh-ost/issues/82
Source Vitess2021-07

Issue 8462: request buffering during sharding

A maintainer's own account of the limits: buffering is reactive, scoped per shard, primary only, and "does not happen for queries in an existing transaction because that's never safe to do". It "fails critically during re-sharding operations".

Carry forwardTransparent cutover is not transparent for in-flight transactions. Callers must retry, and you must say so in the runbook.
github.com/vitessio/vitess/issues/8462
Source Vitess2020-11

Issue 7059: exceptions thrown to the application during cutover

The user-visible version of the same fact: about 65 requests failed with operation not allowed in state NOT_SERVING during a reshard cutover, from an engineer who expected buffering to hide it.

Carry forwardBudget for a small number of hard errors at the swap and prove your clients survive them before the real one.
github.com/vitessio/vitess/issues/7059
Source Vitess2024-03

PR 15393: VStream automatic resume after reshard (closed unmerged)

A proposal to let change-stream consumers resume automatically after a reshard, opened and closed by its author within a day. The underlying fact stands: a reshard breaks downstream consumers of the change stream, and that consequence is rarely in the migration plan.

Carry forwardList everything subscribed to your database's change stream before you reshard. They are participants in the migration whether or not anyone told them.
github.com/vitessio/vitess/pull/15393
Decision record Vitess2022-04

RFC 10134: reimplementing VDiff on tablets

Verification at scale took "hours and days", needed large memory in a component meant to be lightweight, and could not resume: "A fresh snapshot has to be taken and we start VDiff from scratch."

Carry forwardAn unresumable verifier is a verifier that gets skipped. Make it restartable and run it where the data lives.
github.com/vitessio/vitess/issues/10134
Decision record CockroachDB2015-10

RFC: online schema change

An independent reimplementation of F1's protocol, with DELETE_ONLY, WRITE_ONLY and PUBLIC states, backfill in small transactions, and the global-lock alternative rejected in one sentence.

Carry forwardTwo independent teams reached the same decomposition. Treat the staged-state protocol as the default, not as a sophistication.
github.com/cockroachdb/cockroach/blob/master/docs/RFCS/20151014_online_schema_change.md
Source Shopifyrepo

Ghostferry README and TLA+ specification

A live data mover with a formal specification, and an unusually honest caveat next to it: "the specification might not be entirely correct as proofs remain elusive".

Carry forwardA formal model is a design aid, not a guarantee. Keep the runtime verifiers even when you have the model.
github.com/Shopify/ghostferry
Paper Google2013

Online, Asynchronous Schema Change in F1

Proves that common schema changes corrupt data when servers hold different versions, and that decomposing them into intermediate states is safe "so long as all servers are no more than one schema version behind".

Carry forwardThe one-step rule. Ask of any migration plan which two participants are more than one state apart, and for how long.
research.google/pubs/online-asynchronous-schema-change-in-f1
Paper Amazon2022

Amazon DynamoDB: A Scalable, Predictably Performant NoSQL Database Service

The engineering account behind automatic partitioning at 89.2M requests per second, and the source of the partition-splitting lessons cited above.

Carry forwardAutomatic resharding is achievable, and it took a decade of admission-control work to make splitting safe.
amazon.science/publications/amazon-dynamodb
Paper Zeng et al.2024-04

SLSM: lazy schema migration on shared-nothing databases

States the cost of the staged-state protocol plainly: it "leads to delays in the deployment of new schemas since it requires waiting for massive data backfill", and proposes lazy migration instead.

Carry forwardThe backfill, not the state machine, is what makes migrations slow. Research is aimed at removing it; production is not there yet.
arxiv.org/abs/2404.03929
Talk Postgres FM2024-06

Episode 100: To 100TB, and beyond!

Figma, Notion and Adyen in one round table. The clearest statement of the shard-key disagreement, plus the finding that indexes make logical replication "really, really expensive", and Notion's engineer saying four months in that "this is a lot".

Carry forwardPractitioners who have done this converge on starting earlier and disagree on the key. The key is context; the timing is not.
postgres.fm/episodes/to-100tb-and-beyond/transcript
Talk SE Radio2022-05

Episode 510: Deepthi Sigireddi on how Vitess scales MySQL

On why reshard is deliberately manual: "There is a lot of human intervention or orchestration in this process, but that is somewhat by design because re-sharding is somewhat of a scary thing to do."

Carry forwardReverse replication is the feature that makes the switch a decision rather than a commitment. Turn it on and prove the rollback.
se-radio.net/2022/05/episode-510
Case study Etsy via InfoQ2026-04

Etsy migrates a 1,000-shard, 425 TB architecture to Vitess

A 2010-era application-level scheme with "more than 30 different IDs" as sharding keys, ported onto Vitess by writing custom vindexes rather than moving data. Five years and about 2,500 pull requests.

Carry forwardYou can move the routing without moving the rows, and on a fifteen-year-old scheme that is the only affordable path.
infoq.com/news/2026/04/etsy-vitess-sharding-migration
Vendor doc Vitessv25 docs

Reshard workflow reference

The operation as a product: Create, VDiff, SwitchTraffic, ReverseTraffic, Complete, with reverse replication on by default and a lag bound that refuses the switch.

Carry forwardIf you are building this yourself, this page is the checklist of operations your runbook needs.
vitess.io/docs/25.0/reference/vreplication/reshard
Vendor doc Vitessv22 docs

How traffic is switched (VReplication internals)

Documents the topology and denylist changes at cutover, and notably does not state the write-stall duration or the buffering limits, which is why the issue tracker above is the better source for both.

Carry forwardWhen vendor docs are silent about a duration, that number lives in the issue tracker, not in the manual.
vitess.io/docs/22.0/reference/vreplication/internal/cutover
07

Build a miniature, then productionise it

Seven rungs. The line between toy and real is at rung four, where you stop moving data and start proving you moved it correctly.

Two shards and a routing function

One table, two Postgres or MySQL instances, and a function in the application that maps a key to an instance. Write a load generator that inserts and reads by key.

Done when: you can point at any row and say which instance holds it without querying either.  Teaches: routing is the first artefact, not the last.

Move the routing out of the application

Put the mapping in a config file or a key-value store that the application reads at runtime, the way Pinterest keeps its shard-to-host table in ZooKeeper. Change the map while the load generator is running.

Done when: you can move a shard's traffic to a different instance without a deploy.  Teaches: the precondition in section two; everything later is an edit to this map.

Copy a shard while it is being written

Add a third instance. Capture a change-stream position, bulk copy one shard's rows, then replay changes from that position. Deliberately let the copy take minutes while writes continue.

Done when: a row written during the copy exists on the target with the same value.  Teaches: the copy and the live writes race, and the position you captured is the whole correctness argument.

Prove it, then break the proof

Write a comparator: sample rows from both sides and diff them, then run it continuously against live reads the way Stripe used Scientist. Now inject a deliberate mismatch, for example by dropping every hundredth replayed change.

Done when: the comparator finds your injected corruption before you tell it where to look.  Teaches: an unexercised verifier is decoration. This is the rung where the exercise stops being a toy.

Cut over, then roll back

Stop writes, drain the tailer to zero lag, repoint the map, resume. Then start replication from the new instance back to the old one and roll the whole thing back with the load generator still running.

Done when: you have cut over and back twice with zero lost writes, and you can state the stall duration at p100.  Teaches: rollback after a cutover is a data problem, and the reverse channel is what turns it into a routing problem.

Make it fail the way production fails

Four experiments. Hold a long-running transaction open across the swap. Kill the copier mid-batch. Send 90% of your load to one key. Then take the cache away from a read path whose filter key is not the shard key, and watch the fan-out.

Done when: you can predict which of the four produces an error your client cannot retry.  Teaches: the Slack incidents, at laptop scale.

Add capacity, which is the operation you will actually run

Go from three instances to five while everything is running. Instrument the shard map's size, the per-shard load spread, and the time a new instance spends owning data it has not yet received.

Done when: your dashboard would have caught Monzo's failure, meaning it shows ownership and data presence as two separate signals.  Teaches: the migration happens once; this happens every quarter for the life of the system.

08

Keep hunting

The queries that actually produced the material above. The vocabulary matters more than the operators: engineers who have done this write "cutover", "backfill", "dark reads" and "shard key", never "sharding best practices".

Production accounts

  • "sharding" "lessons learned" postgres "we" -tutorial
  • intitle:"how we" sharded OR resharded database zero downtime
  • "logical shards" "physical" migration blog how many shards
  • "dual write" backfill "read path" migration engineering blog

Incidents, which is where the value is

  • postmortem "adding nodes" OR "scaling up" cluster outage incident report
  • "hot shard" OR "hot partition" incident "shard key" engineering
  • site:slack.engineering incident datastore shard
  • "schema migration" postmortem failover health check load

The argument, not the conclusion

  • path:docs/RFCS "schema change" OR resharding
  • repo:vitessio/vitess is:issue reshard cutover buffering
  • repo:vitessio/vitess is:pr is:closed is:unmerged reshard
  • "alternatives considered" sharding "we chose" design doc

Chaining that worked

  • api.github.com/search/issues?q=repo:ORG/REPO+is:pr+is:closed+is:unmerged+TERM
  • "delete only" "write only" schema change intermediate states
  • postgres.fm OR se-radio transcript sharding shard key
  • "we hated moving data" OR "never moves outside that shard"

Two notes on method, because both cost time here. GitHub's web search will not filter closed unmerged pull requests reliably from a browser, but the REST search endpoint will, and that is how PR 15393 surfaced. And several of the best primary sources refuse automated fetches: Etsy's Code as Craft returns 403, and the USENIX and ACM PDFs return 403 to some clients while resolving fine in a browser. When a source is unreachable, say so and cite the accessible report rather than quietly dropping the claim.

09

References

  1. Garrett Fidalgo, Herding elephants: lessons learned from sharding Postgres at Notion Notion, 6 October 2021. Checked 2026-08-29.
  2. Arka Ganguli, Tanner Johnson, Ben Kraft, Nathan Northcutt, The Great Re-shard Notion, 17 July 2023. Checked 2026-08-29.
  3. Sammy Steele, How Figma's Databases Team Lived to Tell the Scale Figma, 14 March 2024. Checked 2026-08-29.
  4. Arka Ganguli, Guido Iaquinti, Maggie Zhou, Rafael Chacón, Scaling Datastores at Slack with Vitess Slack Engineering, 1 December 2020. Checked 2026-08-29.
  5. Laura Nolan, Glen D. Sanford, Jamie Scheinblum, Chris Sullivan, Slack's Incident on 2-22-22 Slack Engineering, 26 April 2022. Checked 2026-08-29.
  6. Emad Mokhtar, Eduardo Ortega, Kevin Van, The Query Strikes Again Slack Engineering, 15 November 2023. Checked 2026-08-29.
  7. Thomas Maurer, Partitioning GitHub's relational databases to handle scale The GitHub Blog, 27 September 2021. Checked 2026-08-29.
  8. Jesse Newland, GitHub availability this week The GitHub Blog, 14 September 2012. Checked 2026-08-29.
  9. Jacqueline Xu, Online migrations at scale Stripe, 2 February 2017. Checked 2026-08-29.
  10. Paarth Madan, Shard Balancing: Moving Shops Confidently with Zero-Downtime at Terabyte-scale Shopify Engineering, 24 September 2021. Checked 2026-08-29.
  11. Ghostferry README and TLA+ specification Shopify, repository read 2026-08-29. Checked 2026-08-29.
  12. Bo Ingram, How Discord Stores Trillions of Messages Discord, 6 March 2023. Checked 2026-08-29.
  13. Marty Weiner, Sharding Pinterest: How we scaled our MySQL fleet Pinterest Engineering, 2015 (the post states the system had then been in production "for 3.5 years" after an early-2012 launch). Checked 2026-08-29.
  14. Cagri Biroglu, Efficiently repartitioning large tables in PostgreSQL Adyen, 27 November 2024. Checked 2026-08-29.
  15. resotto, Designing a Zero Downtime Migration Solution with Strong Data Consistency, Part IV Mercari Engineering, 13 November 2024. Checked 2026-08-29.
  16. Monzo, We had issues with Monzo on 29th July Monzo, 8 September 2019 (incident 29 July 2019). Checked 2026-08-29.
  17. Summary of the Amazon DynamoDB Service Disruption and Related Impacts in the US-East Region Amazon Web Services, September 2015. Checked 2026-08-29.
  18. Somu Perianayagam and Akshat Vig, Lessons learned from 10 years of DynamoDB Amazon Science, 21 October 2022. Checked 2026-08-29.
  19. Elhemali et al., Amazon DynamoDB: A Scalable, Predictably Performant, and Fully Managed NoSQL Database Service USENIX ATC 2022. Checked 2026-08-29.
  20. Ian Rae, Eric Rollins, Jeff Shute, Sukhdeep Sodhi, Radek Vingralek, Online, Asynchronous Schema Change in F1 PVLDB 6(11), 2013. Checked 2026-08-29. PDF.
  21. Zhilin Zeng et al., SLSM: An Efficient Strategy for Lazy Schema Migration on Shared-Nothing Databases arXiv, 5 April 2024. Checked 2026-08-29.
  22. CockroachDB RFC: online schema change Cockroach Labs, 14 October 2015. Checked 2026-08-29.
  23. gh-ost cut-over documentation GitHub, repository read 2026-08-29. Checked 2026-08-29.
  24. Shlomi Noach, Describing safe, blocking, atomic, pure-mysql cut-over phase (gh-ost issue 82) GitHub, 26 June 2016. Checked 2026-08-29.
  25. Vitess issue 7059: exception thrown to app during resharding cutover vitessio/vitess, 20 November 2020. Checked 2026-08-29.
  26. Vitess issue 8462: request buffering during sharding vitessio/vitess, 13 July 2021. Checked 2026-08-29.
  27. Vitess RFC 10134: VDiff2, reimplementing VDiff on tablets vitessio/vitess, 24 April 2022. Checked 2026-08-29.
  28. Vitess PR 15393: VStream, allow automatic resume after reshard (closed unmerged) vitessio/vitess, opened 1 March 2024, closed 2 March 2024. Checked 2026-08-29.
  29. Vitess documentation: Reshard Vitess v25 docs. Checked 2026-08-29.
  30. Vitess documentation: How Traffic Is Switched Vitess v22 docs. Checked 2026-08-29.
  31. Postgres FM episode 100, To 100TB, and beyond! (transcript), with Sammy Steele, Arka Ganguli and Derk van Veen Postgres FM, 7 June 2024. Checked 2026-08-29.
  32. Software Engineering Radio episode 510, Deepthi Sigireddi on how Vitess scales MySQL SE Radio, 4 May 2022. Checked 2026-08-29.
  33. Renato Losio, Etsy Migrates 1000-Shard, 425 TB MySQL Sharding Architecture to Vitess InfoQ, 11 April 2026, reporting Etsy's Code as Craft series, which is not machine-fetchable. Checked 2026-08-29.
  34. Morgan Tocker, Shard early, shard often Percona, 16 November 2009. Checked 2026-08-29.