Checkout Outage on October 18

Incident report: 47 minutes on Sunday morning

Outage length47 minlongest this year (bad)
Failed checkout attempts27,90067% of attempts (bad)
Orders lost (estimate)10,500about $672,000 (bad)
Customers charged twice0confirmed by Finance (good)

In plain terms

Every time a customer clicks Pay, our checkout service borrows one of a fixed number of shared lines to the orders database, saves the order, and hands the line back. We normally allow 200 lines at once. A routine settings change on Sunday morning cut that number to 20. Sunday morning is one of our busiest times, so the 20 lines were used up within minutes and everyone else got an error.

The change was meant for our test environment, which has very little traffic. It was copied into the live settings by mistake. Fixing it took 47 minutes because our usual way of undoing a change refused to run while checkout was unhealthy.

What happens when a customer clicks Pay
What happens when a customer clicks Pay5 nodes. Customer → Website or app (clicks Pay); Website or app → Checkout service; Checkout service → Shared database lines (needs a line); Shared database lines → Orders database (saves order)cust → site: clicks Paysite → coco → pool: needs a linepool → db: saves orderUSERCustomerSVCWebsite or appSVCCheckout serviceSVCShared database linesLimit was 200, became 20DBOrders databaseCLICKS PAYNEEDS A LINESAVES ORDER
What happens when a customer clicks Pay5 nodes. Customer → Website or app (clicks Pay); Website or app → Checkout service; Checkout service → Shared database lines (needs a line); Shared database lines → Orders database (saves order)cust → site: clicks Paysite → coco → pool: needs a linepool → db: saves orderUSERCustomerSVCWebsite or appSVCCheckout serviceSVCShared database linesLimit was 200, became 20DBOrders databaseCLICKS PAYNEEDS A LINESAVES ORDER
USERSVCDB

Impact

measurevaluenote
Checkout attempts during the outage41,800Normal Sunday-morning volume
Attempts that failed27,90067%; the rest got through as lines were freed up
Customers who gave up10,500Did not complete an order within 24 hours
Estimated lost sales$672,00010,500 orders at the $64 average order
Support tickets opened1,860Mostly 'my payment failed'; 94% answered within 4 hours
Customers charged twice0Failed attempts never reached the payment step
Payment card data exposed0Not involved in this incident

The chart shows the share of checkout attempts that succeeded. It dropped from 99% to about 30% within minutes of the change and stayed there until engineers fixed the setting by hand.

Checkout attempts that succeeded
Checkout attempts that succeededLine chart, 17 values of time. rate goes from 99.4% (10:00) to 99.4% (11:04); highest 99.4% at 10:00, lowest 30% at 10:24. Events: 10:12, Change live; 10:52, Fix applied.0%20%40%60%80%100%10:00 · rate: 99.4%10:04 · rate: 99.3%10:08 · rate: 99.4%10:12 · rate: 99.2%10:16 · rate: 42%10:20 · rate: 31%10:24 · rate: 30%10:28 · rate: 32%10:32 · rate: 31%10:36 · rate: 33%10:40 · rate: 30%10:44 · rate: 32%10:48 · rate: 31%10:52 · rate: 34%10:56 · rate: 81%11:00 · rate: 99.1%11:04 · rate: 99.4%usual10:0010:1210:2410:3210:4010:5211:0410:12: Change liveChange live10:52: Fix appliedFix applied
Checkout attempts that succeededLine chart, 17 values of time. rate goes from 99.4% (10:00) to 99.4% (11:04); highest 99.4% at 10:00, lowest 30% at 10:24. Events: 10:12, Change live; 10:52, Fix applied.0%50%100%10:00 · rate: 99.4%10:04 · rate: 99.3%10:08 · rate: 99.4%10:12 · rate: 99.2%10:16 · rate: 42%10:20 · rate: 31%10:24 · rate: 30%10:28 · rate: 32%10:32 · rate: 31%10:36 · rate: 33%10:40 · rate: 30%10:44 · rate: 32%10:48 · rate: 31%10:52 · rate: 34%10:56 · rate: 81%11:00 · rate: 99.1%11:04 · rate: 99.4%usual10:0010:2410:3610:4811:0410:12: Change liveChange live10:52: Fix appliedFix applied
Data
timerateEvent
10:0099.4%
10:0499.3%
10:0899.4%
10:1299.2%Change live
10:1642%
10:2031%
10:2430%
10:2832%
10:3231%
10:3633%
10:4030%
10:4432%
10:4831%
10:5234%Fix applied
10:5681%
11:0099.1%
11:0499.4%

Timeline

All times are Eastern, Sunday October 18.

timewhat happenedwho
10:12 AMA routine settings change is released to all customers at onceAutomated release system
10:16 AMCheckout failures climb as Sunday-morning shoppers arriven/a
10:19 AMAlert fires after failures stay high for 5 minutes; the on-call engineer is pagedNia Osei
10:24 AMIncident declared; an incident commander and 4 engineers joinRafael Mendes
10:31 AMFirst fix, undoing the change, is blocked by the release system's safety checkRafael Mendes
10:38 AMCause found: the database line limit is 20, not 200Nia Osei
10:52 AMEngineers set the limit back to 200 by hand, bypassing the release systemNia Osei and Rafael Mendes
10:59 AMCheckout success is back to 99%; incident closed at 11:30 AM after monitoringRafael Mendes
Where the 47 minutes went (minutes after 10:12 AM)
Where the 47 minutes went (minutes after 10:12 AM)Range chart, 4 rows on a number axis. Not yet noticed: 0 min to 7 min; Finding the cause: 7 min to 26 min; Fixing: 26 min to 40 min; Recovering: 40 min to 47 min.0 min10 min20 min30 min40 min50 minNot yet noticedNot yet noticed: 0 min to 7 minFinding the causeFinding the cause: 7 min to 26 minFixingFixing: 26 min to 40 minRecoveringRecovering: 40 min to 47 min
Where the 47 minutes went (minutes after 10:12 AM)Range chart, 4 rows on a number axis. Not yet noticed: 0 min to 7 min; Finding the cause: 7 min to 26 min; Fixing: 26 min to 40 min; Recovering: 40 min to 47 min.0 min20 min40 min60 minNot yet noticedNot yet noticed: 0 min to 7 minFinding the causeFinding the cause: 7 min to 26 minFixingFixing: 26 min to 40 minRecoveringRecovering: 40 min to 47 min
Data
phasestartend
Not yet noticed07
Finding the cause726
Fixing2640
Recovering4047

Why it happened

Three things lined up. Any one of them alone would likely have limited the damage.

  1. A test-environment value was copied into the live settings file. The value, 20, is correct for the test environment but far too small for live traffic.
  2. Review did not catch it. The reviewer saw a one-line change and the tool did not show what the live value had been before.
  3. There was no gradual rollout, and no easy undo. The change reached every customer at once, and the release system will not undo a change while checkout is unhealthy, which is exactly when an undo is needed.

Detection was also slower than it should be: the alert waits for 5 minutes of sustained failures, so 7 minutes passed before anyone was paged.

For engineers: the change

# config/prod/checkout-service.yaml
 database:
-  pool_max_connections: 200
+  pool_max_connections: 20
# release gate that blocked the rollback
post_deploy_gate:
  require: checkout_success_rate >= 95
  applies_to: [deploy, rollback]

What we are changing

actionownerduestatus
Live setting restored to 200 and locked against edits outside the release systemRafael MendesOct 18Done
Page the on-call engineer after 2 minutes of failures instead of 5Nia OseiOct 19Done
Show old and new live values side by side in every settings reviewTessa LindqvistOct 23In progress
Automatically block any setting that cuts a live limit by more than half unless a second person approvesTessa LindqvistOct 30In progress
Allow an undo even when checkout is unhealthyRafael MendesOct 27In progress
Roll out settings to 5% of customers first and to everyone after 10 healthy minutesJun ParkNov 13In progress
Email and a $10 credit to the 10,500 customers who did not complete an orderCustomer SupportOct 22In progress

Words used in this report


Back to the examples