<?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: Usman Khan</title>
    <description>The latest articles on DEV Community by Usman Khan (@usman_khan_io).</description>
    <link>https://dev.to/usman_khan_io</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%2F4147763%2F3846dd1b-842f-4ef8-9563-883e2f3748ee.jpg</url>
      <title>DEV Community: Usman Khan</title>
      <link>https://dev.to/usman_khan_io</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/usman_khan_io"/>
    <language>en</language>
    <item>
      <title>Foreign Key Cascade Deletes: Un-Indexed FKs, Slow Cascades, and Lock Contention in PostgreSQL</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Thu, 29 Oct 2026 18:00:53 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/foreign-key-cascade-deletes-un-indexed-fks-slow-cascades-and-lock-contention-in-postgresql-3n45</link>
      <guid>https://dev.to/usman_khan_io/foreign-key-cascade-deletes-un-indexed-fks-slow-cascades-and-lock-contention-in-postgresql-3n45</guid>
      <description>&lt;p&gt;Running a DELETE on a parent entity with ON DELETE CASCADE feels like clean relational cleanup. But if any child or grandchild table in the dependency chain lacks an index on its foreign key column, PostgreSQL falls back to full sequential scans on every cascade step, turning a quick delete into a long-running transaction that holds row locks and starves concurrent writes. Here is an architectural deep dive into what actually happens during an un-indexed cascade, and how to build an asynchronous soft-delete purge pipeline.&lt;/p&gt;

&lt;p&gt;In multi-tenant relational design, enforcing referential integrity with foreign keys (&lt;code&gt;FOREIGN KEY ... REFERENCES ... ON DELETE CASCADE&lt;/code&gt;) is foundational. But when a high-throughput B2B SaaS platform purges a top-level tenant or bulk-deletes historical entities, cascading deletes can turn into a performance trap, not because PostgreSQL escalates to table locks, but because a &lt;strong&gt;missing index on a foreign key column&lt;/strong&gt; forces a full sequential scan on every cascade step, stretching a quick delete into a long-running, lock-holding transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Mechanics of Un-Indexed Foreign Key Cascades
&lt;/h2&gt;

&lt;p&gt;When an application deletes a parent row (say, a row in &lt;code&gt;tenants&lt;/code&gt;), PostgreSQL must find and delete every referencing row in child and grandchild tables (&lt;code&gt;users&lt;/code&gt;, &lt;code&gt;invoices&lt;/code&gt;, &lt;code&gt;audit_logs&lt;/code&gt;, &lt;code&gt;api_tokens&lt;/code&gt;, and so on) to satisfy the foreign key constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Un-Indexed Foreign Key Problem
&lt;/h3&gt;

&lt;p&gt;PostgreSQL automatically indexes primary keys and unique constraints, but &lt;strong&gt;it does not create an index on the referencing (child) side of a foreign key&lt;/strong&gt;. You have to add that index yourself. When it is missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequential Child Scans:&lt;/strong&gt; To find the child rows that point at the parent being deleted, PostgreSQL has no index to probe, so it runs a &lt;strong&gt;full sequential scan&lt;/strong&gt; of the child table, once per cascade step, repeated down the hierarchy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Long-Running Transaction, Not a Table Lock:&lt;/strong&gt; This is the point most write-ups get wrong. PostgreSQL does &lt;em&gt;not&lt;/em&gt; escalate row locks to a table-level &lt;code&gt;ShareLock&lt;/code&gt; on the child table, and it has no lock-escalation mechanism at all. The cascade holds the ordinary &lt;code&gt;RowExclusiveLock&lt;/code&gt; on each table it writes, exactly as any &lt;code&gt;DELETE&lt;/code&gt; does, and &lt;code&gt;RowExclusiveLock&lt;/code&gt; does not conflict with other writers. What actually hurts is &lt;em&gt;duration&lt;/em&gt;: the sequential scans make the deleting transaction run for seconds, and for that whole time it holds row locks on every row it is deleting and &lt;code&gt;FOR KEY SHARE&lt;/code&gt; locks on the parent rows it touches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contention and Deadlock Risk:&lt;/strong&gt; A transaction that stays open for seconds while touching millions of rows is the real hazard. Concurrent statements that try to update or delete any of those same rows block until it finishes; the long transaction holds back &lt;code&gt;VACUUM&lt;/code&gt; and inflates bloat; and the more rows and tables one transaction locks, the higher the chance of a lock-ordering deadlock with other transactions. The symptom looks like a sitewide stall, but the cause is a slow transaction holding many row locks, not a single table-level lock.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Measuring the Cost
&lt;/h2&gt;

&lt;p&gt;Consider a standard multi-tenant schema whose &lt;code&gt;tenants&lt;/code&gt; table cascades down three tiers, with no indexes on the foreign key columns:&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="c1"&gt;-- Failure case: foreign keys with no supporting index&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenants&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;UUID&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;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&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="n"&gt;UUID&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;tenant_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;tenants&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="c1"&gt;-- INDEX MISSING&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;user_activity_logs&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;UUID&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;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="c1"&gt;-- INDEX MISSING&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single &lt;code&gt;DELETE FROM tenants WHERE id = '...'&lt;/code&gt; forces PostgreSQL to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delete the matching &lt;code&gt;tenants&lt;/code&gt; row.&lt;/li&gt;
&lt;li&gt;Sequentially scan &lt;code&gt;users&lt;/code&gt; to find rows with that &lt;code&gt;tenant_id&lt;/code&gt;, and delete them.&lt;/li&gt;
&lt;li&gt;For each deleted user, sequentially scan &lt;code&gt;user_activity_logs&lt;/code&gt; to find and delete that user's rows.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On a PostgreSQL 16 test with 2,000 users and 200,000 activity-log rows, deleting one tenant with no FK indexes took &lt;strong&gt;about 2.4 seconds&lt;/strong&gt;. After adding a B-tree index on each foreign key column, the identical delete took &lt;strong&gt;about 32 milliseconds&lt;/strong&gt;, roughly &lt;strong&gt;70x faster&lt;/strong&gt;. Scale the log table to tens of millions of rows and the un-indexed version runs long enough to become a visible production incident: the deleting transaction sits open, holding row locks and blocking any concurrent write that touches the same rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Production Diagnostics: Finding Un-Indexed Foreign Keys
&lt;/h2&gt;

&lt;p&gt;Run this catalog query to list every foreign key whose referencing columns are not covered by the leading columns of some index. (A naive version that compares &lt;code&gt;indkey::text&lt;/code&gt; to &lt;code&gt;conkey::text&lt;/code&gt; does not work: an index prints its columns as &lt;code&gt;1&lt;/code&gt; while a constraint prints &lt;code&gt;{2}&lt;/code&gt;, so the string match silently flags every key. The version below compares them as arrays.)&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="c1"&gt;-- Detect foreign keys with no supporting index (PostgreSQL)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conrelid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;child_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conname&lt;/span&gt;                       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;foreign_key_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pg_get_constraintdef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;constraint_definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_constraint&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connamespace&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s1"&gt;'pg_catalog'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regnamespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'information_schema'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regnamespace&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_index&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conrelid&lt;/span&gt;
        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indpred&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;  &lt;span class="c1"&gt;-- a partial index can't back the FK&lt;/span&gt;
        &lt;span class="c1"&gt;-- the FK columns must be the leading columns of the index, in order&lt;/span&gt;
        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indkey&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;smallint&lt;/span&gt;&lt;span class="p"&gt;[])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;cardinality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conkey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conkey&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;child_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query is read-only and safe to run on a production primary.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Fixes: Index Every Foreign Key, and Purge Asynchronously
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Add a B-Tree Index on Every Foreign Key, CONCURRENTLY
&lt;/h3&gt;

&lt;p&gt;Give every foreign key column a supporting index so cascade lookups use an index scan instead of a sequential scan. Build them with &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt; so the build does not take a blocking lock on a live table. (&lt;code&gt;CONCURRENTLY&lt;/code&gt; cannot run inside a transaction block, and on failure leaves an &lt;code&gt;INVALID&lt;/code&gt; index you drop and recreate.)&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="c1"&gt;-- Index the referencing columns so CASCADE uses index scans&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt; &lt;span class="n"&gt;idx_users_tenant_id&lt;/span&gt;
    &lt;span class="k"&gt;ON&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;tenant_id&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="n"&gt;CONCURRENTLY&lt;/span&gt; &lt;span class="n"&gt;idx_user_activity_logs_user_id&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;user_activity_logs&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Replace Synchronous Hard Deletes with Soft-Delete + Batched Purge
&lt;/h3&gt;

&lt;p&gt;Even with indexes, deleting millions of rows in one statement inside a user-facing request is a bad idea: it is one long transaction holding many locks. Mark the tenant deleted instantly, then purge in small, committed batches from a background worker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PRODUCTION PATTERN: Soft-delete then batched purge (TypeScript)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantLifecycleManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Marks the tenant deleted in a single fast indexed UPDATE,
   * then hands the heavy work to a background purge job.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;softDeleteTenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`UPDATE tenants
          SET deleted_at = now(),
              status     = 'PENDING_PURGE'
        WHERE id = $1 AND deleted_at IS NULL`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueueBackgroundPurgeJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Background worker: delete grandchild rows in small, independently
   * committed batches so no single transaction holds locks for long.
   * Purge order is bottom-up: grandchildren, then children, then the tenant.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;purgeActivityLogs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Each batch is its own transaction (one statement = one commit here).&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`WITH target AS (
            SELECT l.id
              FROM user_activity_logs l
              JOIN users u ON u.id = l.user_id
             WHERE u.tenant_id = $1
             LIMIT $2
             FOR UPDATE OF l SKIP LOCKED
         )
         DELETE FROM user_activity_logs
          WHERE id IN (SELECT id FROM target)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Breathe between batches so normal traffic keeps moving.&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;enqueueBackgroundPurgeJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Push onto your job queue (BullMQ, pgmq, SQS, etc.)&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;Run the same batched pattern for &lt;code&gt;users&lt;/code&gt; after the logs are gone, then delete the &lt;code&gt;tenants&lt;/code&gt; row last. Because each batch commits on its own, locks are released continuously instead of being held for the whole purge.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Executive Summary &amp;amp; Systems Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Index every foreign key column:&lt;/strong&gt; PostgreSQL does not do it for you. Without an index, each cascade step is a full sequential scan, which is what turns a fast delete into a long, lock-holding transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's duration, not lock escalation:&lt;/strong&gt; PostgreSQL holds ordinary &lt;code&gt;RowExclusiveLock&lt;/code&gt; during a cascade and never escalates to a table lock. The contention comes from one slow transaction holding many row locks, so the fix is to make the work fast and short.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't hard-delete large trees synchronously:&lt;/strong&gt; Soft-delete with &lt;code&gt;deleted_at&lt;/code&gt;, then purge in small committed batches from a background worker, using &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; so the purge yields to live traffic.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Seeing slow deletes or lock pile-ups on your Postgres database?&lt;/strong&gt; I help SaaS teams track down un-indexed foreign keys, redesign purge pipelines, and keep multi-tenant databases responsive under load. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/postgresql-foreign-key-cascade-locks-intent-lock-contention" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>performance</category>
      <category>sql</category>
    </item>
    <item>
      <title>The Anatomy of CVE-2024-4577: Why You Shouldn’t Abandon PHP Over a CGI Best-Fit Code Page Exploit</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Sat, 24 Oct 2026 17:28:00 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/the-anatomy-of-cve-2024-4577-why-you-shouldnt-abandon-php-over-a-cgi-best-fit-code-page-exploit-58pi</link>
      <guid>https://dev.to/usman_khan_io/the-anatomy-of-cve-2024-4577-why-you-shouldnt-abandon-php-over-a-cgi-best-fit-code-page-exploit-58pi</guid>
      <description>&lt;p&gt;When a critical Remote Code Execution (RCE) vulnerability hits a language that powers a large share of the web, the headlines predictably declare the death of the ecosystem. But CVE-2024-4577 is not a flaw in modern PHP itself. It is a subtle interaction between Windows character-encoding mechanics and legacy CGI execution. Here is an architectural teardown of how the best-fit mapping bypass worked, why PHP remains a solid production workhorse, and how to secure your infrastructure.&lt;/p&gt;

&lt;p&gt;Whenever a high-severity Remote Code Execution (RCE) flaw hits the headlines, tech Twitter and security newsletters immediately echo the same tired trope: &lt;em&gt;"Is it finally time to rewrite your entire backend in Go or Rust and abandon PHP?"&lt;/em&gt; Before you start a multi-month microservices rewrite for your SaaS, let's look at the systems engineering facts. &lt;strong&gt;CVE-2024-4577&lt;/strong&gt; is a study in edge-case OS encoding interactions, not a failure of modern PHP 8.x architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Root Cause: "Best-Fit" Character Mapping Bypasses
&lt;/h2&gt;

&lt;p&gt;To understand why this vulnerability exists, we have to look past standard PHP code and inspect how Windows parses command-line arguments when a web server invokes PHP in legacy &lt;strong&gt;Common Gateway Interface (CGI)&lt;/strong&gt; mode. CVE-2024-4577 is a bypass of an earlier fix, CVE-2012-1823, which hardened &lt;code&gt;php-cgi&lt;/code&gt; against query-string argument injection. It affects PHP on Windows only, and only when &lt;code&gt;php-cgi.exe&lt;/code&gt; is reachable, either in true CGI mode or in setups that expose the binary directly (the default XAMPP configuration was one well-known example).&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Soft-Hyphen (&lt;code&gt;%AD&lt;/code&gt;) Tricks the Command Line
&lt;/h3&gt;

&lt;p&gt;The CVE-2012-1823 fix taught PHP's CGI handler to refuse a query string that looks like a run of command-line switches, so a raw &lt;code&gt;-d&lt;/code&gt; (which sets PHP configuration directives on the fly) is rejected before it reaches the interpreter.&lt;/p&gt;

&lt;p&gt;The bypass exploits a Windows-specific step that happens &lt;em&gt;before&lt;/em&gt; PHP sees the arguments. When Windows converts a wide-character command line to the multibyte encoding PHP reads, under certain legacy code pages, such as &lt;strong&gt;Traditional Chinese (CP950)&lt;/strong&gt;, &lt;strong&gt;Simplified Chinese (CP936)&lt;/strong&gt;, or &lt;strong&gt;Japanese (CP932)&lt;/strong&gt;, it applies &lt;strong&gt;"best-fit" mapping&lt;/strong&gt;, substituting visually similar ASCII characters for ones that have no direct representation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Injected Byte:&lt;/strong&gt; The attacker sends a request whose query string contains the soft-hyphen byte, URL-encoded as &lt;code&gt;%AD&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Escaper Bypass:&lt;/strong&gt; PHP's CGI argument check inspects the incoming bytes. A soft hyphen (&lt;code&gt;0xAD&lt;/code&gt;) is not an ASCII hyphen (&lt;code&gt;0x2D&lt;/code&gt;), so the check does not treat it as the start of a switch, and the request proceeds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The OS Translation Trap:&lt;/strong&gt; As Windows builds the argument vector for &lt;code&gt;php-cgi.exe&lt;/code&gt;, best-fit mapping converts the soft hyphen &lt;code&gt;0xAD&lt;/code&gt; into a real ASCII hyphen &lt;code&gt;0x2D&lt;/code&gt; (&lt;code&gt;-&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Argument Injection:&lt;/strong&gt; What passed the filter as &lt;code&gt;%AD&lt;/code&gt; text arrives at the interpreter as a genuine &lt;code&gt;-&lt;/code&gt;, turning attacker text into real command-line switches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chaining a couple of injected directives lets the attacker turn on &lt;code&gt;allow_url_include&lt;/code&gt; and set &lt;code&gt;auto_prepend_file&lt;/code&gt; to &lt;code&gt;php://input&lt;/code&gt;, so the request body is executed as PHP, yielding unauthenticated &lt;strong&gt;Remote Code Execution&lt;/strong&gt; on the host. The exact byte sequence is public in the original advisory and is omitted here; the mechanism above is what matters for defending against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Don't Panic: Why Modern PHP Architecture Remains Robust
&lt;/h2&gt;

&lt;p&gt;Claims that this flaw marks the end of enterprise PHP miss three operational realities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modern Production Deployments Do Not Use CGI:&lt;/strong&gt; CGI is a decades-old execution interface. Modern PHP runs on &lt;strong&gt;PHP-FPM (FastCGI Process Manager)&lt;/strong&gt; behind Nginx or Apache, or inside application runtimes like &lt;strong&gt;Swoole&lt;/strong&gt; or &lt;strong&gt;FrankenPHP&lt;/strong&gt;. None of these invoke &lt;code&gt;php-cgi.exe&lt;/code&gt; with a per-request command line, so this particular injection vector does not apply to them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is Specific to Windows Code Pages:&lt;/strong&gt; The bug lives in a Windows wide-to-multibyte conversion step. Linux workloads (Debian, Alpine, or Ubuntu with UTF-8 locales) were never affected by it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Fix Shipped on Disclosure Day:&lt;/strong&gt; The PHP project released patched builds (8.1.29, 8.2.20, and 8.3.8) on the day of coordinated disclosure, hardening the argument handling so best-fit sequences can no longer smuggle in switches. Note that PHP 8.0 and earlier were end-of-life at the time and received no official fix, which is itself a reason to stay on a supported branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern PHP 8.3 is a strongly typed engine with an optional JIT, comfortably handling high request volumes in production. Migrating off a modern PHP stack because of a Windows CGI encoding artifact would be like replacing an entire cloud fleet because one legacy module was misconfigured.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Immediate Remediation &amp;amp; Defense-in-Depth
&lt;/h2&gt;

&lt;p&gt;If you manage Windows-based IIS or Apache infrastructure running PHP through CGI, work through these steps in order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Upgrade to a Patched PHP Runtime
&lt;/h3&gt;

&lt;p&gt;This is the real fix. Deploy an official release that contains the corrected argument handling, then confirm the running version with &lt;code&gt;php -v&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PHP 8.1.29&lt;/strong&gt; or higher on the 8.1 branch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHP 8.2.20&lt;/strong&gt; or higher on the 8.2 branch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHP 8.3.8&lt;/strong&gt; or higher on the 8.3 branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are on PHP 8.0 or earlier, those branches are end-of-life and received no patch; upgrade to a supported branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Temporary Edge Mitigation (If Patching Is Delayed)
&lt;/h3&gt;

&lt;p&gt;If a runtime upgrade is blocked by a deployment window, this Apache rule blocks the known soft-hyphen vector at the edge. Treat it as a stopgap, not a cure: best-fit mapping can involve other characters, so this does not replace patching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Temporary mitigation for Apache HTTP Server (NOT a substitute for patching)&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="sr"&gt; mod_rewrite.c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;RewriteEngine&lt;/span&gt; &lt;span class="ss"&gt;On&lt;/span&gt;
    &lt;span class="c"&gt;# Reject query strings containing the soft-hyphen sequence used by CVE-2024-4577&lt;/span&gt;
    &lt;span class="nc"&gt;RewriteCond&lt;/span&gt; %{QUERY_STRING} %ad [NC]
    &lt;span class="nc"&gt;RewriteRule&lt;/span&gt; .* - [F]
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Migrate Off Legacy CGI to PHP-FPM
&lt;/h3&gt;

&lt;p&gt;The durable architectural fix is to stop invoking &lt;code&gt;php-cgi&lt;/code&gt; per request. Serve PHP through FastCGI Process Manager (PHP-FPM) instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Recommended architecture: Nginx + PHP-FPM over FastCGI&lt;/span&gt;
&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;app.internal.net&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/html/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$query_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="nf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# PHP-FPM listener&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$document_root$fastcgi_script_name&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Executive Summary &amp;amp; Systems Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Understand the Root Mechanics:&lt;/strong&gt; CVE-2024-4577 is a Windows best-fit code page mapping bug affecting legacy CGI execution, not a core PHP language defect, and it is a bypass of the older CVE-2012-1823 fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop Running CGI Mode in Production:&lt;/strong&gt; Serve PHP through PHP-FPM, Swoole, or FrankenPHP rather than per-request &lt;code&gt;php-cgi&lt;/code&gt; invocation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep Runtimes Patched and Supported:&lt;/strong&gt; Stay on a supported branch (8.2.x / 8.3.x) so you receive security fixes like this one promptly.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Running PHP on Windows and unsure if you're exposed?&lt;/strong&gt; I help teams audit their backend security posture, move off legacy CGI, and harden PHP infrastructure for production. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/cve-2024-4577-php-cgi-argument-injection-exploit-fix" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>security</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Kubernetes Pod Churn &amp; Node Thrashing: Ephemeral Storage Exhaustion and Eviction Cascades</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Thu, 15 Oct 2026 20:52:32 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/kubernetes-pod-churn-node-thrashing-ephemeral-storage-exhaustion-and-eviction-cascades-4035</link>
      <guid>https://dev.to/usman_khan_io/kubernetes-pod-churn-node-thrashing-ephemeral-storage-exhaustion-and-eviction-cascades-4035</guid>
      <description>&lt;p&gt;When background worker pods continuously write temporary scratch files, un-rotated log buffers, or dynamic assets directly to container filesystems without explicit ephemeral storage limits, worker nodes quietly enter local disk pressure states. The kubelet then triggers aggressive pod evictions, sparking cluster-wide pod churn, Karpenter/Cluster Autoscaler thrashing, and AWS bill inflation. Here is an architectural deep dive into diagnosing local disk exhaustion and configuring resilient kubelet eviction boundaries.&lt;/p&gt;

&lt;p&gt;In high-throughput B2B SaaS platforms running containerized microservices, background processing workloads (e.g., generating PDF reports, processing video/image assets, compiling code, or handling CSV batch imports) frequently rely on local container file systems for temporary scratch space. Under heavy traffic, un-capped ephemeral storage usage creates an invisible cluster-level failure mode: &lt;strong&gt;Cascading Node Eviction and Auto-Scaler Thrashing&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Mechanics of OverlayFS and Ephemeral Storage Eviction
&lt;/h2&gt;

&lt;p&gt;By default, when a container writes files to its own filesystem (outside of explicitly mounted Persistent Volumes), those writes land on the node's local disk through the container runtime's writable layer, typically the &lt;strong&gt;OverlayFS storage driver&lt;/strong&gt;. Disk-backed &lt;code&gt;emptyDir&lt;/code&gt; volumes and container logs consume the same node disk. If a pod uses significant local disk space without ephemeral storage requests and limits in its manifest, it triggers a chain reaction across the host node.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Anatomy of a Node Eviction Cascade
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded Disk Growth:&lt;/strong&gt; A burst of PDF generation worker tasks writes 15GB of temporary files to &lt;code&gt;/tmp&lt;/code&gt; inside the container overlay filesystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubelet Disk Pressure Breach:&lt;/strong&gt; The node's disk crosses a kubelet hard eviction threshold (by default, &lt;code&gt;nodefs.available&lt;/code&gt; below 10% or &lt;code&gt;imagefs.available&lt;/code&gt; below 15%). The kubelet marks the node &lt;code&gt;DiskPressure = True&lt;/code&gt;, the node is tainted, and the scheduler stops placing new pods on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggressive Pod Eviction:&lt;/strong&gt; The kubelet first garbage-collects dead containers and unused images, then starts evicting pods. It ranks pods by whether their disk usage exceeds their ephemeral storage request, then by pod priority, then by usage. With no requests set, every pod counts as over its request, so priority dominates, and lower-priority core services can be evicted before the high-priority worker that is actually filling the disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Scaler Thrashing (Karpenter / Cluster Autoscaler):&lt;/strong&gt; Rescheduled evicted pods flood neighboring nodes, pushing those nodes into disk pressure as well. The autoscaler provisions new EC2 instances for pending pods while consolidation removes others, so nodes churn continuously and compute spend can multiply for the duration of the incident.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Diagnosing Ephemeral Storage Bloat in Production
&lt;/h2&gt;

&lt;p&gt;Pod objects do not report disk usage, so &lt;code&gt;kubectl get pods&lt;/code&gt; cannot show it. The kubelet's stats summary endpoint does. Run the following diagnostic commands to find which pods are consuming node disk and driving instability (requires &lt;code&gt;jq&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Top 20 pods by ephemeral storage usage (MiB) across all nodes&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;node &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; name | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;/ &lt;span class="nt"&gt;-f2&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;kubectl get &lt;span class="nt"&gt;--raw&lt;/span&gt; &lt;span class="s2"&gt;"/api/v1/nodes/&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="s2"&gt;/proxy/stats/summary"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; node &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'.pods[]
        | [$node, .podRef.namespace, .podRef.name,
           ((.["ephemeral-storage"].usedBytes // 0) / 1048576 | floor)]
        | @tsv'&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt; &lt;span class="nt"&gt;-k4&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="c"&gt;# Inspect recent eviction events&lt;/span&gt;
kubectl get events &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt; &lt;span class="nt"&gt;--field-selector&lt;/span&gt; &lt;span class="nv"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Evicted | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"ephemeral"&lt;/span&gt;

&lt;span class="c"&gt;# Detailed check of Node Disk Pressure conditions&lt;/span&gt;
kubectl describe node &amp;lt;node-name&amp;gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 8 &lt;span class="s2"&gt;"Conditions:"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pod status displaying &lt;code&gt;Evicted&lt;/code&gt; with the message &lt;code&gt;The node was low on resource: ephemeral-storage&lt;/code&gt; confirms your cluster is actively suffering from un-capped container write exhaustion.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Production-Ready Remediation: Strict Resource Limits &amp;amp; In-Memory &lt;code&gt;emptyDir&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Enforce Ephemeral Storage Requests and Limits
&lt;/h3&gt;

&lt;p&gt;Every Kubernetes manifest deployed to production must explicitly define &lt;code&gt;ephemeral-storage&lt;/code&gt; resource boundaries alongside CPU and memory parameters. This lets the scheduler place pods according to available disk, and when a container exceeds its storage limit, the kubelet evicts &lt;em&gt;only&lt;/em&gt; that pod instead of letting the whole node reach disk pressure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# PRODUCTION-GRADE DEPLOYMENT MANIFEST WITH EPHEMERAL LIMITS&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdf-generator-worker&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdf-generator-worker&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdf-generator-worker&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.internal.net/pdf-worker:v2.1.0&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2Gi"&lt;/span&gt;
            &lt;span class="na"&gt;ephemeral-storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1Gi"&lt;/span&gt; &lt;span class="c1"&gt;# Scheduler reserves this much node disk&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2000m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4Gi"&lt;/span&gt;            &lt;span class="c1"&gt;# Includes up to 2Gi of tmpfs scratch below&lt;/span&gt;
            &lt;span class="na"&gt;ephemeral-storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4Gi"&lt;/span&gt; &lt;span class="c1"&gt;# Hard ceiling: kubelet evicts this pod if exceeded&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/tmp/scratch&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scratch-volume&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scratch-volume&lt;/span&gt;
        &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Memory&lt;/span&gt; &lt;span class="c1"&gt;# Back temporary writes with RAM (tmpfs)&lt;/span&gt;
          &lt;span class="na"&gt;sizeLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2Gi&lt;/span&gt; &lt;span class="c1"&gt;# Cap scratch space so it cannot consume all memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Redirect High-Frequency Scratch Writes to In-Memory &lt;code&gt;tmpfs&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For workloads requiring ultra-fast, short-lived file creation (e.g., image resizing or PDF rendering), mount an &lt;code&gt;emptyDir&lt;/code&gt; volume backed by RAM (&lt;code&gt;medium: Memory&lt;/code&gt;). Writes to this volume bypass the node disk entirely, removing them as a source of disk pressure and speeding up file I/O. One catch: files in a memory-backed &lt;code&gt;emptyDir&lt;/code&gt; count toward the container's &lt;strong&gt;memory&lt;/strong&gt; limit, not its ephemeral storage limit. Size the memory limit to cover the application plus the scratch volume, as in the manifest above, or a full scratch directory will get the pod OOM-killed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Tuning Kubelet Eviction Thresholds and Karpenter Node Consolidation
&lt;/h2&gt;

&lt;p&gt;Reduce premature evictions by tuning kubelet eviction settings in your node configuration (for example, the kubelet config in your EKS node group launch template or Karpenter &lt;code&gt;EC2NodeClass&lt;/code&gt; user data):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Kubelet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Eviction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(kubelet-config.json)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evictionHard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memory.available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nodefs.available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nodefs.inodesFree"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"imagefs.available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15%"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evictionSoft"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nodefs.available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15%"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evictionSoftGracePeriod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nodefs.available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2m"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evictionMaxPodGracePeriod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then slow down Karpenter's consolidation so short traffic spikes don't cause node churn (Karpenter v1 API):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Karpenter v1 NodePool with churn guardrails&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;karpenter.sh/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePool&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;nodeClassRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;karpenter.k8s.aws&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EC2NodeClass&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
      &lt;span class="na"&gt;expireAfter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;720h&lt;/span&gt; &lt;span class="c1"&gt;# Rotate nodes every 30 days for patching&lt;/span&gt;
  &lt;span class="na"&gt;disruption&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;consolidationPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WhenEmptyOrUnderutilized&lt;/span&gt;
    &lt;span class="na"&gt;consolidateAfter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt; &lt;span class="c1"&gt;# Wait before consolidating so spikes can settle&lt;/span&gt;
    &lt;span class="na"&gt;budgets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10%"&lt;/span&gt;     &lt;span class="c1"&gt;# Never disrupt more than 10% of nodes at once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Soft Eviction Grace Periods:&lt;/strong&gt; With a soft threshold, the kubelet only evicts if disk stays below 15% free for a full 2 minutes. Short spikes that clear on their own (a batch job finishing, logs rotating, image garbage collection) no longer trigger evictions. The hard threshold at 10% still protects the node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Karpenter Consolidation Guardrails:&lt;/strong&gt; &lt;code&gt;consolidateAfter&lt;/code&gt; delays consolidation so nodes are not removed the moment utilization dips, and disruption &lt;code&gt;budgets&lt;/code&gt; cap how many nodes Karpenter can replace at once. &lt;code&gt;expireAfter&lt;/code&gt; handles routine node rotation; it is not a churn control on its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Executive Summary &amp;amp; Systems Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always Specify &lt;code&gt;ephemeral-storage&lt;/code&gt; Requests and Limits:&lt;/strong&gt; Unbounded container writes will fill host node disks and trigger cascading evictions across unrelated pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use RAM-Backed &lt;code&gt;emptyDir&lt;/code&gt; for Transient Files:&lt;/strong&gt; Mount temporary scratch directories with &lt;code&gt;medium: Memory&lt;/code&gt; to keep high-frequency file I/O off node disks, and raise the memory limit to cover the scratch size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set Soft Kubelet Eviction Windows:&lt;/strong&gt; Configure grace periods on nodes so brief disk spikes can clear before the kubelet evicts pods, and add consolidation delays and disruption budgets to your autoscaler.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Are pod evictions and node churn driving up your Kubernetes bill?&lt;/strong&gt; I help SaaS teams right-size workloads, tune kubelet and Karpenter settings, and build clusters that stay stable under heavy batch traffic. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/kubernetes-pod-churn-ephemeral-storage-node-thrashing" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Distributed Idempotency Key Collisions: Clock Skew Mutex Leaks, Race Conditions, and Double-Execution Vulnerabilities</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Thu, 15 Oct 2026 20:41:01 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/distributed-idempotency-key-collisions-clock-skew-mutex-leaks-race-conditions-and-18kk</link>
      <guid>https://dev.to/usman_khan_io/distributed-idempotency-key-collisions-clock-skew-mutex-leaks-race-conditions-and-18kk</guid>
      <description>&lt;p&gt;Wrapping API handlers with naive Redis key locks (&lt;code&gt;SET key val NX EX 30&lt;/code&gt;) to enforce idempotency creates an illusion of safety. Under concurrent traffic bursts, worker stalls, network partitions, and non-atomic status releases can cause duplicate executions: charging credit cards twice or creating duplicate database records. Here is an architectural deep dive into diagnosing distributed idempotency lock breaches and engineering atomic state transition engines.&lt;/p&gt;

&lt;p&gt;In mission-critical B2B SaaS platforms, especially those handling payment processing, billing generation, or external state provisioning, enforcing strict &lt;strong&gt;exactly-once execution&lt;/strong&gt; via idempotency keys is a core architectural requirement. However, naive Redis lock implementations often contain subtle, high-concurrency race conditions that silently break under network partitions, worker thread stalls, and distributed clock skew.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Fallacy of Naive Redis Mutex Locks
&lt;/h2&gt;

&lt;p&gt;A standard anti-pattern in Node.js and Go microservices is using a simple Redis string set operation to acquire an execution lock prior to invoking business logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NAÏVE ANTI-PATTERN: Vulnerable Idempotency Lock&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handlePaymentNaive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idempotency-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`idempotency:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Attempt to acquire execution lock for 10 seconds&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acquired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;acquired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Concurrent request in progress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Execute heavy business logic (e.g., Stripe API call + DB mutation)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Cache response for future retries&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`response:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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;h3&gt;
  
  
  Three Silent Failure Vectors
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Worker Stall / GC Pause Breach:&lt;/strong&gt; If a Garbage Collection (GC) pause, unhandled event loop block, or downstream Stripe API latency keeps the worker busy for longer than 10 seconds, the Redis TTL expires automatically. Redis deletes the lock while the first request is still executing. A concurrent retry attempt instantly acquires the lock and executes the payment a second time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Non-Atomic Release Bug:&lt;/strong&gt; If a worker finishes execution and calls &lt;code&gt;redis.del(lockKey)&lt;/code&gt; or overwrites the key without verifying ownership, it might delete or clobber a lock that was newly acquired by a second worker after the first worker's lock expired, unlocking the door for a third incoming request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clock Skew &amp;amp; Failover Split-Brain:&lt;/strong&gt; With a single Redis primary, TTLs run on the Redis server's own clock, so application-server skew does not shorten them. Problems appear in distributed setups: multi-master schemes like Redlock depend on bounded clock drift across independent Redis nodes, and Redis replication is asynchronous, so a lock written to a primary can vanish during failover. In both cases, two workers can believe they hold the same lock simultaneously.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Architectural Pattern: Atomic Two-Phase State Engine
&lt;/h2&gt;

&lt;p&gt;To close these race conditions, replace primitive boolean lock flags with a &lt;strong&gt;3-state state engine&lt;/strong&gt; backed by atomic Lua scripts inside Redis. Every state change, both acquiring the key and finalizing it, happens in a single atomic script that checks who owns the key.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Machine States
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PROCESSING&lt;/code&gt;: A request is actively executing. Contains a unique, randomly generated worker UUID (the owner) and an acquisition timestamp.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COMPLETED&lt;/code&gt;: The request finished successfully. Holds the serialized HTTP response status and body payload for replay.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RELEASED&lt;/code&gt; (key deleted): The operation failed with a retryable error. The owning worker deletes the key so the client can retry cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 1: Atomic Acquisition via Redis Lua Scripting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ACQUIRE_LUA_SCRIPT: Atomic Idempotency Lock Acquisition&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lockKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;workerUuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lockTtlSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;existingState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;existingState&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c1"&gt;-- Key does not exist: create PROCESSING state with owner UUID&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cjson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PROCESSING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workerUuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'EX'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lockTtlSeconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"ACQUIRED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;-- Key exists: return current state payload to application&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"LOCKED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;existingState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Phase 2: Ownership-Checked Finalization
&lt;/h3&gt;

&lt;p&gt;Completing or releasing the key must be just as atomic. This script only lets the worker that still owns the &lt;code&gt;PROCESSING&lt;/code&gt; lock write the final state. A stale worker whose lock already expired gets &lt;code&gt;0&lt;/code&gt; back and leaves the key untouched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- FINALIZE_LUA_SCRIPT: Only the current owner may complete or release&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lockKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;workerUuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;-- "COMPLETE" or "RELEASE"&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cjson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="s1"&gt;'PROCESSING'&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="n"&gt;workerUuid&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;-- Lock expired and was taken over: do not touch it&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'COMPLETE'&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'EX'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DEL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Production-Ready TypeScript Middleware Implementation
&lt;/h2&gt;

&lt;p&gt;The application layer acquires the key before running the handler, replays cached responses for completed keys, and finalizes through the ownership-checked script, never with a blind &lt;code&gt;SET&lt;/code&gt; or &lt;code&gt;DEL&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PRODUCTION-GRADE IDEMPOTENCY ENGINE (TypeScript)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;v4&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ACQUIRE_LUA_SCRIPT and FINALIZE_LUA_SCRIPT hold the two Lua scripts above&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createIdempotencyMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idempotency-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;global&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`idempotency:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workerUuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockTtl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 30 second execution ceiling&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Execute Atomic Lua Acquisition Script&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;ACQUIRE_LUA_SCRIPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;workerUuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;lockTtl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PROCESSING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Request is actively being processed by another worker&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CONCURRENT_REQUEST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An identical request with this Idempotency-Key is currently executing.&lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Replay identical response directly from cache&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Cache-Lookup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HIT-IDEMPOTENT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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="c1"&gt;// 2. Intercept the response and finalize ONLY if this worker still owns the lock&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;succeeded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completedPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;body&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;redis&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;FINALIZE_LUA_SCRIPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;workerUuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;succeeded&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RELEASE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;completedPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="mi"&gt;86400&lt;/span&gt; &lt;span class="c1"&gt;// 24-hour replay retention&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;owned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;owned&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Our lock expired mid-request and another worker took over.&lt;/span&gt;
            &lt;span class="c1"&gt;// Redis state is protected; the DB constraint below must catch any duplicate side effect.&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[IDEMPOTENCY] Lock lost before completion:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lockKey&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="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[IDEMPOTENCY] Finalize failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;originalJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nf"&gt;next&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sketch hooks &lt;code&gt;res.json&lt;/code&gt;. If your handlers also respond with &lt;code&gt;res.send&lt;/code&gt; or &lt;code&gt;res.end&lt;/code&gt;, wrap those the same way, or the key will sit in &lt;code&gt;PROCESSING&lt;/code&gt; until its TTL expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Secondary Guardrail: Database-Level Unique Constraints
&lt;/h2&gt;

&lt;p&gt;The ownership check stops a stale worker from corrupting Redis state, but it cannot undo work that worker already did. If a lock expires mid-request, two workers can still both reach the payment step. Redis should act as your fast, first-line filter. For financial mutations, &lt;strong&gt;never rely on Redis alone&lt;/strong&gt;. Always back distributed application locks with strict relational database constraints at the storage engine level.&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="c1"&gt;-- Database-Level Fail-Safe: Unique Idempotency Key Index&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;payment_transactions&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;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;tenant_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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;idempotency_key&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&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;amount_cents&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&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;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;CLOCK_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="c1"&gt;-- Absolute DB Guardrail against double execution&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;unique_tenant_idempotency&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;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&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;Insert this row &lt;em&gt;before&lt;/em&gt; calling the payment provider, so the reservation happens first. If a Redis lock expires under an extreme 30-second stall, the database will reject the second worker's duplicate insert with a &lt;code&gt;23505 unique_violation&lt;/code&gt; error before any money moves. For the external call itself, pass the same idempotency key to the provider (Stripe, for example, accepts an &lt;code&gt;Idempotency-Key&lt;/code&gt; header), so even a duplicate API request is charged only once.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Executive Summary &amp;amp; Systems Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never Use Plain &lt;code&gt;SET NX EX&lt;/code&gt; for Idempotency:&lt;/strong&gt; Always store a structured state payload containing a unique worker UUID to verify lock ownership.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never Release Un-Owned Locks:&lt;/strong&gt; Use Lua scripts to ensure a worker completes or deletes a lock key ONLY if its worker UUID matches the key payload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce Storage-Layer Unique Constraints:&lt;/strong&gt; Always back Redis idempotency checks with unique compound indexes (e.g., &lt;code&gt;(tenant_id, idempotency_key)&lt;/code&gt;) inside PostgreSQL or MySQL, and forward the key to external payment providers.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Worried about double charges or duplicate records in your payment flows?&lt;/strong&gt; I help SaaS teams design idempotent APIs, safe distributed locking, and database guardrails that hold up under retries and failures. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/distributed-idempotency-key-collisions-clock-skew-race-conditions" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>distributedsystems</category>
      <category>api</category>
      <category>node</category>
    </item>
    <item>
      <title>PostgreSQL JSONB Write Amplification: TOAST Chunk Fragmentation and Unseen I/O Thrashing</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Tue, 13 Oct 2026 16:03:10 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/postgresql-jsonb-write-amplification-toast-chunk-fragmentation-and-unseen-io-thrashing-2b3l</link>
      <guid>https://dev.to/usman_khan_io/postgresql-jsonb-write-amplification-toast-chunk-fragmentation-and-unseen-io-thrashing-2b3l</guid>
      <description>&lt;p&gt;Storing flexible schema payloads inside PostgreSQL JSONB columns feels like the ultimate developer productivity shortcut. However, mutating a single nested field inside a large JSONB document forces PostgreSQL to rewrite the entire compressed TOAST object on disk, triggering massive write amplification, Write-Ahead Log (WAL) flooding, and IOPS exhaustion. Here is an architectural deep dive into TOAST storage mechanics and how to eliminate JSONB write amplification.&lt;/p&gt;

&lt;p&gt;In growth-stage B2B SaaS platforms, using PostgreSQL &lt;code&gt;JSONB&lt;/code&gt; columns for multi-tenant configuration blocks, feature flags, or dynamic custom attributes is a common architectural pattern. While schema-less flexibility speeds up initial feature shipping, running high-frequency &lt;code&gt;UPDATE&lt;/code&gt; statements against JSONB documents creates an invisible, compounding infrastructure trap: catastrophic &lt;strong&gt;Write Amplification&lt;/strong&gt; driven by the PostgreSQL TOAST (The Oversized-Attribute Storage Technique) engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Mechanics of TOAST Storage and Partial JSONB Mutations
&lt;/h2&gt;

&lt;p&gt;PostgreSQL stores data in fixed-size pages of &lt;strong&gt;8KB&lt;/strong&gt; by default. When a row exceeds roughly &lt;strong&gt;2KB&lt;/strong&gt;, PostgreSQL transparently compresses its large values (such as long text blocks or serialized JSONB documents) and, if they are still too big, moves them out of line into an auxiliary storage area known as the &lt;strong&gt;TOAST table&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hidden Rewrite Penalty
&lt;/h3&gt;

&lt;p&gt;A widespread architectural misconception is assuming that updating a single key inside a JSONB document using functions and operators like &lt;code&gt;jsonb_set()&lt;/code&gt; or &lt;code&gt;||&lt;/code&gt; performs a localized, in-place edit on disk. In reality, PostgreSQL has no partial-update path for TOASTed values: any change produces a brand-new copy of the whole value.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Whole-Document Serialization:&lt;/strong&gt; When you update a 1-byte boolean inside a 60KB JSONB column, PostgreSQL reads the compressed TOAST chunks, decompresses the document, applies the mutation, re-compresses the entire 60KB payload, and writes completely new TOAST chunks to disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WAL Flooding:&lt;/strong&gt; Because every new TOAST chunk is WAL-logged, changing a single character generates tens of kilobytes of WAL data. This floods replication streams, exhausts disk write IOPS, and drastically delays read-replica synchronization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double MVCC Overhead:&lt;/strong&gt; The parent table record gets a new dead tuple (forcing a primary heap write), while the TOAST table acquires multiple new dead chunk tuples (forcing secondary heap and TOAST index writes), doubling autovacuum cleanup workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Measuring the Amplification Factor: Code &amp;amp; Query Proof
&lt;/h2&gt;

&lt;p&gt;To demonstrate the magnitude of JSONB write amplification, consider a simple settings table holding tenant feature toggles:&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="c1"&gt;-- Production Target Schema with Large JSONB Document&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenant_settings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="n"&gt;UUID&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;company_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&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;config_payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="c1"&gt;-- Large nested user preferences &amp;amp; permissions&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Seed a ~70KB document of 1,500 keys; random values defeat compression,&lt;/span&gt;
&lt;span class="c1"&gt;-- so the payload is forced into out-of-line TOAST storage&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;tenant_settings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="s1"&gt;'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Acme Corp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;jsonb_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'feature_a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;jsonb_object_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pref_'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now flip a single flag and measure exactly how much WAL that one statement generates (run in &lt;code&gt;psql&lt;/code&gt;):&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="c1"&gt;-- Capture the WAL position before the update&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;before_lsn&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;gset&lt;/span&gt;

&lt;span class="c1"&gt;-- Seemingly innocent partial JSONB update&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tenant_settings&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;config_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonb_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config_payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{feature_a}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- WAL bytes written by that single-flag change&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pg_wal_lsn_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'before_lsn'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;wal_generated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logical change is a single boolean, but PostgreSQL rewrites and WAL-logs the entire compressed document. In a test on PostgreSQL 16 with this exact setup, the single flag flip wrote &lt;strong&gt;about 58KB of WAL&lt;/strong&gt;. The same change stored in a plain boolean column (Strategy A below) wrote &lt;strong&gt;under 200 bytes&lt;/strong&gt;, roughly &lt;strong&gt;300x less&lt;/strong&gt;. Multiply that by every settings save across thousands of tenants and the I/O cost becomes the dominant load on your database.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Architectural Solutions: Eliminating JSONB Write Traps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Strategy A: Hybrid Column Decomposition (Extract Fast-Changing Attributes)
&lt;/h3&gt;

&lt;p&gt;Identify attributes within the JSONB payload that experience high mutation frequency (such as &lt;code&gt;last_active_at&lt;/code&gt;, &lt;code&gt;login_counter&lt;/code&gt;, or &lt;code&gt;active_status&lt;/code&gt;) and promote them to dedicated, typed relational columns on the main table. When an &lt;code&gt;UPDATE&lt;/code&gt; leaves the JSONB column untouched, PostgreSQL reuses the existing TOAST pointer instead of rewriting the document.&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="c1"&gt;-- Remediation: Decouple Hot Fields&lt;/span&gt;
&lt;span class="c1"&gt;-- Use non-volatile defaults (now(), not clock_timestamp()) so ADD COLUMN&lt;/span&gt;
&lt;span class="c1"&gt;-- stays a fast metadata-only change instead of a full table rewrite&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenant_settings&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&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="c1"&gt;-- Now updates hit inline page tuples WITHOUT touching the large TOAST object&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tenant_settings&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&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;WHERE&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Strategy B: Key-Value EAV Partitioning for Unstructured Data
&lt;/h3&gt;

&lt;p&gt;If you must store thousands of highly dynamic key-value pairs per tenant, decompose the monolithic JSONB document into an explicit narrow key-value table. This turns each update from a cost proportional to the whole document size into a small, constant-size single-row write.&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="c1"&gt;-- Normalized EAV Storage for Highly Dynamic Attributes&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenant_attributes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tenant_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;tenant_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;attribute_key&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&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;attribute_value&lt;/span&gt; &lt;span class="n"&gt;JSONB&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="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;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Mutating a single setting rewrites ONLY one small row, keeping I/O minimal&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;tenant_attributes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'feature_a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;attribute_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. TOAST Storage Tuning for Read-Heavy JSONB Documents
&lt;/h2&gt;

&lt;p&gt;For JSONB columns that are large but rarely updated, adjust the TOAST compression strategy to minimize CPU overhead during read operations. Both settings apply only to values written after the change; existing rows keep their current format until they are rewritten.&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="c1"&gt;-- Switch TOAST compression from default pglz to LZ4 (PostgreSQL 14+)&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenant_settings&lt;/span&gt; &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;config_payload&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;COMPRESSION&lt;/span&gt; &lt;span class="n"&gt;lz4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Or skip compression entirely: more disk space, less CPU on every read&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenant_settings&lt;/span&gt; &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;config_payload&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;STORAGE&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Executive Summary &amp;amp; Systems Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never Mutate Large JSONB Documents Frequently:&lt;/strong&gt; If a JSONB value is large enough to be TOASTed (roughly 2KB+) and undergoes frequent updates, it WILL trigger extreme TOAST write amplification and WAL bloat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Promote Hot Keys to Relational Columns:&lt;/strong&gt; Extract frequently updated keys out of JSONB blocks and store them as top-level typed columns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor WAL Generation Rates:&lt;/strong&gt; Use &lt;code&gt;pg_wal_lsn_diff()&lt;/code&gt; to track unexpected WAL volume spikes tied to JSONB update workloads.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Are JSONB updates quietly eating your database I/O budget?&lt;/strong&gt; I help SaaS teams find hidden write amplification, redesign hot JSONB schemas, and tune Postgres storage for heavy write loads. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/postgresql-jsonb-write-amplification-toast-fragmentation" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>performance</category>
      <category>sql</category>
    </item>
    <item>
      <title>Server-Sent Events (SSE) Backpressure Breaks: Memory Leaks and Socket Starvation in High-Throughput LLM Pipelines</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Sat, 10 Oct 2026 14:27:10 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/server-sent-events-sse-backpressure-breaks-memory-leaks-and-socket-starvation-in-high-throughput-10hd</link>
      <guid>https://dev.to/usman_khan_io/server-sent-events-sse-backpressure-breaks-memory-leaks-and-socket-starvation-in-high-throughput-10hd</guid>
      <description>&lt;p&gt;Streaming LLM completions over Server-Sent Events (SSE) looks trivial in sandbox demos, but unhandled backpressure under real-world network conditions can destroy backend application servers. When slow clients fail to consume token chunks as fast as upstream model APIs generate them, unbounded Node.js internal stream buffers trigger memory leaks and OOM process crashes. Here is an architectural deep dive into enforcing stream backpressure, managing drain events, and protecting socket thread pools.&lt;/p&gt;

&lt;p&gt;Integrating generative AI features into enterprise SaaS interfaces relies heavily on streaming response tokens via Server-Sent Events (SSE) to lower Time-To-First-Token (TTFT). However, naïve stream piping implementations assume the client's network connection can consume data at the same rate the upstream model provider (e.g., OpenAI or Anthropic) yields tokens. Under real-world network conditions, this assumption breaks down catastrophically.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Mechanics of Unhandled Backpressure in Event Streams
&lt;/h2&gt;

&lt;p&gt;In Node.js and similar asynchronous event-driven runtimes, HTTP responses are writable streams backed by internal memory buffers. When an upstream LLM API yields token deltas at high velocity (e.g., 80 to 120 tokens per second), the application server forwards these chunks directly into the client's HTTP response socket.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Slow Clients Trigger Node.js Heap Exhaustion
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Producer-Consumer Velocity Mismatch:&lt;/strong&gt; An upstream LLM provider generates text rapidly, while a client on a high-latency mobile connection or throttled browser tab consumes data slowly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded Internal Buffering:&lt;/strong&gt; When &lt;code&gt;res.write(chunk)&lt;/code&gt; is called faster than the TCP socket can flush frames over the wire, Node.js buffers the overflow chunks in process memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cascading Out-Of-Memory (OOM) Crashes:&lt;/strong&gt; Under high concurrency (e.g., 500 parallel streaming sessions), unbounded buffer growth consumes gigabytes of memory, ending in &lt;code&gt;JavaScript heap out of memory&lt;/code&gt; process crashes across backend instances.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. The Flaw in Naive SSE Controller Implementations
&lt;/h2&gt;

&lt;p&gt;A standard anti-pattern in Node.js/Express controllers involves directly listening to LLM token events and pushing them into the response without inspecting return values or handling stream backpressure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NAÏVE ANTI-PATTERN: Zero Backpressure Control&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;streamLLMResponseNaive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keep-alive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upstreamStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// FAILURE VECTOR: If client socket is slow, res.write returns FALSE&lt;/span&gt;
  &lt;span class="c1"&gt;// Naïve code ignores this signal, buffering unbounded chunks in RAM!&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;upstreamStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data: [DONE]&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&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;When &lt;code&gt;res.write()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt;, it explicitly signals that the stream's internal &lt;code&gt;highWaterMark&lt;/code&gt; threshold has been breached. Continuing to push data ignores this backpressure boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Production-Ready Backpressure-Aware SSE Pipeline
&lt;/h2&gt;

&lt;p&gt;To safely stream LLM tokens at scale, backend services must inspect &lt;code&gt;res.write()&lt;/code&gt; signals. If a write returns &lt;code&gt;false&lt;/code&gt;, the worker must pause the upstream reader until the underlying TCP socket fires a &lt;code&gt;'drain'&lt;/code&gt; event. The drain wait must also be cancellable: if the client disconnects while the buffer is full, &lt;code&gt;'drain'&lt;/code&gt; never fires, and an un-cancellable wait becomes its own memory leak.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PRODUCTION PATTERN: Explicit Backpressure &amp;amp; Abort Handling&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;once&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;streamLLMResponseResilient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keep-alive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Accel-Buffering&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Disable Nginx proxy buffering&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;abortController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Clean up upstream request if client abruptly disconnects.&lt;/span&gt;
  &lt;span class="c1"&gt;// Listen on res, not req: req 'close' fires as soon as the request body is read.&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writableEnded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upstreamStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;upstreamStream&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aborted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// 1. Write chunk and inspect backpressure buffer capacity&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canContinue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 2. If internal buffer is full, pause until the socket drains&lt;/span&gt;
      &lt;span class="c1"&gt;//    (or the client disconnects, which rejects with AbortError)&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;canContinue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aborted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data: [DONE]&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[SSE_STREAM] Client disconnected mid-stream. Resource freed.&lt;/span&gt;&lt;span class="dl"&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[SSE_STREAM_ERROR]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Headers are already sent once streaming starts, so just close the stream&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headersSent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Reverse Proxy Tuning: Nginx and AWS ALB Buffering Traps
&lt;/h2&gt;

&lt;p&gt;Backpressure handling in application code is rendered useless if reverse proxies in your network path silently buffer event streams in memory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nginx Proxy Buffering:&lt;/strong&gt; By default, Nginx buffers upstream HTTP responses before forwarding them to clients. This breaks real-time token delivery and obscures backpressure signals. Always pass the &lt;code&gt;X-Accel-Buffering: no&lt;/code&gt; header or set &lt;code&gt;proxy_buffering off;&lt;/code&gt; in location blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS ALB Idle Timeouts:&lt;/strong&gt; Ensure your AWS Application Load Balancer idle timeout exceeds the maximum expected LLM completion duration (e.g., set to 120+ seconds) to prevent premature 504 Gateway Timeouts on long reasoning chains.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Executive Summary &amp;amp; Systems Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Respect &lt;code&gt;res.write()&lt;/code&gt; Return Values:&lt;/strong&gt; Never write to an HTTP response stream blindly. Pause upstream generation when &lt;code&gt;res.write()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; and await the &lt;code&gt;'drain'&lt;/code&gt; event, with an abort signal so the wait can't hang forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach Abort Controllers to Client Sockets:&lt;/strong&gt; Always listen for &lt;code&gt;res.on('close')&lt;/code&gt; to terminate upstream LLM API connections immediately when users navigate away or close tab sockets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable Proxy Buffering:&lt;/strong&gt; Configure edge proxies and gateways explicitly to allow immediate, unbuffered chunk passthrough for &lt;code&gt;text/event-stream&lt;/code&gt; content types.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Are your LLM streaming endpoints leaking memory or dropping connections under load?&lt;/strong&gt; I help SaaS teams build production-grade AI streaming pipelines with proper backpressure, abort handling, and proxy configuration. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/sse-backpressure-memory-leaks-llm-streaming-pipes" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>node</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>PostgreSQL MVCC Autovacuum Starvation: HOT Update Breakdown &amp; Table Bloat under Heavy Write Loads</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Fri, 09 Oct 2026 15:32:22 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/postgresql-mvcc-autovacuum-starvation-hot-update-breakdown-table-bloat-under-heavy-write-loads-2e1</link>
      <guid>https://dev.to/usman_khan_io/postgresql-mvcc-autovacuum-starvation-hot-update-breakdown-table-bloat-under-heavy-write-loads-2e1</guid>
      <description>&lt;p&gt;When PostgreSQL tables scale past hundreds of millions of rows under high-frequency updates, default autovacuum configurations quietly break. The failure of Heap Only Tuple (HOT) updates triggers exponential index bloat, disk I/O thrashing, and database starvation. Here is an architectural deep dive into diagnosing HOT update degradation, tuning fillfactors, and optimizing autovacuum for heavy SaaS write workloads.&lt;/p&gt;

&lt;p&gt;In high-throughput B2B SaaS platforms, relational databases frequently handle thousands of state mutations per second: updating tenant session tokens, subscription statuses, or webhook dispatch counters. Under default PostgreSQL settings, Multi-Version Concurrency Control (MVCC) mechanics can silently trigger catastrophic table and index bloat, degrading query performance from single-digit milliseconds to multi-second timeouts.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Mechanics of MVCC and the HOT Update Failure Vector
&lt;/h2&gt;

&lt;p&gt;PostgreSQL handles concurrent operations via MVCC. When an application executes an &lt;code&gt;UPDATE&lt;/code&gt; statement, Postgres does not overwrite the existing row on disk in place. Instead, it creates a new row version (a new tuple) and marks the old tuple as dead. Dead tuples remain on disk until cleaned up by the &lt;code&gt;autovacuum&lt;/code&gt; daemon process.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Heap Only Tuple (HOT) Updates Prevent Index Bloat
&lt;/h3&gt;

&lt;p&gt;Normally, inserting a new tuple requires updating every index attached to the table. To avoid this massive I/O penalty, Postgres uses &lt;strong&gt;Heap Only Tuple (HOT) updates&lt;/strong&gt;. A HOT update occurs when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;UPDATE&lt;/code&gt; does not modify any column referenced by a table index.&lt;/li&gt;
&lt;li&gt;The target page in heap memory has sufficient free space to store the new tuple version alongside the old one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When HOT criteria are met, Postgres links the new tuple directly from the old tuple inside the same data page. B-Tree indexes continue pointing to the original root tuple, completely avoiding index write amplification.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Silent Breakdown Under Production Load
&lt;/h3&gt;

&lt;p&gt;When an application updates an indexed column (such as updating &lt;code&gt;updated_at&lt;/code&gt; or &lt;code&gt;last_seen_at&lt;/code&gt; where an index exists), or when heap pages fill up completely (100% page density), &lt;strong&gt;HOT updates fail completely&lt;/strong&gt;. Every row update forces new index pointers into every B-tree index on the table, resulting in severe index bloat that autovacuum cannot reclaim efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Diagnosing HOT Update Failure and Autovacuum Starvation
&lt;/h2&gt;

&lt;p&gt;To detect if your write-heavy tables are suffering from HOT update degradation, run the following diagnostic query on your production PostgreSQL instance:&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="c1"&gt;-- Diagnostic Query: Evaluating HOT Update Success Ratios across Tables&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;schemaname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_tup_upd&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_updates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_tup_hot_upd&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hot_updates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_tup_hot_upd&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_tup_upd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hot_update_ratio_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_dead_tup&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dead_tuple_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_autovacuum&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_user_tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n_tup_upd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;n_tup_upd&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;code&gt;hot_update_ratio_pct&lt;/code&gt; falls below &lt;strong&gt;80–90%&lt;/strong&gt; on a table experiencing heavy updates, your database is actively incurring massive index write amplification and page fragmentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Architectural Remediation: Table Fillfactor &amp;amp; Autovacuum Tuning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Setting Table-Level Fillfactors for HOT Space Allocation
&lt;/h3&gt;

&lt;p&gt;By default, PostgreSQL fills heap pages to 100% capacity during initial &lt;code&gt;INSERT&lt;/code&gt; operations. Consequently, subsequent &lt;code&gt;UPDATE&lt;/code&gt; calls have zero free space on the page for a new HOT tuple. Reduce the table fillfactor to reserve space on heap pages explicitly for row updates.&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="c1"&gt;-- Reduce page density to 80% to reserve 20% page space for HOT updates&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenant_session_states&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fillfactor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Rewrite existing pages so the new fillfactor applies to old data too.&lt;/span&gt;
&lt;span class="c1"&gt;-- VACUUM FULL takes an exclusive lock; in production, use pg_repack instead.&lt;/span&gt;
&lt;span class="k"&gt;VACUUM&lt;/span&gt; &lt;span class="k"&gt;FULL&lt;/span&gt; &lt;span class="n"&gt;tenant_session_states&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Aggressive Per-Table Autovacuum Tuning
&lt;/h3&gt;

&lt;p&gt;Global default autovacuum parameters are far too conservative for high-volume SaaS databases. Instead of waiting for 20% of a multi-million row table to turn into dead tuples before vacuuming, configure aggressive, scale-factor thresholds on write-heavy tables:&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="c1"&gt;-- Configure Aggressive Autovacuum Parameters for High-Throughput Write Tables&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenant_session_states&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;autovacuum_vacuum_scale_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- Trigger vacuum after 2% row changes (default is 20%)&lt;/span&gt;
    &lt;span class="n"&gt;autovacuum_vacuum_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- Minimum dead tuple threshold&lt;/span&gt;
    &lt;span class="n"&gt;autovacuum_vacuum_cost_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;-- Increase I/O credit allocation for this table&lt;/span&gt;
    &lt;span class="n"&gt;autovacuum_vacuum_cost_delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;           &lt;span class="c1"&gt;-- Reduce I/O delay between vacuum cycles (ms)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Removing Redundant &amp;amp; Unused Indexes
&lt;/h2&gt;

&lt;p&gt;Every additional index on a table reduces the probability of a HOT update. Eliminate synthetic indexes on frequently updated columns (such as timestamps used strictly for auditing rather than filtering) and drop duplicate B-tree indexes.&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="c1"&gt;-- Identifying Unused Indexes Causing HOT Update Blockers&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;schemaname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;indexrelname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;idx_scan&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;number_of_scans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;index_size&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_user_indexes&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_index&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;idx_scan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;indisunique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Executive Summary &amp;amp; Systems Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolate Frequently Mutated Attributes:&lt;/strong&gt; Move fast-changing counter columns (e.g., &lt;code&gt;last_active_at&lt;/code&gt;, &lt;code&gt;login_count&lt;/code&gt;) into a dedicated secondary state table to protect the primary table from HOT update failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never Leave High-Write Tables at Fillfactor 100:&lt;/strong&gt; Set &lt;code&gt;fillfactor = 70-80&lt;/code&gt; on high-frequency update tables to give PostgreSQL the required heap page room for HOT tuples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tune Autovacuum per Table:&lt;/strong&gt; Override global defaults on high-volume tables to vacuum dead tuples incrementally before index bloat degrades query execution plans.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Is table bloat or autovacuum lag slowing down your Postgres database?&lt;/strong&gt; I help SaaS teams audit database performance, tune autovacuum and fillfactors, and design multi-tenant data architectures that hold up under heavy write loads. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/postgresql-mvcc-autovacuum-starvation-hot-update-breakdown" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>performance</category>
      <category>sql</category>
    </item>
    <item>
      <title>Production AI &amp; LLM Pipelines: Guardrails, Streaming Resilience, and Cost-Aware Fallbacks</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Thu, 08 Oct 2026 21:09:12 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/production-ai-llm-pipelines-guardrails-streaming-resilience-and-cost-aware-fallbacks-3ki3</link>
      <guid>https://dev.to/usman_khan_io/production-ai-llm-pipelines-guardrails-streaming-resilience-and-cost-aware-fallbacks-3ki3</guid>
      <description>&lt;p&gt;Moving generative AI features from prototype scripts to mission-critical SaaS production requires far more than wrapping an OpenAI or Anthropic API client. Upstream API timeouts, rate-limit spikes, context window overflows, and malformed JSON outputs cause cascading UI crashes if unhandled. Here is an architectural playbook for building streaming-resilient LLM pipelines with structural JSON guardrails, dynamic model fallbacks, and token budget safety nets.&lt;/p&gt;

&lt;p&gt;Deploying Large Language Models into enterprise SaaS platforms introduces failure modes never encountered in traditional REST microservices. When upstream LLM providers suffer transient latency spikes, HTTP 429 rate limits, or produce hallucinated schema structures, your application backend must maintain strict availability SLAs.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Vulnerability of Unhedged LLM Integration
&lt;/h2&gt;

&lt;p&gt;Integrating generative AI features via synchronous inline API calls exposes SaaS platforms to three critical operational risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-Deterministic Latencies:&lt;/strong&gt; Inference responses can span anywhere from 800ms to 30 seconds, causing web server socket timeouts and user experience degradation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upstream Outages and Rate Limiting:&lt;/strong&gt; Single-vendor dependence means an upstream outage or sudden tenant spike instantly breaks all AI capabilities across your product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema Drift &amp;amp; Unstructured Outputs:&lt;/strong&gt; Prompt completion models occasionally produce invalid JSON formats, breaking downstream application parser logic and triggering unhandled runtime exceptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Architectural Blueprint: Resilience and Fallback Layer
&lt;/h2&gt;

&lt;p&gt;A production-ready LLM orchestration layer wraps model provider SDKs in an abstraction client featuring automated circuit breaking, schema validation retries, and dynamic model tier fallback routing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Model Fallback Execution Pattern
&lt;/h3&gt;

&lt;p&gt;If the primary model endpoint (e.g., Claude 3.5 Sonnet) exceeds a latency budget or throws a transient HTTP error, the pipeline immediately reroutes the execution to a secondary provider or smaller, faster model tier (e.g., GPT-4o-mini or Llama 3) without failing the client request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Production-Grade Resilient LLM Invocation Engine (TypeScript)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZodSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ModelRequestPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZodSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResilientLLMPipeline&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;primaryTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 6 second primary SLA limit&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;executeWithFallback&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelRequestPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="c1"&gt;// 1. Attempt Primary High-Capability Model (e.g., Primary Provider)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeModelWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary-tier-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primaryTimeout&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[LLM_FALLBACK] Primary model failed or timed out. Engaging secondary provider.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 2. Fallback to Secondary Cost-Efficient Model (e.g., Secondary Provider)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeModelWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondary-tier-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="c1"&gt;// Extended timeout for fallback&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;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;invokeModelWithTimeout&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelRequestPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;timeout&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callLLMProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 3. Structural Guardrail Validation&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validatedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validatedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`JSON_SCHEMA_VALIDATION_FAILED: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;validatedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;return&lt;/span&gt; &lt;span class="nx"&gt;validatedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutId&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Real-Time Streaming Resilience with Server-Sent Events (SSE)
&lt;/h2&gt;

&lt;p&gt;For interactive user-facing AI applications (such as workspace copilots or conversational interfaces), holding an HTTP connection open until the complete completion is generated creates severe perceived latency. Streaming response tokens via &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; reduces Time-To-First-Token (TTFT) from seconds to milliseconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decoupling Connection Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client Handshake:&lt;/strong&gt; Establish an SSE connection (&lt;code&gt;Content-Type: text/event-stream&lt;/code&gt;) from the client web application to the API gateway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Chunking:&lt;/strong&gt; Pipe upstream vendor streaming deltas directly to the response socket, minimizing internal buffer overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection Termination Safeguards:&lt;/strong&gt; If a client disconnects mid-stream, abort the upstream LLM API request immediately to prevent wasted token consumption on unread responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Enterprise Guardrails and Safety Controls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict Outbound Token Budget Caps:&lt;/strong&gt; Enforce tenant-level token bucket limits in Redis to prevent rogue API scripts or prompt injection loops from generating multi-thousand dollar bill shocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input Pre-Filtering &amp;amp; Sanitization:&lt;/strong&gt; Validate and sanitize user inputs prior to prompt construction to mitigate prompt injection attacks and eliminate unnecessary whitespace bloat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous Telemetry Logging:&lt;/strong&gt; Offload raw prompt payloads, completion metadata, latency metrics, and actual consumed token counts to cold storage (e.g., ClickHouse or S3) asynchronously via background queues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Key Engineering Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never Depend on a Single AI Provider:&lt;/strong&gt; Abstract LLM integrations behind a unified client layer capable of seamless multi-vendor fallbacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce Schema Validation at the Edge:&lt;/strong&gt; Use typed structural validation libraries (e.g., Zod) to validate model completion outputs before returning data to application logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize for TTFT via SSE:&lt;/strong&gt; Use Server-Sent Events to stream tokens in real-time, drastically lowering perceived latency for end-users.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Shipping AI features that need to stay up when your LLM provider doesn't?&lt;/strong&gt; I help SaaS teams design production LLM pipelines with model fallbacks, schema guardrails, and token budgets that keep costs and outages under control. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/production-ai-llm-pipelines-guardrails-fallbacks" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>node</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Advanced System Architecture: Designing Multi-Tenant Event-Driven Queues with Fair-Share Scheduling</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Wed, 07 Oct 2026 16:10:11 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/advanced-system-architecture-designing-multi-tenant-event-driven-queues-with-fair-share-scheduling-1hjn</link>
      <guid>https://dev.to/usman_khan_io/advanced-system-architecture-designing-multi-tenant-event-driven-queues-with-fair-share-scheduling-1hjn</guid>
      <description>&lt;p&gt;In multi-tenant SaaS systems, standard FIFO (First-In, First-Out) background job queues present a severe architectural risk: a single enterprise tenant queuing 100,000 asynchronous tasks can monopolize worker pools and starve interactive jobs for thousands of other tenants. Here is an engineering blueprint for implementing multi-tenant fair-share queue schedulers, dynamic concurrency isolation, and weighted round-robin dispatchers.&lt;/p&gt;

&lt;p&gt;In multi-tenant SaaS platforms, background processing engines (handling webhooks, PDF generation, bulk data syncs, or video processing) must balance throughput against tenant isolation. A naive global First-In, First-Out (FIFO) queue allows a single high-volume customer to overwhelm worker pools, resulting in multi-hour processing delays for neighboring tenants.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Noisy Neighbor Problem in Asynchronous Queues
&lt;/h2&gt;

&lt;p&gt;Standard distributed queue setups, such as a single Redis list, SQS queue, or RabbitMQ exchange, treat all incoming payloads equally. Under light traffic, this abstraction works seamlessly. However, as tenant usage diverges between self-serve accounts and enterprise clients, systemic vulnerabilities emerge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Worker Pool Starvation:&lt;/strong&gt; Tenant A triggers a batch import containing 50,000 tasks. Tenant B triggers a single, time-sensitive transactional invoice email. Under FIFO, Tenant B waits behind 50,000 tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Head-of-Line Blocking:&lt;/strong&gt; Unhandled retries or slow downstream execution for one tenant's tasks stall worker threads, degrading processing SLAs for all tenants sharing the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unpredictable Latency Variances:&lt;/strong&gt; P99 queue wait times balloon, violating enterprise customer SLAs and triggering cascading API timeouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Architectural Pattern: Per-Tenant Virtual Queues with Fair-Share Dispatching
&lt;/h2&gt;

&lt;p&gt;To guarantee tenant isolation without over-provisioning worker infrastructure, systems must decouple &lt;em&gt;job ingestion&lt;/em&gt; from &lt;em&gt;worker execution&lt;/em&gt;. Instead of pushing payloads to a single shared queue, jobs are routed into tenant-specific virtual queues managed by an intermediate &lt;strong&gt;Fair-Share Dispatcher&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Deficit Round-Robin (DRR) Scheduler Engine
&lt;/h3&gt;

&lt;p&gt;A proven strategy for fair-share queue orchestration is the &lt;strong&gt;Deficit Round-Robin (DRR)&lt;/strong&gt; algorithm. The scheduler cycles through active tenant queues, dispensing a fixed processing credit (quantum) per turn. If a tenant's queue is empty or exhausts its quantum, the dispatcher advances to the next tenant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Production Fair-Share Tenant Queue Dispatcher (TypeScript / Redis Pattern)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TenantJob&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;costWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g., estimated execution time or complexity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FairShareQueueDispatcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;quantumCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Max execution credits per round&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redisClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;redisClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Pushes a job to a tenant-isolated sub-queue
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TenantJob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenantQueueKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`queue:tenant:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeTenantsSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queue:active_tenants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantQueueKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeTenantsSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Dequeues the next fair-share job across active tenants
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetchNextJob&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TenantJob&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeTenants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;smembers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queue:active_tenants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeTenants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;activeTenants&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenantQueueKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`queue:tenant:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawJob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lpop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantQueueKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rawJob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Remove empty tenant queues from active rotation&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;srem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queue:active_tenants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TenantJob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawJob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Re-add tenant to active set if additional items remain&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remainingCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;llen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantQueueKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remainingCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queue:active_tenants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;srem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queue:active_tenants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Return job for worker execution frame&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Enforcing Tenant Concurrency Caps with Distributed Semaphores
&lt;/h2&gt;

&lt;p&gt;Fair-share round-robin scheduling guarantees equal queue entry opportunities, but long-running tasks can still saturate worker threads. To prevent slow jobs from occupying all execution slots, enforce &lt;strong&gt;Dynamic Max Concurrency Limits&lt;/strong&gt; per tenant tier using Redis semaphores.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Lua Script: Tenant Concurrency Semaphore Lock&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;tenantLockKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;maxAllowedConcurrency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lockTtlSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;currentConcurrency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SCARD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenantLockKey&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;currentConcurrency&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maxAllowedConcurrency&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;workerToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'INCR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'global:worker_nonce'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SADD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenantLockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workerToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'EXPIRE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenantLockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lockTtlSeconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tostring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workerToken&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="c1"&gt;-- Lock acquired&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"CONCURRENCY_LIMIT_EXCEEDED"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;-- Max concurrent slots active&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Tier-Based SLA Scheduling Strategies
&lt;/h2&gt;

&lt;p&gt;While strict equality works well for homogeneous environments, enterprise SaaS business models often demand priority weighted scheduling for higher-tier accounts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weighted Deficit Allocations:&lt;/strong&gt; Assign larger credit quanta to Enterprise tier tenants (e.g., Enterprise = 50 credits, Standard = 10 credits) so high-tier accounts drain faster while lower tiers remain protected from total starvation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated High-Priority Worker Pools:&lt;/strong&gt; Partition 20% of worker node infrastructure exclusively for low-latency interactive tasks (e.g., OAuth tokens, user-facing notifications) while routing background batch syncs to a flexible spot-instance cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive Backpressure Notifications:&lt;/strong&gt; Expose queue depth and estimated execution time metrics via tenant APIs so clients can adjust bulk upload speeds before hitting queue throttles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Key Engineering Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never Rely on a Single Global FIFO Queue:&lt;/strong&gt; Partition asynchronous workloads into tenant-scoped virtual queues to eliminate noisy neighbor risks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decouple Ingestion from Dispatching:&lt;/strong&gt; Use algorithms like Deficit Round-Robin to distribute worker processing capacity equitably across active tenants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine Fair Scheduling with Concurrency Controls:&lt;/strong&gt; Enforce strict per-tenant worker concurrency limits to prevent long-running tasks from occupying all compute resources.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Is one big customer slowing down background jobs for everyone else?&lt;/strong&gt; I help SaaS teams design tenant-isolated queue architectures, fair-share schedulers, and concurrency limits that keep every customer fast. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/advanced-system-architecture-multi-tenant-fair-share-queues" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>redis</category>
      <category>distributedsystems</category>
      <category>backend</category>
    </item>
    <item>
      <title>Zero-Downtime Database Migrations: Shadow Tables, Dual-Writing, and Schema Expansion</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Tue, 06 Oct 2026 16:23:49 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/zero-downtime-database-migrations-shadow-tables-dual-writing-and-schema-expansion-9jh</link>
      <guid>https://dev.to/usman_khan_io/zero-downtime-database-migrations-shadow-tables-dual-writing-and-schema-expansion-9jh</guid>
      <description>&lt;p&gt;Taking a mission-critical B2B SaaS database offline for a multi-hour &lt;code&gt;ALTER TABLE&lt;/code&gt; migration is no longer acceptable for high-growth engineering teams. Long-running DDL statements acquire exclusive table locks, holding incoming web transactions hostage and causing catastrophic application outages. Here is an actionable operational guide to executing zero-downtime schema migrations using shadow tables, dual-write abstraction layers, and zero-lock schema phase expansion.&lt;/p&gt;

&lt;p&gt;In high-throughput relational database architectures, running a blocking &lt;code&gt;ALTER TABLE ADD COLUMN&lt;/code&gt; or changing a column type on a table containing hundreds of millions of rows can trigger exclusive &lt;code&gt;AccessExclusiveLock&lt;/code&gt;s. This blocks read and write queries, causes connection pool backpressure, and brings down web application layers. Scaling B2B SaaS applications requires decoupling schema evolution from application code deployments entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Anatomy of Database Locking Vulnerabilities
&lt;/h2&gt;

&lt;p&gt;When an application attempts structural DDL modifications on relational databases like PostgreSQL or MySQL, the engine requests exclusive table locks. Under active write load, three major failure modes occur:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lock Queue Contention:&lt;/strong&gt; A heavy DDL statement waiting for existing read queries to finish will block all subsequent incoming &lt;code&gt;SELECT&lt;/code&gt; and &lt;code&gt;UPDATE&lt;/code&gt; queries behind it in the lock queue, exhausting connection pools in seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Table Rewrite Overhead:&lt;/strong&gt; Changing data types or setting default values without &lt;code&gt;DEFAULT NULL&lt;/code&gt; on older engine versions forces full physical table rewrites, locking I/O channels for hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application-Database Version Mismatch:&lt;/strong&gt; Deploying code that expects a new column before the database finished updating, or dropping a column while old application nodes are still serving traffic, causes unhandled HTTP 500 runtime exceptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. The Expand-Contract (Parallel Run) Migration Framework
&lt;/h2&gt;

&lt;p&gt;To safely migrate production tables containing tens of millions of rows, engineering teams employ the &lt;strong&gt;Expand-Contract Pattern&lt;/strong&gt;, breaking a single risky database alteration into discrete, backward-compatible phases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Expand Schema (Non-Blocking DDL)
&lt;/h3&gt;

&lt;p&gt;Add new columns, indices, or shadow tables without deleting or modifying existing columns. All new database structures must be optional or nullable to preserve backward compatibility with current application deployments.&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="c1"&gt;-- PostgreSQL Safe Column Creation with Low Lock Timeout&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;lock_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Add column as NULLABLE first to avoid table rewrite&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;organization_subscriptions&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;billing_tier_v2&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create indices CONCURRENTLY to prevent blocking concurrent write transactions&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt; &lt;span class="n"&gt;idx_org_sub_tier_v2&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;organization_subscriptions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;billing_tier_v2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;billing_tier_v2&lt;/span&gt; &lt;span class="k"&gt;IS&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Phase 2: Enable Dual-Write Application Abstraction
&lt;/h3&gt;

&lt;p&gt;Update backend application logic to write payload mutations to both the old schema location and the new schema location simultaneously. Primary reads continue pointing to the legacy column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TypeScript Dual-Write Abstraction Layer Example&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateSubscriptionTier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newTier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Write to Legacy Structure (Source of Truth for Reads)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`UPDATE organization_subscriptions SET tier = $1 WHERE org_id = $2`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;newTier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Dual-Write to Expanded Structure (Shadow Destination)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`UPDATE organization_subscriptions SET billing_tier_v2 = $1 WHERE org_id = $2`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;transformToNewFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTier&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;orgId&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Backfilling Historical Data and Parity Auditing
&lt;/h2&gt;

&lt;p&gt;With dual-writes active, newly modified records reflect in both schemas. However, historical records created prior to the expansion phase must be backfilled asynchronously without exceeding database CPU, memory, and IOPS budgets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch Backfilling Pattern
&lt;/h3&gt;

&lt;p&gt;Use chunked keyset pagination (never offset-based pagination) to iterate through historical records in background batch workers.&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="c1"&gt;-- Batch Backfill Query using Keyset Pagination (Chunk Size: 1000)&lt;/span&gt;
&lt;span class="c1"&gt;-- $1 = last processed id from the previous batch&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;organization_subscriptions&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;billing_tier_v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'BASIC'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'plan_tier_1'&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ENTERPRISE'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'plan_tier_3'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'plan_tier_2'&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;organization_subscriptions&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;billing_tier_v2&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shadow Read Parity Auditing
&lt;/h3&gt;

&lt;p&gt;Before switching read operations over to the new schema, run an asynchronous comparison worker that executes shadow reads, comparing legacy outputs against new schema outputs to verify 100% data parity.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Phase 3 &amp;amp; 4: Cutover Reads and Contract Legacy Schema
&lt;/h2&gt;

&lt;p&gt;Once parity auditing reaches 100% data consistency and background backfills finish, execute the final cutover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cutover Reads:&lt;/strong&gt; Deploy application code that reads primary queries from &lt;code&gt;billing_tier_v2&lt;/code&gt; while maintaining dual-writes to the old column for an observation period (e.g., 48 hours).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop Dual-Writes:&lt;/strong&gt; Deploy application code that removes legacy column write references completely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract Schema:&lt;/strong&gt; Drop the old column asynchronously using &lt;code&gt;ALTER TABLE ... DROP COLUMN ...&lt;/code&gt; with low lock timeouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Key Architectural Rules for Zero-Downtime Rollouts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set Explicit Lock Timeouts:&lt;/strong&gt; Never execute production DDL without setting &lt;code&gt;SET lock_timeout = '2s';&lt;/code&gt;. It is significantly better for a migration script to fail and retry than to hold up the database lock queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never Change Data Types In-Place:&lt;/strong&gt; Altering a column type (e.g., &lt;code&gt;INT&lt;/code&gt; to &lt;code&gt;BIGINT&lt;/code&gt; or &lt;code&gt;VARCHAR&lt;/code&gt; to &lt;code&gt;JSONB&lt;/code&gt;) forces a full table rewrite. Always build a shadow column or shadow table instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decouple Deployments:&lt;/strong&gt; Every phase of an Expand-Contract migration must be a separate code deployment. Never attempt schema expansion, backfilling, and legacy column dropping in a single deployment pipeline.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Planning a risky schema change on a production database?&lt;/strong&gt; I help SaaS teams design zero-downtime migration plans, from expand-contract rollouts to backfill pipelines that won't take your app offline. &lt;a href="https://ctousman.com/consult?utm_source=devto&amp;amp;utm_medium=article_cta&amp;amp;utm_campaign=devto_case_study" rel="noopener noreferrer"&gt;Book a consultation →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/zero-downtime-database-migrations-shadow-tables-dual-write" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>devops</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Advanced System Architecture: Designing Multi-Region Read Replicas and Global Data Consistency</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Mon, 05 Oct 2026 09:38:07 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/advanced-system-architecture-designing-multi-region-read-replicas-and-global-data-consistency-3a6j</link>
      <guid>https://dev.to/usman_khan_io/advanced-system-architecture-designing-multi-region-read-replicas-and-global-data-consistency-3a6j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; As enterprise SaaS platforms expand internationally, serving all database queries from a single primary region causes severe latency degradation for global users. Distributing read replicas across multiple cloud regions solves latency, but introduces complex consistency guarantees. Here is an architectural deep dive into designing multi-region database topologies, handling read-after-write consistency, and mitigating replication lag.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Serving global enterprise clients from a single database cluster region introduces unavoidable physical network latency. A user in Singapore querying a database in US-East incurs a baseline 200ms round-trip delay before business logic even executes. Deploying multi-region read replicas minimizes read latencies to single-digit milliseconds, but requires precise handling of replication lag and global consistency guarantees.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Multi-Region Topology Spectrum
&lt;/h2&gt;

&lt;p&gt;When expanding database infrastructure across geographical regions, engineering leadership must choose a replication strategy based on write volume and consistency requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-Leader Cross-Region Replication:&lt;/strong&gt; A primary database cluster accepts all write traffic in one central region (e.g., &lt;code&gt;us-east-1&lt;/code&gt;) and streams physical Write-Ahead Logs (WAL) or binary logs asynchronously to read-only replicas in target regions (e.g., &lt;code&gt;eu-central-1&lt;/code&gt;, &lt;code&gt;ap-southeast-1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Leader (Active-Active) Replication:&lt;/strong&gt; Primary databases in multiple regions accept local writes and synchronize changes asynchronously. Highly complex due to cross-region write conflict resolution policies (e.g., Last-Write-Wins vs. CRDTs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Globally Distributed Consensus Engines:&lt;/strong&gt; Engines like CockroachDB, Google Spanner, or YugabyteDB use Raft or Paxos consensus across regions to provide strict serializability at the cost of higher write latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For 95% of growth-stage B2B SaaS platforms, the &lt;strong&gt;Single-Leader Cross-Region Topology&lt;/strong&gt; offers the optimal balance between operational simplicity and sub-50ms read latencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Solving the Read-After-Write Consistency Hazard
&lt;/h2&gt;

&lt;p&gt;The primary architectural challenge with asynchronous cross-region replicas is &lt;strong&gt;replication lag&lt;/strong&gt;. If a user updates their profile in Singapore, the write routes to &lt;code&gt;us-east-1&lt;/code&gt;. If the user immediately refreshes their browser and the subsequent read hits the local Singapore replica before the WAL payload arrives, the user sees old data, causing support tickets and perceived system bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: Causal Consistency via Write LSN Tracking
&lt;/h3&gt;

&lt;p&gt;To prevent stale reads without routing all traffic back to the primary database, modern API gateways enforce &lt;em&gt;Read-After-Write Consistency&lt;/em&gt; by tracking Log Sequence Numbers (LSN) in user session state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Middleware Handler for Monotonic Read Consistency (Node.js &amp;amp; PostgreSQL)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;primaryDb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localReplicaDb&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./database-nodes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;consistencyAwareReadHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastWriteLsn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-user-last-lsn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// If user recently executed a write, verify local replica has caught up&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastWriteLsn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isReplicaUpToDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;checkReplicaLsn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localReplicaDb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastWriteLsn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isReplicaUpToDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Fallback to Primary DB if replication lag has not caught up&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dbNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;primaryDb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Route-Reason&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REPLICA_LAG_FALLBACK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&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="c1"&gt;// Default path: Route read to low-latency local region replica&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dbNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localReplicaDb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Route-Reason&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOCAL_REPLICA_HIT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkReplicaLsn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replicaClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetLsn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;replicaClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`SELECT pg_last_wal_replay_lsn() &amp;gt;= $1::pg_lsn AS is_caught_up;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;targetLsn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;is_caught_up&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&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;When a write transaction completes on the primary database, return the new commit LSN (&lt;code&gt;SELECT pg_current_wal_lsn()&lt;/code&gt;) to the client via HTTP response headers or auth tokens, updating their local session state.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Connection Pooling and Intelligent Query Routing
&lt;/h2&gt;

&lt;p&gt;Cross-region database architectures fail if application workers create direct un-pooled TCP connections to remote databases across WAN links. Network jitter and TLS handshake overhead will negate the benefits of regional proximity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architectural Rules for Global Routing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy Regional Connection Proxies:&lt;/strong&gt; Position PgBouncer or AWS RDS Proxy in every edge region alongside application compute nodes to maintain warm connection pools to local replicas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate Transactional Workloads:&lt;/strong&gt; Explicitly split read-heavy background analytics, search filters, and API GET queries away from state-modifying POST/PUT/DELETE handlers at the application ORM level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit Breaking for Replica Failures:&lt;/strong&gt; Implement automated health checks that automatically reroute regional reads back to the primary cluster if replication lag exceeds an operational SLA threshold (e.g., &amp;gt; 2.0 seconds).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Architectural Summary and Trade-Offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read Latency vs. Data Freshness:&lt;/strong&gt; Asynchronous read replicas deliver ultra-low read latency globally, but trade off instant consistency. Use LSN session cookies to bridge the gap cleanly for active writers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost vs. Availability:&lt;/strong&gt; Running cross-region replicas increases database infrastructure bills and egress costs (inter-region WAL streaming). Ensure regional replica instance sizes match write traffic to prevent replication worker starvation.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ctousman.com/blog/advanced-system-architecture-multi-region-read-replicas" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;. I write about SaaS architecture, databases, and scaling infrastructure. Follow for more deep dives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>postgres</category>
      <category>database</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Microservices Event Pipelines: Outbox Pattern, CDC, and Idempotent Event Sourcing</title>
      <dc:creator>Usman Khan</dc:creator>
      <pubDate>Sat, 03 Oct 2026 11:30:35 +0000</pubDate>
      <link>https://dev.to/usman_khan_io/microservices-event-pipelines-outbox-pattern-cdc-and-idempotent-event-sourcing-1iha</link>
      <guid>https://dev.to/usman_khan_io/microservices-event-pipelines-outbox-pattern-cdc-and-idempotent-event-sourcing-1iha</guid>
      <description>&lt;p&gt;In distributed microservice architectures, synchronizing database updates with downstream event streams is a notorious source of silent data drift. Naive dual-writing within application code fails when database commits succeed but message broker network writes time out. Enforcing transactional consistency requires decoupled, log-based event publishing.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Fallacy of Application-Level Dual Writing
&lt;/h2&gt;

&lt;p&gt;Consider a standard e-commerce or SaaS subscription workflow: an application endpoint updates the user record in PostgreSQL and immediately dispatches a &lt;code&gt;UserCreated&lt;/code&gt; event to Kafka or RabbitMQ.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Failure Scenario A:&lt;/strong&gt; The database transaction commits successfully, but the message broker goes offline or drops the connection. The event is lost forever, resulting in state divergence downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure Scenario B:&lt;/strong&gt; The message broker accepts the event payload, but the local database transaction rolls back due to a constraint violation. Downstream workers process an event for entity state that does not exist in the primary datastore.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Distributed two-phase commits (2PC) solve this in theory, but introduce severe availability bottlenecks, network latency, and operational coupling that degrade microservice performance at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Enforcing Atomicity: The Transactional Outbox Pattern
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Transactional Outbox Pattern&lt;/strong&gt; eliminates dual-write risk by persisting domain entity mutations and event notifications within the same local ACID transaction. Instead of sending messages over the network directly, the application writes events to a dedicated &lt;code&gt;outbox&lt;/code&gt; table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Schema for Outbox Records
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Outbox Table DDL in PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;transactional_outbox&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;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;aggregate_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&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;aggregate_id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&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;event_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&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;payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&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;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;CLOCK_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;processed_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Index for high-throughput polling or CDC sequence scan&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_outbox_unprocessed&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;transactional_outbox&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;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;processed_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Atomic Transaction Execution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example Node.js/TypeScript Transaction Handler&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTenantAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TenantPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Insert primary entity record into operational database&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tenantData&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Insert event payload into outbox table in the SAME transaction&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;aggregateType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TENANT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;aggregateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TENANT_PROVISIONED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;ownerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&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="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Guaranteed atomicity: both succeed or both roll back&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;h2&gt;
  
  
  3. Change Data Capture (CDC) with Debezium and Kafka
&lt;/h2&gt;

&lt;p&gt;Once events reside in the &lt;code&gt;outbox&lt;/code&gt; table, they must be streamed out to external message brokers without polling overhead. Polling (e.g. &lt;code&gt;SELECT * FROM outbox WHERE processed = false&lt;/code&gt;) causes lock contention and table bloat under high throughput.&lt;/p&gt;

&lt;h3&gt;
  
  
  Log-Based Change Data Capture
&lt;/h3&gt;

&lt;p&gt;Using &lt;strong&gt;Debezium&lt;/strong&gt; attached to the PostgreSQL Write-Ahead Log (WAL) or MySQL Binary Log (binlog), event records are read directly from the transaction log without issuing SQL queries against active tables.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No table locks:&lt;/strong&gt; Debezium tails the transaction log asynchronously and takes no read locks on production application tables. The one thing to monitor is replication slot lag: if the connector stalls, PostgreSQL retains WAL on disk until it catches up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At-least-once delivery:&lt;/strong&gt; Every write to the outbox table produces a change event streamed into a dedicated Kafka topic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated cleanup:&lt;/strong&gt; A background job or partition-maintenance routine drops historical outbox records once Kafka has acknowledged ingestion.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Idempotent Consumer Design in Event Processing
&lt;/h2&gt;

&lt;p&gt;Because log-based CDC and distributed brokers provide &lt;em&gt;at-least-once&lt;/em&gt; delivery, downstream consumers must assume duplicate events will arrive. Handlers must guarantee strict idempotency using a lock or deduplication store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Event Consumer Handler with Redis Lock &amp;amp; Deduplication&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleTenantProvisionedEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KafkaEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dedupKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`event:processed:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Claim the event with a short-lived lock (5 min),&lt;/span&gt;
  &lt;span class="c1"&gt;//    so a crashed worker can't block the event forever&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claimed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dedupKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PROCESSING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;claimed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dedupKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[DEDUPLICATION] Event &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; already processed. Skipping.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Still in-flight on another worker: fail so the broker redelivers later&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Event &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is in-flight on another worker`&lt;/span&gt;&lt;span class="p"&gt;);&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="c1"&gt;// 2. Execute idempotent downstream business logic&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provisioningService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setupIsolatedResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Mark completed and keep the dedup record for 7 days&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dedupKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;604800&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Release the lock so retries can execute&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dedupKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Trigger broker retry or Dead Letter Queue (DLQ) routing&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;h2&gt;
  
  
  5. Key Architectural Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never dual-write across network boundaries:&lt;/strong&gt; Commit domain state changes and event records inside a single local database transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage database log tailing:&lt;/strong&gt; Use log-based CDC (Debezium / Kafka Connect) instead of SQL table polling to stream events without locking overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce consumer-side idempotency:&lt;/strong&gt; Design consumers with atomic deduplication locks that survive crashes, so unavoidable redeliveries are handled safely.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ctousman.com/blog/microservices-event-pipelines-outbox-pattern-cdc?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=outbox_pattern_cdc" rel="noopener noreferrer"&gt;ctousman.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About the Author:&lt;/strong&gt;&lt;br&gt;
I'm &lt;strong&gt;Usman Khan&lt;/strong&gt;, Fractional CTO &amp;amp; Systems Architect. I advise high-growth SaaS and e-commerce platforms on event-driven architecture, distributed systems, and backend performance.&lt;/p&gt;

&lt;p&gt;Need an architectural review of your event pipelines? &lt;a href="https://ctousman.com/?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=outbox_pattern_cdc" rel="noopener noreferrer"&gt;Book a 30-min strategy call&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>kafka</category>
      <category>postgres</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
