An incident report, resolved

Sync paused for 2 hours on Tuesday 13 October

Reading time about 5 minutesWritten 2026-10-15For: the whole teamExample with made-up data

A database change locked the notes table and a restart loop kept it locked, so sync paused for two hours, and no note was lost.

Keep in your head (only these)
  1. Sync paused from 14:10 to 16:10. No note was lost.
  2. A restart loop kept the notes table locked.
  3. Migrations no longer run at start-up. Two follow-ups are open.

What broke, who felt it, and when?

Fixed, 2 follow-ups open
Syncstopped for the phone and web apps
190people were syncing when it stopped
2 hpaused, 14:10 to 16:10
0notes lost. 3 of 5 follow-ups done
  1. 14:05A deploy starts a change to the notes table.
  2. 14:10Sync stops answering.
  3. 15:20The team finds the cause.
  4. 16:10Sync is normal again.

Take: sync was down for two hours, nobody lost a note, and the fix is already live.

  1. What broke, and who felt it?Writing never stopped. Copying between devices did.
  2. Where did the two hours go?15 minutes to notice, 55 to find the cause, 50 to fix.
  3. Why did one change take two hours?Five links in a chain, and the one that failed.
  4. What is fixed, and what is still open?Five follow-ups: three done, one under way, one to rule on.
  5. What stops it happening again?Each follow-up guards a different weak point.

Chapter 1 of 5, about 1 minute

What broke, and who felt it?

Before this: sync paused from 14:10 to 16:10, and no note was lost.

The notes app keeps working without a connection. Reading and writing never stopped. What stopped was the part that copies changes between a person's devices.

14:10 to 16:10
  • A banner said "Waiting to sync".
  • Notes could still be read and written.
  • Changes waited in a queue on each device.
  • A second device showed old notes.
By 16:40
  • Every queued change had arrived.
  • 0 notes were lost.
  • 0 notes were duplicated.
  • The banner went away on its own.

Take from this: the cost was a two-hour delay, and the people who felt it most use two devices.

Show the numbers behind the tiles
  • 190 people had the app open at 14:10.
  • 74 of them wrote at least one note during the pause.
  • 412 changes waited in queues, and all 412 arrived by 16:40.
  • About 3,400 people use the app in a week, so this was about 6 in 100 of them.

Remember: writing never stopped, so the cost was a two-hour delay, not lost notes.

Chapter 2 of 5, about 1 minute

Where did the two hours go?

Before this: the app kept working, so the cost was a two-hour delay.

The two hours fall into three parts. The alert reached the team 15 minutes after sync stopped. Finding the cause took 55 minutes. Shipping the fix took 50.

15 minbefore the alert reached the team
55 minto find the cause
50 minto ship the fix, 25 of them a deploy

Take from this: almost half the time went on finding the cause, so a clearer alert saves the most.

The cause hid well. The logs showed time-outs, not a lock, and each restart cleared the one clue the database held.

Show the afternoon minute by minute
14:05  Deploy starts. The start-up script begins the index build.
14:10  Sync stops answering. The health check restarts the server.
14:22  Second restart. The build begins again. Restarts repeat every 12 minutes.
14:25  The sync alert reaches the team.
15:20  Cause found: every start re-runs the unfinished build.
15:25  A deploy that skips migrations at start-up begins.
15:50  That deploy finishes and the server starts clean.
16:00  Saves work again. The retry backlog drains.
16:10  Sync is normal.
16:40  The last queued change arrives.

Remember: finding the cause took 55 of the 120 minutes, so a clearer alert saves the most time.

Chapter 3 of 5, about 2 minutes

Why did one change take two hours?

Before this: almost half the time went on finding a cause that the logs did not show.

One change set off four more, and the last one kept the others going. Read the chain from left to right. The red step turned minutes into hours.

Migration
A change to the shape of the database, run as code.
Index
A lookup table that makes searches fast. Building one on a big table blocks saves.
Lock
A hold that makes other work wait while a change runs.
  1. Index addedA deploy adds it for sharing, on the live notes table.
  2. Table locksThe build blocks every save for about 9 minutes.
  3. Phones retryRequests time out at 30 seconds, and 190 apps retry at once.
  4. Server restartsThe health check sees no answer and restarts it.
  5. Build starts overEach start re-runs the unfinished change, so the lock never clears.

Take from this: the build alone would have ended in nine minutes. The restart loop stretched it to two hours.

Show the numbers behind the chain
  • The rehearsal copy held 2,000 notes, and the build took about 1 second.
  • The live table held 410,000 notes, and the build needed about 9 minutes without a break.
  • The server restarted every 12 minutes, so the build never reached its end.
  • The server allows 50 connections. 190 retrying apps used them all.

Remember: the restart loop, not the index, turned a nine-minute lock into two hours.

Chapter 4 of 5, about 1 minute

What is fixed, and what is still open?

Before this: a restart loop turned a nine-minute lock into two hours.

The review produced five follow-ups. The one that stopped the outage is already done.

  • Stop migrations at start-upDone on Wednesday 14 October. Someone now starts each migration by hand, once.
  • Rehearse on a full-size copyDone. The test copy holds 400,000 notes, so a slow change shows up before it ships.
  • Alert after 3 minutesDone. The sync alert used to wait for 10 minutes of failures.
  • Build indexes in the backgroundUnder way. Ted is changing the build so saves keep working. Due Friday 23 October.
  • Quiet hours for deploysNeeds a ruling from Mara: no deploys from 13:00 to 17:00 on weekdays?

Take from this: the outage cannot repeat as it happened, and the two open items shrink the next one.

Show the change to the start-up script
- run-migrations && start-server
+ start-server
# migrations now run only by hand:  npm run migrate

Remember: three follow-ups are done, one is under way, and one waits for Mara's ruling.

Chapter 5 of 5, about 1 minute

What stops it happening again?

Before this: three follow-ups are done, and two are still open.

Each follow-up guards a different weak point. A slow change can still happen, but it can no longer stop sync for two hours.

On 13 October
  • The rehearsal copy held 2,000 notes.
  • The start-up script ran migrations.
  • The alert waited for 10 minutes of failures.
  • An index build blocked every save.
From 14 October
  • The rehearsal copy holds 400,000 notes.
  • A person starts a migration by hand, once.
  • The alert fires after 3 minutes.
  • Index builds will run in the background, due 23 October.

Take from this: four things changed since Tuesday, and a fifth, quiet hours for deploys, waits on a ruling.

Remember: each follow-up guards a different weak point, so one slip cannot take sync down for two hours again.

Recap

  1. Writing never stopped, so the cost was a two-hour delay, not lost notes.
  2. Finding the cause took 55 of the 120 minutes, so a clearer alert saves the most time.
  3. The restart loop, not the index, turned a nine-minute lock into two hours.
  4. Three follow-ups are done, one is under way, and one waits for Mara's ruling.
  5. Each follow-up guards a different weak point, so one slip cannot take sync down for two hours again.