<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/feed/styles.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Mohammad Abu Mattar | Incidents</title><description>The latest Incidents from Mohammad Abu Mattar.</description><link>https://mkabumattar.com/</link><item><title>A Regional Control-Plane Failure Took a Payments API Offline</title><link>https://mkabumattar.com/incidents/post/single-region-payments-api-regional-outage/</link><guid isPermaLink="true">https://mkabumattar.com/incidents/post/single-region-payments-api-regional-outage/</guid><description>A money-movement API ran in one AWS region for years. A regional control-plane incident took it offline for hours with nowhere to fail over to. The fix was idempotency first, then a second region.</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;A payments API that moves real money had run in a single AWS region for years.
A regional control-plane incident took the whole service offline for a few
hours. There was no second region to fail over to, so there was nothing to do
but wait for the region to come back.&lt;/p&gt;
&lt;p&gt;Recovery time was whatever the provider&amp;#39;s recovery time turned out to be. That
is the finding.&lt;/p&gt;
&lt;h2&gt;Impact&lt;/h2&gt;
&lt;p&gt;Total loss of service for the duration. A payments API fails worse than most
things at the same downtime:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;settlements stuck mid-flight rather than merely delayed&lt;/li&gt;
&lt;li&gt;partners escalating against a system that could not answer them&lt;/li&gt;
&lt;li&gt;an availability conversation to have afterwards, because the product is
regulated&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Timeline&lt;/h2&gt;
&lt;p&gt;There isn&amp;#39;t one, and I am not going to invent it. The engineering record of
the work that followed puts the outage at a few hours but never captured when
we detected it or when it cleared.&lt;/p&gt;
&lt;p&gt;What is established: the region&amp;#39;s control plane degraded, the service had no
second region to move to, and it stayed down until the region recovered. No
mitigation existed on our side. A timeline of our actions would be a list of
things we could not do.&lt;/p&gt;
&lt;h2&gt;Root cause&lt;/h2&gt;
&lt;p&gt;Two separate things, and only one of them was ever ours to fix.&lt;/p&gt;
&lt;p&gt;The trigger was a control-plane incident in the provider&amp;#39;s region. Rare,
outside our control, and not something an architecture review would have
prevented.&lt;/p&gt;
&lt;p&gt;The cause was that we ran in one region with no failover target. When the only
region is unavailable there is no operational response available. No runbook
helps, because every path in it ends at the same unreachable place.&lt;/p&gt;
&lt;h2&gt;Contributing factors&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Years of reliability had been reading as evidence of resilience.&lt;/strong&gt; The
service had been dependable in one region for a long time, and that record was
doing the work of an argument that one region was enough. It was never that.
It only meant the question had not come up yet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write paths were not idempotent.&lt;/strong&gt; This did not cause the outage, but it
shaped what a fix could look like. When a region gets shaky, clients, proxies
and queues all start retrying at once. Without idempotency, that retry storm
turns one payment into several, so any failover built on those write paths
would have swapped an outage for double charges.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No recovery objective was written down.&lt;/strong&gt; With no agreed RTO or RPO, there
was no number a single-region design had to answer to.&lt;/p&gt;
&lt;h2&gt;What changed&lt;/h2&gt;
&lt;p&gt;The mandate was to survive the loss of an entire region without losing a
committed payment or charging anyone twice. In order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Idempotency keys in a globally replicated store&lt;/strong&gt;, so a retry returns the
original result instead of performing the operation again. This came first
on purpose. The real problem was never &amp;quot;run in two regions&amp;quot;, it was &amp;quot;make
every money-touching operation safe to repeat, then run in two regions.&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Active-active across two regions&lt;/strong&gt; rather than a warm standby, so normal
traffic exercises the failover path instead of an emergency being the first
time anyone tries it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aurora Global Database&lt;/strong&gt; with sub-second replication and a promotable
writer in each region. It fit the EKS and Aurora PostgreSQL the team
already ran, and data-residency rules ruled out a single global write
master.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A written RTO and RPO, proven monthly&lt;/strong&gt;, by deliberately failing a region
out under production-like conditions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On-call needed a failover they could trust at 3am without manual database
promotion steps, because manual steps under pressure are how a recoverable
incident becomes a data-loss incident. That constraint is why the failover is
automatic rather than a runbook.&lt;/p&gt;
&lt;h2&gt;Did it hold&lt;/h2&gt;
&lt;p&gt;In the quarter after cutover the primary region degraded twice. Both times
traffic moved to the healthy region inside the target window, with no
customer-visible errors and no duplicate settlements.&lt;/p&gt;
&lt;p&gt;Finance and risk watched one number, the double-charge count, and it stayed at
zero. Failures kept happening. Every write path had become safe to repeat.&lt;/p&gt;
&lt;h2&gt;What I would do differently&lt;/h2&gt;
&lt;p&gt;Write the recovery objective down before you need it. The missing RTO was not
a documentation gap. It was the reason nobody ever had to defend running in
one region, and a number on paper forces that conversation while there is
still time to have it.&lt;/p&gt;
&lt;p&gt;Read a long clean record as the weak evidence it is. Years of uptime in one
region tells you nothing about losing that region. It tells you the question
has not been asked.&lt;/p&gt;
&lt;p&gt;The full build, including the architecture, the idempotent request flow, the
failover timeline and what broke during the game-days, is written up in
&lt;a href=&quot;/case-studies/post/multi-region-active-active-payments&quot;&gt;Multi-Region Active-Active for a Payments API&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>Reliability</category><category>Architecture</category><author>Mohammad Abu Mattar</author></item></channel></rss>