<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Indie Rob</title>
    <description>The latest articles on DEV Community by Indie Rob (@indierob_).</description>
    <link>https://dev.to/indierob_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4108728%2Fc6562aa5-9991-420b-9255-f9295d4d9c4a.jpg</url>
      <title>DEV Community: Indie Rob</title>
      <link>https://dev.to/indierob_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/indierob_"/>
    <language>en</language>
    <item>
      <title>credits_remaining -= 1 is a race condition</title>
      <dc:creator>Indie Rob</dc:creator>
      <pubDate>Thu, 03 Sep 2026 21:49:01 +0000</pubDate>
      <link>https://dev.to/indierob_/creditsremaining-1-is-a-race-condition-52fo</link>
      <guid>https://dev.to/indierob_/creditsremaining-1-is-a-race-condition-52fo</guid>
      <description>&lt;p&gt;A &lt;code&gt;credits_remaining: int&lt;/code&gt; column on your users table looks perfectly fine for an AI SaaS. Sign a user up, drop 100 credits into their row, deduct on each request, top up on payment. Done.&lt;/p&gt;

&lt;p&gt;It stays fine until the work you're charging for goes asynchronous. Then it becomes surprisingly easy to charge twice, overspend, or lose track of why a balance changed.&lt;/p&gt;

&lt;p&gt;I built it the naive way first. Here is the failure mode I ran into, and the pattern I ended up shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  The race
&lt;/h2&gt;

&lt;p&gt;Imagine a user has 10 credits. Two API requests arrive almost simultaneously, each costing 7 credits' worth of compute.&lt;/p&gt;

&lt;p&gt;Both handlers read the balance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT credits_remaining FROM users WHERE id = :id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both check &lt;code&gt;10 &amp;gt;= 7&lt;/code&gt;. Both decide the user can afford the job. Both enqueue the work.&lt;/p&gt;

&lt;p&gt;You just sold 14 credits of compute for 10.&lt;/p&gt;

&lt;p&gt;If your compute costs real money — a hosted LLM call, a GPU minute, a paid third-party API — you are eating the delta. If it costs enough, that is a real leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  "But I'll wrap it in a transaction"
&lt;/h2&gt;

&lt;p&gt;Common first fix. Doesn't do what most people think.&lt;/p&gt;

&lt;p&gt;Postgres defaults to &lt;code&gt;READ COMMITTED&lt;/code&gt; isolation. Under &lt;code&gt;READ COMMITTED&lt;/code&gt;, two concurrent transactions can each see the pre-spend balance before either commits. Both read 10, both decide 7 is affordable, both write back a new balance of 3 — the second overwriting the first. Net effect: same overspend, and you have thrown away one of the deductions on top of it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SERIALIZABLE&lt;/code&gt; isolation would catch this. It also turns every write in your application into something that might fail with a serialization error and need a retry loop, whether it has anything to do with the billing race or not. Most teams don't want that as a global setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The narrower fix: lock the row for the spend
&lt;/h2&gt;

&lt;p&gt;The tighter answer is to serialize only the read-and-write for the balance being spent from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT credits_remaining FROM users WHERE id = :id FOR UPDATE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;InsufficientCredits&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE users SET credits_remaining = credits_remaining - :cost &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHERE id = :id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; acquires a row-level lock. The second concurrent transaction blocks until the first commits, then sees the updated balance of 3 and correctly refuses the second 7-credit job.&lt;/p&gt;

&lt;p&gt;That closes the race. Two different jobs can no longer overspend the same balance.&lt;/p&gt;

&lt;p&gt;Another problem shows up almost immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second race: retries
&lt;/h2&gt;

&lt;p&gt;Workers retry. Brokers redeliver. HTTP clients retry on &lt;code&gt;504&lt;/code&gt;. Users double-click. If the same logical job can enter your deduction path twice, your billing correctness now depends on every caller in the system behaving perfectly and never retrying a completed request.&lt;/p&gt;

&lt;p&gt;That is a bad invariant to bet a billing system on.&lt;/p&gt;

&lt;p&gt;The row lock doesn't help here. Each retry is its own transaction. It dutifully takes the lock, sees the current balance, and deducts again.&lt;/p&gt;

&lt;p&gt;The fix is separate: an &lt;strong&gt;idempotency key&lt;/strong&gt; at the deduction layer. Give every logical job a stable ID, and make the deduction insert unique on &lt;code&gt;(user_id, job_id, kind)&lt;/code&gt;. A retry that tries to re-insert the same fact gets rejected by the unique constraint, and the handler treats that rejection as "already recorded, carry on".&lt;/p&gt;

&lt;h2&gt;
  
  
  The distinction I originally missed
&lt;/h2&gt;

&lt;p&gt;Row lock and idempotency key are both about "the same fact shouldn't be charged twice", but they solve different races:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Row lock&lt;/strong&gt; prevents &lt;em&gt;two different jobs&lt;/em&gt; from overspending the same balance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency key&lt;/strong&gt; prevents &lt;em&gt;the same job&lt;/em&gt; from being charged twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I originally treated them as one problem. They aren't. You need both, and they compose cleanly in the same handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern I ended up shipping: an append-only ledger
&lt;/h2&gt;

&lt;p&gt;Once I accepted that "the balance" is derived state, not primary state, the schema simplified.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;credit_entries&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;          &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;     &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;delta&lt;/span&gt;       &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;        &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;job_id&lt;/span&gt;      &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reason&lt;/span&gt;      &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt;  &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;credit_entries&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every movement is a row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grant           +100
deduct            -7
task_started       0
task_completed     0
refund            +7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The balance is &lt;code&gt;SUM(delta) WHERE user_id = ?&lt;/code&gt;. Never stored, always computed. If you're worried about the read cost, cache it — but the ledger stays the source of truth.&lt;/p&gt;

&lt;p&gt;Spending looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        SELECT COALESCE(SUM(delta), 0)
        FROM credit_entries
        WHERE user_id = :id
        FOR UPDATE
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;InsufficientCredits&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            INSERT INTO credit_entries
                (user_id, delta, kind, job_id, reason)
            VALUES
                (:id, :delta, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deduct&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, :job_id, :reason)
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;UniqueViolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# (user, job, 'deduct') already exists. Retry is a no-op.
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Row lock handles the concurrent-jobs race. Unique constraint handles the retry race. Same handler. Both problems addressed, no special cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the ledger earns its weight
&lt;/h2&gt;

&lt;p&gt;More machinery than a single integer column, obviously. It earns it in ways you feel every week once you have real users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refunds are new facts, not mutations.&lt;/strong&gt; A failed job gets a &lt;code&gt;+7 refund&lt;/code&gt; entry that references the original deduction. The original deduction stays. Nothing was rewritten. You can look back at the sequence and see exactly what happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failed jobs remain explainable.&lt;/strong&gt; The &lt;code&gt;task_started&lt;/code&gt; and &lt;code&gt;task_completed&lt;/code&gt; entries mean you can tell the difference between "we charged the user and the job succeeded" and "we charged the user and something died".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reconciliation becomes possible.&lt;/strong&gt; If workers crash between deducting and completing, you can find orphaned deductions programmatically — &lt;code&gt;deduct&lt;/code&gt; entries without a matching &lt;code&gt;task_completed&lt;/code&gt; or &lt;code&gt;refund&lt;/code&gt; after some timeout — and repair them without opening the database by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support can answer "why is my balance 37?"&lt;/strong&gt; They run one query and read the last N rows in chronological order. This is a real support cost you're paying whether you notice it or not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Billing state has an audit trail by default&lt;/strong&gt;, without you having to bolt on a separate audit log later.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it is overkill
&lt;/h2&gt;

&lt;p&gt;If your credits map 1:1 to synchronous API calls, no async work, no retries, no long-running compute, then no — you don't need this. A locked update on a column is fine.&lt;/p&gt;

&lt;p&gt;The ledger earns its complexity when at least one of these is true: work happens off the request path, workers can retry, refunds are non-trivial, or support has to answer questions about historical balances.&lt;/p&gt;

&lt;p&gt;If you're building an AI SaaS with billed LLM calls, all four are usually true from month one.&lt;/p&gt;

&lt;h2&gt;
  
  
  In production
&lt;/h2&gt;

&lt;p&gt;I put the decision doc, a concurrency test receipt (real PostgreSQL sessions, two concurrent deducts of 7 on a starting balance of 10, asserting exactly one succeeds and the balance ends at 3), and a few other engineering excerpts into a public inspection repo for &lt;a href="https://thefabrica.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=credit_ledger" rel="noopener noreferrer"&gt;The Fabrica&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/webrot9/thefabrica-inspect" rel="noopener noreferrer"&gt;github.com/webrot9/thefabrica-inspect&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not the commercial source. Enough code and enough decisions to inspect whether the engineering claims hold up before deciding to license the full codebase. The ledger is one of the pieces I most wanted to expose there, because it's exactly the kind of thing that separates a starter kit from a codebase you can actually run a business on.&lt;/p&gt;

&lt;p&gt;If you're building anything with async, billed work, do the ledger from day one. Retrofitting it after your first "sold 14 for 10" incident is not fun.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>python</category>
      <category>saas</category>
      <category>concurrency</category>
    </item>
  </channel>
</rss>
