<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Failure-Design on ErrorZap</title><link>https://errorzap.com/tags/failure-design/</link><description>Recent content in Failure-Design on ErrorZap</description><image><title>ErrorZap</title><url>https://errorzap.com/og.png</url><link>https://errorzap.com/og.png</link></image><generator>Hugo</generator><language>en-US</language><copyright>ErrorZap</copyright><lastBuildDate>Wed, 04 Nov 2026 09:00:00 -0700</lastBuildDate><atom:link href="https://errorzap.com/tags/failure-design/index.xml" rel="self" type="application/rss+xml"/><item><title>Last Good Data Is a Feature</title><link>https://errorzap.com/posts/last-good-data-is-a-feature/</link><pubDate>Wed, 04 Nov 2026 09:00:00 -0700</pubDate><guid>https://errorzap.com/posts/last-good-data-is-a-feature/</guid><description>When a public-data source breaks, replacing yesterday&amp;rsquo;s truth with today&amp;rsquo;s garbage is not freshness. It is vandalism with a timestamp.</description><content:encoded><![CDATA[<p>I used to think a data collector had two states: it ran or it failed.</p>
<p>Then I started collecting from public websites.</p>
<p>A public source can return HTTP 200 and still be useless. It can serve a login page where a table used to be. It can return only the first page, repeat yesterday&rsquo;s records with new timestamps, move columns, invent empty dates, or decide that a polite automated request looks suspicious. The transport succeeded. The data did not.</p>
<p>The most dangerous collector is not the one that crashes. It is the one that confidently replaces a known-good dataset with a plausible pile of nonsense.</p>
<h2 id="fresh-is-not-the-same-as-true">Fresh is not the same as true</h2>
<p>Imagine a local information page with 8,000 verified records. Tonight&rsquo;s refresh retrieves 14 rows because the source changed pagination. A naive pipeline writes those 14 rows over the old file and celebrates a successful run.</p>
<p>By morning, the website is technically current and practically destroyed.</p>
<p>The safer pattern keeps separate objects:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>incoming candidate
</span></span><span style="display:flex;"><span>       |
</span></span><span style="display:flex;"><span>       v
</span></span><span style="display:flex;"><span>validate structure, counts, dates, uniqueness
</span></span><span style="display:flex;"><span>       |
</span></span><span style="display:flex;"><span>       +---- bad ----&gt; quarantine + alert
</span></span><span style="display:flex;"><span>       |
</span></span><span style="display:flex;"><span>       +---- good ---&gt; atomically promote to current
</span></span><span style="display:flex;"><span>                              |
</span></span><span style="display:flex;"><span>                              +--&gt; preserve previous known-good
</span></span></code></pre></div><p>The last-good dataset is not merely a backup. It is part of the product contract.</p>
<p>If tonight&rsquo;s import fails validation, readers should see yesterday&rsquo;s honest data plus a visible freshness warning. They should not see an empty page pretending that nothing exists.</p>
<h2 id="validation-should-be-rude">Validation should be rude</h2>
<p>Collectors need to distrust their source in specific, testable ways.</p>
<p>Useful gates include:</p>
<ul>
<li>required columns are still present;</li>
<li>row count did not collapse unexpectedly;</li>
<li>identifiers remain unique;</li>
<li>dates are parseable and not absurdly in the future;</li>
<li>detail-page coverage stays within a known range;</li>
<li>a sample of old records still exists;</li>
<li>content is not an error page wearing a 200 status code.</li>
</ul>
<p>None of these proves perfection. Together they make silent catastrophe much harder.</p>
<p>The thresholds should also be explainable. &ldquo;Reject if the row count falls more than 40 percent&rdquo; is debatable but testable. &ldquo;The page looked weird&rdquo; is neither.</p>
<h2 id="stale-should-be-visible">Stale should be visible</h2>
<p>Preserving last-good data creates a new obligation: do not pass stale data off as fresh.</p>
<p>Every public-facing dataset should carry a small status card:</p>
<ul>
<li>last successful source retrieval;</li>
<li>last successful validation;</li>
<li>current row count;</li>
<li>whether detail coverage is complete or partial;</li>
<li>number of quarantined records;</li>
<li>link to the original source;</li>
<li>plain-language caveat when the source is blocked or changing.</li>
</ul>
<p>That little card is more useful than a hundred lines of invisible logging. It tells readers what the system knows about itself.</p>
<h2 id="failure-can-be-a-product-feature">Failure can be a product feature</h2>
<p>Most software demos focus on the happy path: click Refresh, watch new rows appear. Real reliability lives in the ugly path: the source returns less, later, malformed, duplicated, or nothing at all.</p>
<p>The mature response is not heroic scraping. It is restraint.</p>
<p>Keep yesterday&rsquo;s truth. Quarantine today&rsquo;s suspicious changes. Explain the age of the evidence. Try again later without turning one upstream failure into a downstream lie.</p>
<p>Sometimes the most important new data is the decision not to replace the old data.</p>
]]></content:encoded></item></channel></rss>