<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Data That Must Survive on Brian Faust</title><link>https://vacua.dev/series/data-that-must-survive/</link><description>Recent content in Data That Must Survive 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://vacua.dev/series/data-that-must-survive/rss.xml" rel="self" type="application/rss+xml"/><item><title>Data Outlives the Code That Created It</title><link>https://vacua.dev/thoughts/data-outlives-the-code-that-created-it/</link><pubDate>Fri, 01 Dec 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/data-outlives-the-code-that-created-it/</guid><description>&lt;p&gt;A failed queue record kept returning. Its payload named a mail class the
running application could not resolve correctly, so retrying it could only
repeat the same failure.&lt;/p&gt;
&lt;p&gt;The retained workaround was blunt: once a minute, delete failed records whose
payload matched that job. It stopped an unusable delivery attempt from
disturbing the queue. It did not make the payload readable.&lt;/p&gt;
&lt;p&gt;That small incident exposes an awkward storage contract. Serializing an
executable object does not merely store its arguments. It preserves facts about
the code which created it: class name, namespace, property layout, and sometimes
framework behavior. The payload can live much longer than those facts.&lt;/p&gt;</description></item><item><title>Audit History Is Different from Application Logging</title><link>https://vacua.dev/thoughts/audit-history-is-different-from-application-logging/</link><pubDate>Wed, 01 Nov 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/audit-history-is-different-from-application-logging/</guid><description>&lt;p&gt;When a cart appears to expire, support may ask:&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;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;Which cart changed?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;What was its prior state?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;What state followed?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Who or what caused the transition?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Which policy allowed it?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;An engineer investigating the same report may ask:&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;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;Which request handled it?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Which worker retried?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;How long did the database call take?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Which exception and stack occurred?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;What else happened on that host?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Both groups need evidence. They do not need the same record.&lt;/p&gt;</description></item><item><title>An Index Must Serve a Real Query</title><link>https://vacua.dev/thoughts/an-index-must-serve-a-real-query/</link><pubDate>Sun, 01 Oct 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/an-index-must-serve-a-real-query/</guid><description>&lt;p&gt;A production query needed a small page of recent records requiring operational
attention. The table already had an index which looked relevant. The measured
scan still discarded 84,003 historical index entries and recorded 122,936
shared-buffer reads before finding 83 eligible rows.&lt;/p&gt;
&lt;p&gt;The missing index was not “an index on the table.” It was an index for this
query:&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;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;one tenant
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;active records with zero price
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;created during the last three months
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;newest creation time first
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stable identifier as the tie-breaker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;first page only&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Indexing starts with the access path, not with a list of columns which sound
important.&lt;/p&gt;</description></item><item><title>Pagination Needs a Stable Order</title><link>https://vacua.dev/thoughts/pagination-needs-a-stable-order/</link><pubDate>Fri, 01 Sep 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/pagination-needs-a-stable-order/</guid><description>&lt;p&gt;A financial report encoded two sort rules as one string:&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;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;lowercased customer name + &amp;#34;-&amp;#34; + customer identifier&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Sharded tests with concurrently created identifiers exposed unreliable output.
The correction stopped asking a compound string to express precedence. It
compared names directly and consulted the stable customer identifier only when
the names matched.&lt;/p&gt;
&lt;p&gt;The retained diff does not prove that equal names alone caused the failing
order. It does expose the rule every paginator depends on: the primary
comparison, tie-breaker, and comparison semantics must be explicit, and each
row needs exactly one reproducible position.&lt;/p&gt;</description></item><item><title>Soft Deletes Preserve Ambiguity Too</title><link>https://vacua.dev/thoughts/soft-deletes-preserve-ambiguity-too/</link><pubDate>Tue, 01 Aug 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/soft-deletes-preserve-ambiguity-too/</guid><description>&lt;p&gt;Adding &lt;code&gt;deleted_at&lt;/code&gt; to a table answers one question: when was this row marked as
deleted?&lt;/p&gt;
&lt;p&gt;It does not answer whether the row was safe to mark, who should still see it,
whether its credentials remain valid, whether it still occupies a unique
business slot, how it can be restored, or when its data should be erased.&lt;/p&gt;
&lt;p&gt;A retained cart cleanup could not safely reduce those questions to one nullable
timestamp. Old carts might still have a shipment, a payment session, a booking
attempt, or item work which had crossed a meaningful boundary. The eventual
policy archives a deliberately narrow set and preserves the aggregate as
evidence.&lt;/p&gt;</description></item><item><title>An Identifier Should Outlive Its Storage Engine</title><link>https://vacua.dev/thoughts/an-identifier-should-outlive-its-storage-engine/</link><pubDate>Sat, 01 Jul 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/an-identifier-should-outlive-its-storage-engine/</guid><description>&lt;p&gt;A billing export once had enough information to reconstruct a missing account,
but not enough to name it.&lt;/p&gt;
&lt;p&gt;The account still existed in an older system. Its stable identifier reached the
export action. No matching row existed in the newer application&amp;rsquo;s user table,
so the action built a temporary object with safe fallback details. That object
had a synthetic local identifier, but its export identifier remained null.
Later, a queued data object required that identifier to be a string and
rejected the payload.&lt;/p&gt;</description></item><item><title>Time Zones Are Part of the Data Model</title><link>https://vacua.dev/thoughts/time-zones-are-part-of-the-data-model/</link><pubDate>Thu, 01 Jun 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/time-zones-are-part-of-the-data-model/</guid><description>&lt;p&gt;One metering path assigns an event to a billing month after converting its
occurrence time into the account&amp;rsquo;s timezone. The same path derives its daily
usage key directly from the occurrence timestamp without that conversion.&lt;/p&gt;
&lt;p&gt;Near midnight, those two fields can disagree:&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;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;event instant: May 31, 21:30 UTC
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;local business time: June 1, 00:30
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;billing period: June
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stored usage date: May 31&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Each expression looks like a date calculation. They model different things.
The defect appears only when the difference changes a boundary.&lt;/p&gt;</description></item><item><title>Money Is Not a Floating-Point Number</title><link>https://vacua.dev/thoughts/money-is-not-a-floating-point-number/</link><pubDate>Mon, 01 May 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/money-is-not-a-floating-point-number/</guid><description>&lt;p&gt;A booking-time price arrived as four values:&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;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;net amount: 407
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tax rate: 25.5%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tax amount: 104
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gross amount: 511&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The amounts are integers in the currency&amp;rsquo;s minor unit. The rate is a decimal
ratio. The contract checks that net plus tax equals gross and that applying the
declared rounding rule to net multiplied by the rate produces the tax amount.&lt;/p&gt;
&lt;p&gt;None of those details is decorative. Remove the unit, mix the rate&amp;rsquo;s two
representations, or let two services round at different stages, and the same
price can stop balancing.&lt;/p&gt;</description></item><item><title>Null Is a Domain Decision</title><link>https://vacua.dev/thoughts/null-is-a-domain-decision/</link><pubDate>Sat, 01 Apr 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/null-is-a-domain-decision/</guid><description>&lt;p&gt;We added four nullable fields to preserve a pricing snapshot: net amount, tax
rate, tax amount, and gross amount.&lt;/p&gt;
&lt;p&gt;Each field could now be absent. That did not mean each field was independently
optional.&lt;/p&gt;
&lt;p&gt;For older records, all four nulls meant one thing: this record predates the
snapshot and does not contain captured pricing evidence. For new records, all
four values had to arrive together. A row containing gross and net but no tax
rate was not “partly known.” It was invalid.&lt;/p&gt;</description></item><item><title>A Migration Is a Production Change</title><link>https://vacua.dev/thoughts/a-migration-is-a-production-change/</link><pubDate>Wed, 01 Mar 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-migration-is-a-production-change/</guid><description>&lt;p&gt;We deployed an additive schema change which the database could execute without
rewriting the table. It still waited indefinitely.&lt;/p&gt;
&lt;p&gt;The statement needed an exclusive table lock. A long-running session already
held a conflicting lock, so the migration queued. New application traffic then
queued behind the migration. The column addition was cheap after lock
acquisition; waiting to begin was the incident.&lt;/p&gt;
&lt;p&gt;Another schema change looked cheap for the opposite reason: remove fields and
tables which the application no longer read. The SQL was small. The decision
would erase financial history from data sets measured in tens of gigabytes.&lt;/p&gt;</description></item><item><title>A Transaction Defines What Must Succeed Together</title><link>https://vacua.dev/thoughts/a-transaction-defines-what-must-succeed-together/</link><pubDate>Wed, 01 Feb 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-transaction-defines-what-must-succeed-together/</guid><description>&lt;p&gt;A booking flow reserved funds, completed the remote booking, and captured the
funds. Only then did synchronous response processing fail.&lt;/p&gt;
&lt;p&gt;The remote booking existed. The funds were captured. The caller received a
failure.&lt;/p&gt;
&lt;p&gt;The capture was individually valid, and its own database transaction committed
cleanly. It was still too early. We had drawn the success boundary around
“remote booking returned” while the operation promised something larger: a
complete booking result which the caller could use.&lt;/p&gt;</description></item><item><title>Let the Database Reject Impossible State</title><link>https://vacua.dev/thoughts/let-the-database-reject-impossible-state/</link><pubDate>Sun, 01 Jan 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/let-the-database-reject-impossible-state/</guid><description>&lt;p&gt;Two workers asked the same reasonable question: does this owner already have an
active record? Both received the same honest answer: no.&lt;/p&gt;
&lt;p&gt;Each worker created one.&lt;/p&gt;
&lt;p&gt;Nothing in either request was obviously careless. The application checked
first. The insert happened inside a transaction. Tests which ran one request at
a time stayed green. The defect lived in the interval between the check and the
write, where both transactions were allowed to be correct about a state which
had already become stale.&lt;/p&gt;</description></item></channel></rss>