<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Reliability on Brian Faust</title><link>https://wimdy.works/tags/reliability/</link><description>Recent content in Reliability on Brian Faust</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate><atom:link href="https://wimdy.works/tags/reliability/rss.xml" rel="self" type="application/rss+xml"/><item><title>Recover the First Useful Service, Not Every Server</title><link>https://wimdy.works/thoughts/recover-the-first-useful-service-not-every-server/</link><pubDate>Thu, 01 Oct 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/recover-the-first-useful-service-not-every-server/</guid><description>&lt;p&gt;Six components sit on the recovery checklist: a database, queue, cache,
carrier adapter, identity service, and API. You can follow that checklist
exactly, restore the first four without error, and still leave every customer
unable to retrieve a shipment.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://wimdy.works/reproductions/ops-05/"&gt;OPS-05 fixture&lt;/a&gt;
 gives every recovery action a deterministic
duration. Inventory order reaches an accepted customer journey at 540 virtual
milliseconds. Restoring identity, shipment data, and the API first reaches a
bounded read-only journey at 240. Both schedules finish all six components at
540.&lt;/p&gt;</description></item><item><title>Replication Copies Corruption Too</title><link>https://wimdy.works/thoughts/replication-copies-corruption-too/</link><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/replication-copies-corruption-too/</guid><description>&lt;p&gt;The primary answers &lt;code&gt;SELECT 1&lt;/code&gt;. So does its streaming standby. Their WAL
positions show that the standby has caught up, and PostgreSQL promotes it
without error. &lt;code&gt;shipment-100&lt;/code&gt; is missing from both.&lt;/p&gt;
&lt;p&gt;Every infrastructure signal in that sequence can be true at the same time. The
&lt;a href="https://wimdy.works/reproductions/ops-04/"&gt;OPS-04 fixture&lt;/a&gt;
 makes it happen with PostgreSQL 18.1: a valid
&lt;code&gt;DELETE&lt;/code&gt; commits on the primary, reaches the physical standby, and survives
promotion. Failover restores a writable endpoint, but the deleted shipment is
still deleted. Replication worked perfectly. That is the problem.&lt;/p&gt;</description></item><item><title>A Backup Is Only an Input to Recovery</title><link>https://wimdy.works/thoughts/a-backup-is-only-an-input-to-recovery/</link><pubDate>Sat, 01 Aug 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-backup-is-only-an-input-to-recovery/</guid><description>&lt;p&gt;The backup job is green. The archive exists, its checksum matches, and
&lt;code&gt;pg_restore&lt;/code&gt; exits successfully. A shipment lookup still returns nothing.&lt;/p&gt;
&lt;p&gt;That is not a hypothetical combination. The &lt;a href="https://wimdy.works/reproductions/ops-03/"&gt;OPS-03 fixture&lt;/a&gt;

contains a schema-only archive with a stable SHA-256 identity. PostgreSQL
restores it without error. The tables and constraints exist. The application
probe then asks for &lt;code&gt;shipment-100&lt;/code&gt;, receives no row, and rejects the recovery.&lt;/p&gt;
&lt;p&gt;The checksum answered which bytes we selected. The restore command answered
whether PostgreSQL could apply them. Neither answered whether the recovered
database could do useful work.&lt;/p&gt;</description></item><item><title>Rollback Cannot Restore a Contract You Already Removed</title><link>https://wimdy.works/thoughts/rollback-cannot-restore-a-contract-you-already-removed/</link><pubDate>Wed, 01 Oct 2025 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/rollback-cannot-restore-a-contract-you-already-removed/</guid><description>&lt;p&gt;Release A stores a catalogue quote in &lt;code&gt;amount_cents&lt;/code&gt;. Release B renames the
column to &lt;code&gt;amount_minor&lt;/code&gt; and deploys successfully. Every new B process is
healthy, so the deployment dashboard looks reassuring.&lt;/p&gt;
&lt;p&gt;One A process is still serving traffic during the rolling update. Its next
query asks PostgreSQL for &lt;code&gt;amount_cents&lt;/code&gt; and fails because the column no longer
exists. Restoring the A image produces the same error. The old code returned;
the contract it needs did not.&lt;/p&gt;</description></item><item><title>Observability Must Answer the Next Operator Question</title><link>https://wimdy.works/thoughts/observability-must-answer-the-next-operator-question/</link><pubDate>Mon, 01 Sep 2025 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/observability-must-answer-the-next-operator-question/</guid><description>&lt;p&gt;A catalogue quote request returns &lt;code&gt;503&lt;/code&gt; after its price dependency times out.
One telemetry bundle produces five uncorrelated log lines, a metric labelled
with request ID and raw URL, and three pages. None of those pages names an
owner, the affected scope, the hold duration, or even the first query to run.
There is plenty of telemetry and very little help.&lt;/p&gt;
&lt;p&gt;The other bundle records less and tells the operator more. One page identifies
the affected route, region, and release. Its first query measures the failure
users can see. From there, an exemplar selects a failed trace, the trace locates
the dependency wait, and a correlated event names the application failure
class. The runbook then asks the question which actually matters: is it safe to
bypass that dependency?&lt;/p&gt;</description></item><item><title>A Code Review Should Test the Decision, Not the Author</title><link>https://wimdy.works/thoughts/a-code-review-should-test-the-decision-not-the-author/</link><pubDate>Thu, 01 Aug 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-code-review-should-test-the-decision-not-the-author/</guid><description>&lt;p&gt;One booking flow could trigger the same automatic wallet refill twice. The
first refill happened while booking. A later debit event ran the ordinary
refill check again.&lt;/p&gt;
&lt;p&gt;The correction wrote a short-lived marker per account containing the
pending amount. When a matching shipment debit arrived, it consumed the marker
and skipped one refill. Focused tests covered the expected sequence.&lt;/p&gt;
&lt;p&gt;That is enough to begin a review, not finish one. A tidy helper and a green
happy-path test can make the change look settled. The decision is not “is this
cache helper elegant?” It is:&lt;/p&gt;</description></item><item><title>A Cache Must Distinguish Empty from Unavailable</title><link>https://wimdy.works/thoughts/a-cache-must-distinguish-empty-from-unavailable/</link><pubDate>Mon, 01 Oct 2018 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-cache-must-distinguish-empty-from-unavailable/</guid><description>&lt;p&gt;A cached search returns two pickup locations. One location is deleted locally,
but the search keeps returning both.&lt;/p&gt;
&lt;p&gt;The tempting repair is a shorter time to live. That trades a known correctness
bug for a shorter known correctness bug. The system already knows the cached
answer is false; waiting five minutes instead of an hour does not make it less
false. A TTL is not invalidation with worse timing.&lt;/p&gt;
&lt;p&gt;The opposite failure looks similar. A remote lookup returns nothing during an
outage. Replacing the cache with an empty list would remove locations which may
still exist.&lt;/p&gt;</description></item><item><title>Partial Work Needs a Child Ledger</title><link>https://wimdy.works/thoughts/partial-work-needs-a-child-ledger/</link><pubDate>Sat, 01 Sep 2018 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/partial-work-needs-a-child-ledger/</guid><description>&lt;p&gt;A queue job receives 1,000 events in one remote response. It dispatches 600
child jobs, then times out.&lt;/p&gt;
&lt;p&gt;The parent row says processing failed. Some children have already finished.
Others are waiting in the queue. The remaining 400 were never dispatched. A
counter can report 573 completed events, but it cannot say which 573 they were.&lt;/p&gt;
&lt;p&gt;Running the parent again may duplicate the first 600. Leaving it alone loses
the last 400. Neither choice is defensible because the system retained a total
and discarded the membership behind it.&lt;/p&gt;</description></item><item><title>An Idempotency Key Is Not an Identity</title><link>https://wimdy.works/thoughts/an-idempotency-key-is-not-an-identity/</link><pubDate>Fri, 01 Jun 2018 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/an-idempotency-key-is-not-an-identity/</guid><description>&lt;p&gt;A booking test sends the same request twice with the same idempotency key. The
remote booking service is called once, and both callers receive the same
successful response. Then one field changes while the key stays the same. The
remote service is still called once, but the second request receives a conflict
instead of the first result.&lt;/p&gt;
&lt;p&gt;The key did not establish sameness. It only gave the application somewhere to
look.&lt;/p&gt;</description></item><item><title>A Third-Party API Is an Unreliable Collaborator</title><link>https://wimdy.works/thoughts/a-third-party-api-is-an-unreliable-collaborator/</link><pubDate>Thu, 01 Feb 2018 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-third-party-api-is-an-unreliable-collaborator/</guid><description>&lt;p&gt;A scheduled import begins by listing remote orders. The list request returns a
502 HTML page, so there are no trustworthy item identifiers to process.&lt;/p&gt;
&lt;p&gt;On another run the list succeeds. One order then returns a 504 while its
details are being loaded. The remaining orders are still independent and can
continue.&lt;/p&gt;
&lt;p&gt;These failures share a provider, but not a recovery boundary. Calling both “the
API is down” is convenient and useless: it either discards work which could
continue or pretends the response established more than it did.&lt;/p&gt;</description></item><item><title>A Release Gate Must Fail Before Incompatible Code Goes Live</title><link>https://wimdy.works/thoughts/a-release-gate-must-fail-before-incompatible-code-goes-live/</link><pubDate>Tue, 01 Nov 2016 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-release-gate-must-fail-before-incompatible-code-goes-live/</guid><description>&lt;p&gt;We had a deployment step everybody knew about and people still forgot it. The
code was pulled manually, then someone had to run a shell script which applied
pending migrations. The instructions existed, but the habit remained
concentrated in one operator.&lt;/p&gt;
&lt;p&gt;Eventually a deployment completed without the script. Traffic reached code
which expected columns or tables that did not exist, and database queries began
failing. Running the migrations restored the application. There was no obscure
database bug here; the release process had declared success before one of its
prerequisites was true.&lt;/p&gt;</description></item><item><title>Retries Can Multiply the Original Failure</title><link>https://wimdy.works/thoughts/retries-can-multiply-the-original-failure/</link><pubDate>Mon, 01 Aug 2016 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/retries-can-multiply-the-original-failure/</guid><description>&lt;p&gt;We once had roughly 50,000 failed jobs sitting in queues and trying again every
60 seconds. Some represented a provider outage. Others contained malformed
input which could wait for three months and still be malformed. The queue made
no distinction and had no terminal condition.&lt;/p&gt;
&lt;p&gt;During a long outage, workers spent their time reproducing failures we already
understood. Redis filled with delayed work, fresh operations had to compete
with old ones, and a dependency problem became sustained load on our own
application. The retry loop was meant to make work reliable. What it actually
made durable was failure.&lt;/p&gt;</description></item><item><title>A Timeout Is Part of the Contract</title><link>https://wimdy.works/thoughts/a-timeout-is-part-of-the-contract/</link><pubDate>Fri, 01 Jul 2016 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-timeout-is-part-of-the-contract/</guid><description>&lt;p&gt;One accounting integration went down often enough that we stopped being
surprised by it. Sometimes it came back with an expired TLS certificate. A
different provider scheduled maintenance during our peak operating hours.
Neither system became more reliable just because our code was willing to wait.&lt;/p&gt;
&lt;p&gt;Waiting longer was not resilience. It turned someone else&amp;rsquo;s outage into our
capacity problem while leaving the caller with the same unanswered question:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;How long may this attempt wait before its result is no longer useful?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;That answer belongs to the operation, not the HTTP library. If we leave it to a
library default, a transport setting decides how much of the customer&amp;rsquo;s time
and our worker capacity one dependency may consume.&lt;/p&gt;</description></item></channel></rss>