<script data-pm-proxy="intercept"></script><?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Sev-1 Database]]></title><description><![CDATA[I manage PostgreSQL, MySQL and MariaDB at Microsoft. Each week, I share practical takeaways from real incidents no theory, just what actually breaks and how to fix it.]]></description><link>https://haiderzdbre.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!7Pgn!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30ab7b47-3853-4472-a161-41d35e82e7b6_608x608.png</url><title>The Sev-1 Database</title><link>https://haiderzdbre.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 01 Sep 2026 17:18:02 GMT</lastBuildDate><atom:link href="/__u/haiderzdbre.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Haider Z]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[haiderzdbre@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[haiderzdbre@substack.com]]></itunes:email><itunes:name><![CDATA[Haider Z @ Microsoft]]></itunes:name></itunes:owner><itunes:author><![CDATA[Haider Z @ Microsoft]]></itunes:author><googleplay:owner><![CDATA[haiderzdbre@substack.com]]></googleplay:owner><googleplay:email><![CDATA[haiderzdbre@substack.com]]></googleplay:email><googleplay:author><![CDATA[Haider Z @ Microsoft]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[[Free] Challenge #1: Distributed PostgreSQL with Citus]]></title><description><![CDATA[Adding a worker is not just adding compute. In a distributed PostgreSQL cluster, it requires replaying the database's entire logical identity.]]></description><link>https://haiderzdbre.substack.com/p/citus-distributed-postgres-metadata-sync-oom</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/citus-distributed-postgres-metadata-sync-oom</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Sun, 30 Aug 2026 03:01:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!FvdJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128075; <strong>Welcome to The Sev-1 Database.</strong></p><p>I break down real-world database outages, PostgreSQL C-engine internals, distributed databases and production triage runbooks.</p><ul><li><p><strong>Free subscribers</strong> get our architecture deep dives and database engineering challenges.</p></li><li><p><strong>Premium members 5$/month</strong> unlock full high-severity RCAs, deep DBA diagnostic toolkits, optimization cheatsheets etc.</p></li><li><p><strong><a href="http://thesev1database.com">New Platform Launched</a></strong></p></li></ul><p>If you find this breakdown valuable, consider subscribing or sharing it with your team:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><h3><strong>Table of Contents</strong></h3><ul><li><p><a href="#1-the-incident-scenario">1. Scenario</a></p></li><li><p><a href="#2-the-challenge-questions">2. Challenge</a></p></li><li><p><a href="#3-engine-mechanics-shell-tables-vs-physical-shards">3. Engine Mechanics: Shell Tables vs. Physical Shards</a></p></li><li><p><a href="#4-inside-the-c-engine-metadata_syncc--activation-flow">4. Inside the C Engine</a></p></li><li><p><a href="#5-why-work_mem-and-swap-fail-to-prevent-the-oom">5. Why Fail to Prevent the OOM</a></p></li><li><p><a href="#6-the-10-second-diagnostic-sql-checklist">6. 10-Second Diagnostic SQL Checklist</a></p></li><li><p><a href="#7-the-4-hidden-architectural-failures-of-schema-based-sharding">7. 4 Hidden Architectural Failures of Schema-Based Sharding</a></p></li><li><p><a href="#8-architectural-decision-framework-row-based-vs-schema-based">8. Architectural Decision Framework: Row-Based vs. Schema-Based</a></p></li><li><p><a href="#9-upstream-evolution--operational-guardrails">9. Upstream Evolution &amp; Operational Guardrails</a></p></li><li><p><a href="#10-level-up-on-the-sev-1-database">10. Level Up on The Sev-1 Database</a></p></li></ul><div><hr></div><h3><strong>1. Incident Scenario</strong></h3><p>You run a multi-tenant SaaS application on a distributed PostgreSQL cluster using <strong>Citus</strong> with <strong>schema-based sharding</strong> (each customer tenant has their own dedicated schema, holding ~20 tables, custom types, sequences, and distributed functions).</p><p>Your business is growing fast: you now have <strong>2,500 active tenants</strong>, resulting in over <strong>50,000 distributed relations and dependent objects</strong> across the coordinator.</p><p>To handle increased traffic, you provision a brand-new worker node (<code>worker_4</code>) and execute the standard activation command on the coordinator:</p><p>SQL</p><pre><code><code>SELECT citus_add_node('worker_4', 5432);
-- Or the metadata activation UDF:
-- SELECT start_metadata_sync_to_node('worker_4', 5432);

</code></code></pre><h4><strong>What happens next:</strong></h4><ol><li><p>Coordinator starts churning CPU for several minutes.</p></li><li><p>Suddenly, the command crashes with:</p></li></ol><pre><code><code>ERROR: out of memory
DETAIL: Failed on request of size 8192 in memory context "metadata_sync_context".</code></code></pre><ol start="3"><li><p>A few minutes later, distributed queries routed to <code>worker_4</code> start throwing:<br>Plain text</p></li></ol><pre><code><code>ERROR: relation "tenant_1402.orders" does not exist</code></code></pre><p>Your colleague suggests: <em>&#8220;Let&#8217;s increase </em><code>work_mem</code><em> to 1GB, bump </em><code>maintenance_work_mem</code><em>&#8221;</em></p><div><hr></div><h3><strong>2. Challenge</strong></h3><p>Before scrolling down to the breakdown, ask this to yourself:</p><ol><li><p><strong>Why did worker_4 throw <a href="https://thesev1database.com/errors/undefined-table#fix">relation does not exist</a> even though shard placements were registered in the coordinator catalog?</strong></p></li><li><p><strong>Why does increasing <a href="https://thesev1database.com/gucs/work-mem">work_mem </a>completely fail to prevent this OOM?</strong></p></li><li><p><strong>What is start_metadata_sync_to_node() actually doing under the hood in C?</strong></p></li><li><p><strong>What are the downstream architectural traps of schema-based sharding as your tenant count continues to scale?</strong></p></li></ol><div><hr></div><h3><strong>3. Engine Mechanics: Shell Tables vs. Physical Shards</strong></h3><p>If you dont know what is shell tables vs physical shards<br><br><strong>Shell Tables vs. Physical Shards</strong></p><ul><li><p><strong>Shell Table</strong>: A metadata-only representation of a distributed table on a worker node. It contains the table definition (schema, columns, indexes, etc.) but <strong>does not store actual data</strong>. Citus uses shell tables so DDL operations and metadata stay consistent across the cluster.</p></li><li><p><strong>Physical Shard</strong>: Actual data-containing partition of a distributed table stored on a worker node. Queries on distributed tables ultimately read from and write to these physical shards.<br><br><strong>In one line:</strong><br><strong>Shell Table = table structure and metadata.</strong><br><strong>Physical Shard = actual distributed data storage.</strong><br></p></li></ul><p><br>In schema-based sharding, a distributed table has two distinct identities:</p><pre><code><code>[ Coordinator Node ]
  &#9500;&#9472;&#9472; Logical Shell:  tenant_a.orders (in pg_class &amp; pg_dist_partition)
  &#9492;&#9472;&#9472; Routing Map:    Placement points to Worker 1

[ Worker 1 (Existing) ]
  &#9500;&#9472;&#9472; Logical Shell:  tenant_a.orders (Used for parsing, planning, locking)
  &#9492;&#9472;&#9472; Physical Shard: tenant_a.orders_102034 (Actual data on disk)

[ Worker 4 (New) ]
  &#9500;&#9472;&#9472; Logical Shell:  MISSING (Sync aborted mid-flight!)
  &#9492;&#9472;&#9472; Physical Shard: (Not yet placed)

</code></code></pre><ul><li><p><strong>Physical Shard Table (</strong><code>orders_102034</code><strong>):</strong> Stores the actual tuples for that tenant. It only exists on the specific worker hosting that shard placement.</p></li><li><p><strong>Logical Shell Table (</strong><code>tenant_a.orders</code><strong>):</strong> Contains column types, constraints, indexes, and schema definitions. It holds <strong>zero rows</strong> on workers that don&#8217;t own the shard, but it is <strong>mandatory</strong> for PostgreSQL to parse SQL, verify foreign keys, and execute distributed functions.</p></li></ul><p>If a worker is missing the logical shell table, running a query or distributed function against it fails immediately with <code>relation does not exist</code> even if placement rows exist in the coordinator catalog.</p><h3><strong>4. Inside the C Engine: </strong><code>metadata_sync.c</code></h3><p>When you add a metadata-capable worker, the coordinator executes <code>start_metadata_sync_to_node()</code> (<code>src/backend/distributed/metadata/metadata_sync.c</code>).</p><p>Here is the call graph:</p><pre><code><code>start_metadata_sync_to_node('worker_4', 5432)
  &#9474;
  &#9500;&#9472;&#9472; 1. Creates dedicated MetadataSyncContext
  &#9500;&#9472;&#9472; 2. Calls ActivateNodeList()
  &#9492;&#9472;&#9472; 3. Runs SyncNodeMetadataSnapshotToNode()
           &#9500;&#9472;&#9472; Drops old metadata snapshot on the worker
           &#9500;&#9472;&#9472; Re-creates distributed catalogs (pg_dist_*)
           &#9492;&#9472;&#9472; Calls SyncCitusTableMetadata()
                 &#9492;&#9472;&#9472; Dispatches thousands of CREATE SCHEMA, 
                     CREATE TABLE (shell), CREATE SEQUENCE, 
                     and CREATE FUNCTION statements over libpq

</code></code></pre><div><hr></div><h3><strong>5. Why Fail to Prevent the OOM</strong></h3><p>PostgreSQL memory is strictly compartmentalized:</p><ul><li><p><strong>Executor Memory (</strong><code>work_mem</code><strong>):</strong> Used for sorting, hash joins, and aggregates. When exceeded, the engine spills tuples to temporary files on disk.</p></li><li><p><strong>Internal Memory (</strong><code>MemoryContext</code><strong> / </strong><code>palloc</code><strong>):</strong> Used for query trees, cache-invalidation messages, and metadata DDL command buffers. <code>MemoryContext</code><strong> allocations live in backend heap RAM and can NEVER spill to disk.</strong></p></li></ul><p>When synchronizing 50,000 objects in a single long transaction, the coordinator builds a massive dependency graph in memory, accumulating DDL command strings and cache-invalidation queues inside <code>MetadataSyncContext</code>.</p><p>Eventually, a memory allocation exceeds the process ceiling, and PostgreSQL raises <a href="https://thesev1database.com/runbooks/stop-work-mem-from-oom-killing-the-server">out of memory</a>. The <code>size 8192</code> in the log detail is simply the size of the final allocation request that failed not the total memory consumed.</p><div><hr></div><p>&#128161; <em>Enjoying this breakdown? Share it with a teammate or database engineer who manages PostgreSQL at scale.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><h3><strong>6. 10-Second Diagnostic SQL Checklist</strong></h3><p>When managing or scaling a Citus cluster, always verify metadata synchronization before routing traffic:</p><pre><code><code>-- 1. Check metadata sync status across all workers
SELECT nodeid, nodename, nodeport, hasmetadata, metadatasynced, shouldhaveshards
FROM   pg_dist_node;

</code></code></pre><p><strong>What to look for:</strong></p><pre><code><code> nodename | hasmetadata | metadatasynced 
----------+-------------+----------------
 worker_1 | t           | t
 worker_2 | t           | t
 worker_3 | t           | t
 worker_4 | t           | f   &lt;--- &#9888;&#65039; SYNC INCOMPLETE OR FAILED

</code></code></pre><p>SQL</p><pre><code><code>-- 2. Verify worker-side catalog state
SELECT nodename, success, result
FROM   run_command_on_workers('SELECT count(*) FROM pg_dist_partition');

</code></code></pre><p><em>(If </em><code>result = 0</code><em> with </em><code>success = true</code><em>, the worker is reachable, but its distributed metadata is empty).</em></p><div><hr></div><h3><strong>7. 4 Hidden Architectural Failures of Schema-Based Sharding</strong></h3><p>Metadata synchronization OOM is only the <em>first</em> failure mode. When you scale PostgreSQL past thousands of schemas, four subtle architectural bottlenecks emerge:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!FvdJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!FvdJ!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png 424w, /__u/substackcdn.com/image/fetch/$s_!FvdJ!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png 848w, /__u/substackcdn.com/image/fetch/$s_!FvdJ!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png 1272w, /__u/substackcdn.com/image/fetch/$s_!FvdJ!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!FvdJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png" width="1018" height="367" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:367,&quot;width&quot;:1018,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:19949,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/213046019?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!FvdJ!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png 424w, /__u/substackcdn.com/image/fetch/$s_!FvdJ!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png 848w, /__u/substackcdn.com/image/fetch/$s_!FvdJ!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png 1272w, /__u/substackcdn.com/image/fetch/$s_!FvdJ!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F619991f9-55bd-4660-8231-cfe0015b070a_1018x367.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ol><li><p><strong>Per-Connection </strong><code>relcache</code><strong> Memory Bloat:</strong></p></li></ol><p>Each backend process caches metadata for touched tables in its private <code>relcache</code>. With 50,000 tables, if connections touch many schemas, backend memory balloons to <strong>200MB&#8211;500MB per connection</strong> just storing catalog cache. 200 connections can silently consume 60GB of RAM on catalog cache alone.</p><ol start="2"><li><p><strong>DDL Lock Storms:</strong></p></li></ol><p>Adding a single column in schema-based sharding requires executing <strong>2,500 separate </strong><code>ALTER TABLE</code><strong> statements</strong>, acquiring 2,500 <code>ACCESS EXCLUSIVE</code> locks. If one tenant has a running query, the entire migration blocks and stalls incoming application reads.</p><ol start="3"><li><p><strong>Autovacuum Scheduler Starvation:</strong></p></li></ol><p>Autovacuum workers spend immense time scanning 50,000 catalog entries, causing vacuum lag on the active tables that actually need cleanup.</p><ol start="4"><li><p><strong>Cross-Tenant Analytics Wall:</strong></p></li></ol><p>Cross-customer reporting requires dynamically unioning 2,500 distinct tables (<code>SELECT * FROM t1.orders UNION ALL SELECT * FROM t2.orders...</code>), blowing past query planning memory limits.</p><h3><strong>8. Architectural Decision Framework: Row-Based vs. Schema-Based</strong></h3><p>Before choosing schema-based sharding, use this decision framework:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!yb5f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!yb5f!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png 424w, /__u/substackcdn.com/image/fetch/$s_!yb5f!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png 848w, /__u/substackcdn.com/image/fetch/$s_!yb5f!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png 1272w, /__u/substackcdn.com/image/fetch/$s_!yb5f!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!yb5f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png" width="738" height="273" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:273,&quot;width&quot;:738,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:34685,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/213046019?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!yb5f!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png 424w, /__u/substackcdn.com/image/fetch/$s_!yb5f!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png 848w, /__u/substackcdn.com/image/fetch/$s_!yb5f!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png 1272w, /__u/substackcdn.com/image/fetch/$s_!yb5f!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed1104c-33a0-4320-a677-55fb2501e4e1_738x273.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Used AI to help create table format diagram.</figcaption></figure></div><h4><strong>Core Rule of Thumb:</strong></h4><ul><li><p>If tenants share the same schema structure and you plan to scale beyond 1,000 tenants: <strong>Use Row-Based Sharding (</strong><code>tenant_id</code><strong> distribution column).</strong></p></li><li><p>If you must use Schema-Based Sharding (e.g. strict regulatory schema isolation): <strong>Set a hard limit of ~1,000 tenants per cluster</strong>, then shard across <em>separate database clusters</em>.</p></li></ul><div><hr></div><h3><strong>9. Upstream Evolution &amp; Operational Guardrails</strong></h3><ol><li><p><strong>Upstream Stabilization (PR #6728 / <a href="https://www.citusdata.com/updates/v11-3/">Citus 11.3+</a>):</strong></p></li></ol><p>Citus team introduced major memory stabilization by splitting metadata synchronization into nontransactional phases and aggressively resetting child memory contexts during batch execution. Ensure your cluster runs on modern builds.</p><ol start="2"><li><p><strong>Schema &amp; Object Hygiene:</strong></p></li></ol><p>In multi-tenant architectures, obsolete tenant schemas, unused views, and abandoned index definitions aren&#8217;t just wasted disk they multiply the metadata synchronization dependency graph. Regularly drop decommissioned tenant objects.</p><ol start="3"><li><p><strong>Safe Worker Recovery:</strong></p></li></ol><p>If a worker&#8217;s metadata sync is corrupted or failed, stop propagation before retrying:<br>SQL</p><pre><code><code>SELECT stop_metadata_sync_to_node('worker_4', 5432, true);
</code></code></pre><h3><strong>10. Level Up on The Sev-1 Database</strong></h3><p>This challenge illustrates the core philosophy of <strong><a href="https://thesev1database.com">The Sev-1 Database</a></strong>: understanding the exact C-source call paths, memory structures, and catalog invariants so you can diagnose production outages with precision.</p><p>On <strong>thesev1database.com</strong>, we&#8217;ve built:</p><ul><li><p><strong>71 deep engine lessons</strong> with verified C source citations.</p></li><li><p><strong>105 production runbooks</strong> with literal psql outputs.</p></li><li><p><strong>102 lab-verified SQLSTATE error references</strong>.</p></li></ul><p>&#128073; <strong><a href="https://thesev1database.com/pricing">Start exploring with our 7-Day Free Pro Trial</a></strong></p><p>Did you get the answers right? How does your team manage schema scale across distributed clusters? Let me know in the comments below!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/citus-distributed-postgres-metadata-sync-oom?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/citus-distributed-postgres-metadata-sync-oom?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/citus-distributed-postgres-metadata-sync-oom/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/citus-distributed-postgres-metadata-sync-oom/comments"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[[Premium] Engine: How Replication Slots Guarantee WAL Retention]]></title><description><![CDATA[Why your PostgreSQL primary chose to crash with a full disk rather than break a promise to a dead replica.]]></description><link>https://haiderzdbre.substack.com/p/postgres-replication-slot-wal-bloat-disk-full</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgres-replication-slot-wal-bloat-disk-full</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Thu, 27 Aug 2026 03:01:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7Pgn!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30ab7b47-3853-4472-a161-41d35e82e7b6_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey everyone,</p><p>When I talk to database engineers about why high-scale PostgreSQL clusters fail, 80% of the time it comes down to a misunderstanding of how the engine handles <strong>retention</strong>.</p><p>Today, I&#8217;m sharing a complete preview of one of our core Pro engine lessons from <strong><a href="https://thesev1database.com/lessons/replication-slots">The Sev-1 Database</a></strong>: <strong>Replication Slots</strong>.</p><p>If you&#8217;ve ever had a primary run out of disk space with no huge queries running, this is what was happening under the hood.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><h3><strong>Problem Slots Were Invented to Solve</strong></h3><p>Before replication slots existed (introduced in PostgreSQL 9.4), standbys relied on <code>wal_keep_size</code> (or the older <code>wal_keep_segments</code>).</p><p>Logic was simple: <em>&#8220;Keep the last 64 GB of WAL on the primary and pray the replica doesn&#8217;t lag further than that.&#8221;</em></p><p>If the replica went down for maintenance or network latency spiked and the primary generated 65 GB of WAL, the primary recycled the older files. When the replica woke up, the WAL it needed was gone. Standby was hopelessly stranded (<code>requested WAL segment has already been removed</code>) and had to be rebuilt from scratch using <code>pg_basebackup</code>.</p><p>Replication slots solved this by turning <em>&#8220;hope we keep enough&#8221;</em> into a <strong>guarantee</strong>.</p><div><hr></div><h3><strong>How a Slot Works in the C Engine</strong></h3><p>A replication slot (<code>src/backend/replication/slot.c</code>) is a named, durable data structure written to <code>$PGDATA/pg_replslot/&lt;slot_name&gt;/state</code>.</p><p>Instead of guessing a fixed buffer size, a slot records the exact Log Sequence Number (<code>restart_lsn</code>) that a specific consumer still needs.</p><p>When your primary runs a checkpoint and looks to recycle old 16 MB WAL segments, it calls <code>ReplicationSlotReserveWal()</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;7ba69141-f493-4725-bfaf-3aa5d50ec63e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">[ Primary writes WAL ] &#9472;&#9472;&#9472;&gt; Current LSN: 0/2A000000
                                  &#9474;
Live Standby Slot &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&gt; restart_lsn: 0/29F80000 (Lag: ~500 KB) &#9472;&#9472;&gt; OK to recycle before this!
                                  &#9474;
Stalled CDC Slot  &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&gt; restart_lsn: 0/14000060 (3 days old)   &#9472;&#9472;&gt; LOCKED. No recycling!</code></pre></div><p>Primary finds the <strong>minimum </strong><code>restart_lsn</code><strong> across all slots</strong>. It is physically forbidden from deleting any WAL segment beyond that point.</p><p>Slots survive server restarts. If a standby drops offline for 6 hours and comes back, it picks up right where it stopped. Zero manual intervention.</p><div><hr></div><h3><strong>Trap: Logical Slots and Catalog Bloat</strong></h3>
      <p>
          <a href="/__u/haiderzdbre.substack.com/p/postgres-replication-slot-wal-bloat-disk-full">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Postgres Is Down. Now What?]]></title><description><![CDATA[SQL, lessons, runbooks now with a 7-day Pro trial]]></description><link>https://haiderzdbre.substack.com/p/postgres-is-down-now-what</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgres-is-down-now-what</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Wed, 19 Aug 2026 04:30:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7Pgn!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30ab7b47-3853-4472-a161-41d35e82e7b6_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Postgres is down.</strong></p><p>You don&#8217;t want another blog post.</p><p>You want to know:</p><p><strong>What broke?<br>What should I run?<br>What did the output actually look like?<br>And how do I know I&#8217;ve fixed it?</strong></p><p>That&#8217;s what I&#8217;m building with <strong>The Sev-1 Database</strong>.</p><p>Every PostgreSQL error explanation is free.</p><p>And now, you can try <strong>Pro for 7 days without paying</strong>.</p><p>No watered-down demo. No sample chapter.</p><p>For 7 days, you get the same Pro content engineers use:</p><ul><li><p><strong>100+ production runbooks</strong> diagnosis &#8594; exact SQL &#8594; pitfall &#8594; verification</p></li><li><p><strong>Deep PostgreSQL lessons</strong> including the actual <code>psql</code> commands and output</p></li><li><p><strong>Interview concepts + model answers</strong> for engineers preparing for senior/staff roles</p></li><li><p><strong>Lab-backed troubleshooting</strong> lock graphs, blocking PIDs, audit output, and other evidence from real scenarios</p></li></ul><p>Idea is simple:</p><p><strong>Don&#8217;t read about what you could do.<br>Run it. See the evidence. Understand why it worked.</strong></p><p>If it becomes useful enough to keep in your incident toolkit, Pro is <strong>$24.99/month</strong>.</p><p>If it doesn&#8217;t, cancel before day 7 and <strong>you pay nothing</strong>.</p><p>Free content isn&#8217;t going away.</p><p>But if you&#8217;re the person people call when PostgreSQL is on fire, <strong>I&#8217;d try Pro for a week.</strong></p><p>&#8594; <strong>Start the 7-day Pro trial:</strong> <a href="https://thesev1database.com/pricing">https://thesev1database.com/pricing</a></p><p>Haider</p>]]></content:encoded></item><item><title><![CDATA[New Platform 𝗘𝗮𝗿𝗹𝘆 𝗔𝗰𝗰𝗲𝘀𝘀 𝗶𝘀 𝗻𝗼𝘄 𝗼𝗽𝗲𝗻]]></title><description><![CDATA[The Sev-1 Database Platform shows you what to run when production is on fire.]]></description><link>https://haiderzdbre.substack.com/p/new-platform</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/new-platform</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Fri, 07 Aug 2026 15:52:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7Pgn!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30ab7b47-3853-4472-a161-41d35e82e7b6_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>&#128640; &#120280;&#120302;&#120319;&#120313;&#120326; &#120276;&#120304;&#120304;&#120306;&#120320;&#120320; &#120310;&#120320; &#120315;&#120316;&#120324; &#120316;&#120317;&#120306;&#120315;.<br><br>Free and Premium access are live. Explore it, break it, and tell me what's missing.<br><br>Every guide has been execution-verified against real PostgreSQL containers because theory doesn't fix incidents.<br><br>&#128073; </span><a href="https://thesev1database.com/">Access Now</a></p><p>Link: https://thesev1database.com</p><p></p>]]></content:encoded></item><item><title><![CDATA[[DBEng] Azure HorizonDB: Microsoft’s Cloud-Native Distributed PostgreSQL]]></title><description><![CDATA[Compute ships the log; storage turns it into the database and how that sets Microsoft's new Postgres apart from Aurora, AlloyDB, and Neon.]]></description><link>https://haiderzdbre.substack.com/p/azure-horizondb-architecture</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/azure-horizondb-architecture</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Fri, 24 Jul 2026 18:31:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!WPbX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><h2><strong>Table of contents (What&#8217;s inside)</strong></h2><ul><li><p>One thing to understand first</p></li><li><p>Two purpose&#8209;built storage services</p></li><li><p>How a commit actually happens</p></li><li><p>Why &#8220;stateless compute&#8221; is the real win</p></li><li><p>Where it sits among the cloud-native family (Aurora &#183; AlloyDB &#183; Neon)</p></li><li><p>Other bet: AI built into the engine (filtered DiskANN &#183; Foundry models)</p></li><li><p>What people miss</p></li><li><p>Key takeaway</p></li></ul><div><hr></div><p>&#128236; I break down one Postgres / cloud&#8209;database architecture like this.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><h2>One thing to understand first</h2><p>Azure HorizonDB is Microsoft&#8217;s cloud-native, PostgreSQL-compatible database. It went to preview in the 2025&#8211;2026 wave as the tier that sits above Flexible Server, and Microsoft built it out of what it learned running some of the largest PostgreSQL fleets in Azure, including the databases behind ChatGPT.</p><p>Two ideas hold the whole thing up, and Microsoft states both of them plainly. <strong>Compute and storage are pulled apart.</strong> And <strong>the write-ahead log </strong><em><strong>is</strong></em><strong> the database.</strong> Get those two, and the rest of HorizonDB falls out of them. <em>One caveat before we start: this is new, and it is in preview. Treat the specifics as a moving target, and check them against Microsoft&#8217;s current docs.</em></p><p>A picture helps. Every database does two jobs: <strong>cooking</strong>, which is running your queries, and <strong>keeping the food</strong>, which is holding your data. Where those two jobs live is the whole story. <strong>Ordinary PostgreSQL is a food truck.</strong> Both jobs ride in one vehicle, so the cook (the query engine) works right next to the pantry (your data files on local disk). That is simple, and it works. But the two can never scale apart. Want a second server to take more readers? You build a whole second truck and copy the entire pantry into it.</p><p><strong>HorizonDB puts those jobs in two separate buildings:</strong> a <strong>kitchen</strong> (compute) and a <strong>storehouse</strong> (storage). Then it does something odd with the storehouse. It never shelves a single finished dish. </p><p>Your data pages are not kept there at all. What it keeps instead is one ever-growing <strong>order book</strong>: an exact, append-only list of every change ever made. <em>Add this row. Change that value.</em></p><p>That order book <em>is</em> the WAL, PostgreSQL&#8217;s write-ahead log. Kitchen&#8217;s only job toward storage is to <strong>write the next line in it</strong>. It never ships finished plates back. And when a dish is finally needed, the storehouse <strong>replays the order book and rebuilds that exact dish on the spot</strong>.</p><p>That is what the log <em>is</em> the database really means. The true copy of your data is the running list of changes, not a stack of data files, and the pages get reconstructed from that list on demand. It is the family trait HorizonDB shares with Aurora, AlloyDB and Neon.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!WPbX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!WPbX!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!WPbX!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!WPbX!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!WPbX!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!WPbX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1929447,&quot;alt&quot;:&quot;azure horizondb architecture&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/208254841?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="azure horizondb architecture" title="azure horizondb architecture" srcset="/__u/substackcdn.com/image/fetch/$s_!WPbX!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!WPbX!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!WPbX!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!WPbX!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd447a4a9-042e-4628-9d36-b51e1d63bff2_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Two purpose-built storage services</h2><p>Microsoft splits the storehouse into two fleets. Each one is tuned for a single job.</p><ul><li><p><strong>WAL service</strong> takes the log stream from the primary. It is built for exactly one thing: appending WAL to the tail of a sequential stream, fast. And it is durable and zone-resilient by default.</p></li><li><p><strong>Data (page) storage fleet</strong> holds the actual pages. They are <strong>sharded across many storage nodes</strong>, and every shard is copied into more than one availability zone. </p><p><br>As WAL lands, these nodes <strong>replay it to rebuild the 8 KB data pages</strong> and hand them back to compute. Spread a database&#8217;s pages across the whole fleet, with each node reading from a bank of high-performance NVMe disks, and Microsoft says the page tier can serve <strong>hundreds of thousands of IOPS per instance</strong>. Both fleets lean on Azure Blob storage underneath for durability.</p></li></ul><p>Here is the consequence, and it is the whole point. <strong>Compute never writes data pages to storage. It writes only the log.</strong> Pages are just <em>derived state</em> that storage rebuilds from WAL. That is exactly why checkpoints and full-page writes, the biggest sources of write amplification in classic PostgreSQL, stop being the compute node&#8217;s job at all.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a paid subscriber 5$/month.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>How a commit actually happens</h2><p>Follow a single <code>UPDATE</code> on the primary.</p><ul><li><p>The change is written as WAL and <strong>appended to the durable WAL service</strong>, which acknowledges it back to the client. <br><br>Microsoft&#8217;s framing here is unusually precise: the WAL service <strong>durably commits a transaction across three availability zones in a single network hop</strong>. A classic PostgreSQL primary plus hot standby needs <strong>four hops</strong> to make that same commit durable, and every extra hop is one more thing that can fail or add latency. On this path Microsoft reports <strong>sub-millisecond multi-zone commit latencies</strong>.</p></li><li><p>After that, <strong>filtered WAL</strong> is applied <em>asynchronously</em> to the data shards that own those pages, and streamed out to the read replicas so they can refresh whatever pages they already hold in memory.</p></li></ul><p>Compare that to community PostgreSQL. There, the same commit writes WAL to the server&#8217;s own disk, and sooner or later the server has to <strong>checkpoint</strong> its dirty pages down to its own data files. <br><br>A hot standby has to receive that WAL and <strong>replay it onto its own separate copy of the data</strong>. HorizonDB acknowledges the moment the shared, zone-resilient log service has the record. And there is only ever <strong>one copy of the data</strong>, shared by every replica.</p><div><hr></div><h2>Why stateless compute is the real win</h2><p>Durability, checkpointing, full-page writes, WAL archiving, backups: all of it moves <em>into</em> the storage layer. So the compute node is left doing almost nothing except running your queries. <br><br>Microsoft publishes a whole table of traditional PostgreSQL background jobs it hands off from compute to storage. The takeaway for a practitioner is simple. The CPU, disk and network that used to go on keeping the database durable now go on your workload instead. <br><br>Each compute replica gets 8 GB of memory per core, and a local NVMe cache for hot pages on top of that.</p><p>Two operational superpowers fall out of a compute tier that owns no data.</p><ul><li><p><strong>Read replicas come up almost instantly.</strong> A new replica just attaches to the <em>same</em> shared storage. There is no volume to copy and replay first. <br><br>A read-only endpoint then load-balances across them. And since the storage layer feeds them WAL, not the primary, <strong>adding replicas puts basically no extra load on the primary</strong>. Each one scales on its own, so you get real workload isolation.</p></li><li><p><strong>Failover is fast and clean.</strong> A standby already shares the same durable WAL and storage as the primary, so it only has to replay a little WAL to take over.<br><br>Because storage is the single source of truth, the old primary <strong>cannot diverge</strong> after a failover. That kills the <code>pg_rewind</code>-style &#8220;which timeline won?&#8221; reconciliation that self-managed failovers always have to wrestle with.</p></li></ul><div><hr></div><h2>Where it sits among the cloud-native family</h2><p>Every product in this pathway makes the same core move. Ship the log, and let a distributed storage service turn it into pages. <br><br>What differs is where each one spends that architecture. Lining HorizonDB up against its peers is the fastest way to see what it actually is.</p><ul><li><p><strong>vs AWS Aurora.</strong> Aurora slices data into 10 GB segments, replicates them <em>six ways across three AZs</em>, and commits on a <em>4-of-6 quorum</em>, with up to 15 shared-storage replicas. HorizonDB gets to the same &#8220;log is the database&#8221; goal by another route: <strong>two separate services</strong> (a WAL service and a page-server fleet) instead of one unified redo fleet. And it frames durability as a <strong>three-AZ commit in a single hop</strong> rather than a published quorum count. Same family, different plumbing.</p></li><li><p><strong>vs Google AlloyDB.</strong> AlloyDB is disaggregated Postgres too, but its headline act is a built-in <strong>columnar engine</strong> that speeds up analytical (HTAP) queries on the same data. HorizonDB&#8217;s headline is a different one: <strong>AI in the engine</strong> plus raw OLTP scale, not a columnar analytics accelerator.</p></li><li><p><strong>vs Neon.</strong> Neon takes disaggregation to a <strong>serverless</strong> extreme, with scale-to-zero and instant copy-on-write branching. HorizonDB is not serverless, it is provisioned. It is tuned for large, always-on, mission-critical OLTP, not ephemeral branch-per-PR development.</p></li><li><p><strong>vs Azure Flexible Server (the managed tier right below it in Azure).</strong> Flexible Server is community PostgreSQL on a managed disk: a single primary that owns its data files. HorizonDB is the disaggregated tier above it. Microsoft cites up to <strong>3,072 vCores</strong> across primary and replicas, storage that auto-grows to <strong>128 TB</strong>, and up to <strong>3x the throughput</strong> of open-source Postgres on transactional workloads.</p></li></ul><p>The one-line mental model: <strong>Aurora buys you AZ-resilient durability, Neon buys elasticity and branching, AlloyDB buys analytics. HorizonDB buys OLTP scale plus an AI stack, on Azure.</strong></p><div><hr></div><h2>Other bet: AI built into the engine</h2><p>HorizonDB&#8217;s second design bet is as big as the storage one. Instead of bolting a separate vector database and a layer of app-side glue onto Postgres, Microsoft pulls the AI work <em>inside</em> the engine, right next to your operational rows. <br><br>The announcement leads with <strong>two concrete engine features</strong>, and one launch customer frames the service as being for &#8220;Vector DB, RAG, and Agentic AI.&#8221; Read each of the two as <em>feature, then value, then impact</em>, rather than as a name to memorise.</p><p><strong>1. A filtered DiskANN vector index.</strong></p><ul><li><p><strong>Feature.</strong> HorizonDB adds advanced filtering to the DiskANN vector index, so an ordinary SQL predicate, your <code>WHERE</code> clause, gets <strong>pushed down into the vector similarity search itself</strong>. Microsoft says this gives significant performance and scalability gains over pgvector&#8217;s HNSW index <em>while holding accuracy</em>, and that it is built for similarity search over transactional data already living in Postgres.</p></li><li><p><strong>Value.</strong> Filtered vector search is the classic trap. <em>Pre-</em>filter, and you fight the index. <em>Post-</em>filter, and you wreck recall. Ask for the 10 nearest rows &#8220;for tenant 42&#8221; and a naive index hands you the 10 globally-nearest, most of which you then throw away, sometimes leaving you two real matches instead of ten. Push the predicate <em>into</em> the graph traversal, and the search only ever walks candidates that already satisfy the filter.</p></li><li><p><strong>Impact.</strong> &#8220;Find similar rows <em>for this customer, in this category, since this date</em>&#8220; stays fast and accurate as the table grows. And because the vectors sit right beside the rows they describe, you are not running a separate vector store or keeping two copies of the truth in sync. One database, one backup, one security boundary.</p></li></ul><p><strong>2. Built-in AI model management.</strong></p><ul><li><p><strong>Feature.</strong> Generative, embedding and <strong>reranking</strong> models from Microsoft Foundry are wired into the database with <strong>zero configuration</strong>, callable from inside the engine.</p></li><li><p><strong>Value.</strong> Normally, turning a row into an embedding, or calling an LLM, means standing up app-tier code: an API client, secret management, retries, a round-trip out of the data tier. Make the model a first-class thing the database itself can call, and all of that plumbing collapses.</p></li><li><p><strong>Impact.</strong> You can embed a row <em>as you write it</em>, or rerank a result set, without the data ever leaving the database&#8217;s trust boundary, and without a separate service to run. Reranking earns its spot here. It is the step that turns a raw nearest-neighbour list into genuinely relevant RAG context, and now it is a built-in model call instead of one more dependency to operate.</p></li></ul><p><strong>Why this is even possible, and where to go deeper.</strong> Both features lean straight on the architecture from the first half of this post. A <em>stateless compute tier with spare CPU</em>, plus a <em>shared page store</em>, is exactly the headroom that in-engine model calls and big vector scans need to run without starving your transactions. <br><br>That is the through-line to hold: <strong>the storage design is what pays for the AI design.</strong> The broader stack, surface by surface, is a deep topic of its own: durable embedding pipelines, BM25 keyword ranking, hybrid search, graph queries, agent protocols, all with runnable SQL.</p><div><hr></div><p><em><span>Reproduced on PostgreSQL 17.10. Failure-mode signal: </span><a href="https://pgmoodring.com/">pgmoodring.com </a><span>&#8212;105 reports till 24 July, deadlocks #1. No numbers in this post are fabricated.</span></em></p><div><hr></div><h2>What people miss</h2><ul><li><p><strong>PostgreSQL-compatible still means proprietary storage.</strong> Same as Aurora and AlloyDB: your SQL ports over, but the storage internals and some operational behaviours are Microsoft-specific. Factor in the lock-in.</p></li><li><p><strong>It is single-writer.</strong> HorizonDB scales <em>reads</em> by adding replicas onto shared storage, but writes still funnel through one primary. It is not multi-master.</p></li><li><p><strong>Preview means real gaps today.</strong> At launch, Microsoft documents fixed seven-day backup retention, no cross-region read replicas, service-managed keys only (no customer-managed keys), and no built-in connection pooling yet. If your workload needs any one of those, it is a blocker. The <a href="https://thesev1database.com/2026/06/08/azure-horizondb-architecture-ai-functions-cost-2026/#limits">full preview reality check</a> lists them with their impact.</p></li><li><p><strong>Match the tool to the need.</strong> If a single Flexible Server primary carries your load comfortably, the disaggregated tier may be more capability, and more cost, than you actually need.</p></li></ul><div><hr></div><p>&#128640; The <strong>PostgreSQL Health Report</strong> bundles the lock-chain and contention checks I run during a Sev-1 into one SQL file &#8212; <strong><a href="https://haiderdba.gumroad.com/l/ccget">$29</a></strong>.</p><div><hr></div><h2>Key takeaway</h2><p><span>Do not memorise HorizonDB as a feature list. Hold the mental model. </span><strong>Compute ships the log; a two-fleet storage service turns that log into the database.</strong><span> <br><br>From that one idea you can derive the rest, and place it next to its neighbours: instant shared-storage replicas and clean, no-rewind failover (the family trait), a three-AZ single-hop commit (its own twist), and the headroom for an in-engine AI stack (its bet). <br><br>Aurora, AlloyDB and Neon start from the same idea and spend it differently. Once the model clicks, you can reason about any of them, which is exactly the muscle a real design discussion, or an interview, asks for.</span></p><div><hr></div><p><em>References</em> <a href="https://learn.microsoft.com/en-us/azure/horizondb/ai/ai-overview">Azure HorizonDB: Architecture, AI Functions</a>.  <a href="https://learn.microsoft.com/en-us/azure/horizondb/overview">Azure HorizonDB Architecture Details, Revealed from Microsoft</a>.</p>]]></content:encoded></item><item><title><![CDATA[[PREMIUM] PostgreSQL Deadlock RCA: reproducing and killing 40P01 for good]]></title><description><![CDATA[A full PostgreSQL deadlock RCA from a real reproduction]]></description><link>https://haiderzdbre.substack.com/p/postgresql-deadlock-detected-40p01-reproduce-and-fix-rca</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgresql-deadlock-detected-40p01-reproduce-and-fix-rca</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Mon, 20 Jul 2026 12:56:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Uhrl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On <strong>19 July</strong>, the live community feed on <a href="https://pgmoodring.com">pgmoodring.com</a> logged <strong>42 PostgreSQL incident reports</strong>, and <strong>deadlocks</strong> were the single most-reported failure mode of the day. So I did what I always do when the fleet lights up on one failure mode: I reproduced it on a local instance, captured everything PostgreSQL actually said, and worked backwards to the fix.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Uhrl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Uhrl!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png 424w, /__u/substackcdn.com/image/fetch/$s_!Uhrl!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png 848w, /__u/substackcdn.com/image/fetch/$s_!Uhrl!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Uhrl!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Uhrl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png" width="1273" height="660" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:660,&quot;width&quot;:1273,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120768,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/207768339?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Uhrl!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png 424w, /__u/substackcdn.com/image/fetch/$s_!Uhrl!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png 848w, /__u/substackcdn.com/image/fetch/$s_!Uhrl!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Uhrl!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e8581d2-bc5c-408a-966a-eeda5f5e3400_1273x660.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><em>Premium edition. This is a <strong>reproduction RCA</strong> not a customer incident. Every log line, lock row, error and timing below is <strong>real captured output</strong> from a local <strong>PostgreSQL 17.10</strong> run. Nothing is hand-written for the article; it is generated from the artifacts my reproduction lab captured.</em></p></blockquote><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a paid subscriber 5$/month.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Contents</h2><ol><li><p>Setup</p></li><li><p>What actually happened</p></li><li><p>Real deadlock report</p></li><li><p>Victim&#8217;s client error</p></li><li><p>Root cause: <code>ShareLock on transaction</code></p></li><li><p>The lock graph (<code>pg_locks</code>)</p></li><li><p>RCA timeline</p></li><li><p>Right way to fix it</p></li><li><p>Reproduce it yourself</p></li></ol><div><hr></div><h2>1. Setup</h2><p>A single local <strong>PostgreSQL 17.10</strong> instance. One table, seeded with <strong>50,000 rows</strong> an ordinary <code>accounts</code> table, the kind every payments, ledger or inventory system has:</p><pre><code><code>CREATE TABLE wallet.accounts (
    id      bigint PRIMARY KEY,
    owner   text    NOT NULL,
    balance numeric NOT NULL DEFAULT 0
);
-- seeded with 50,000 rows
</code></code></pre><p>One setting matters for this write-up: I set <strong>deadlock_timeout = '2s'</strong> in the lab (production default is <code>1s</code>) so I had a clean window to snapshot the blocked state <em>before</em> the detector fired. That timeout is exactly how long a session waits on a lock before <strong>PostgreSQL bothers to check for a cycle it is not a performance knob, </strong>it is the deadlock detector&#8217;s patience.</p><h2>2. What actually happened</h2>
      <p>
          <a href="/__u/haiderzdbre.substack.com/p/postgresql-deadlock-detected-40p01-reproduce-and-fix-rca">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[PG Mood Ring: What's This?]]></title><description><![CDATA[Lately, I&#8217;ve started asking myself one question after every PostgreSQL production incident:]]></description><link>https://haiderzdbre.substack.com/p/pg-mood-ring-whats-this</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/pg-mood-ring-whats-this</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Fri, 17 Jul 2026 11:31:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!H7Oh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lately, I&#8217;ve started asking myself one question after every PostgreSQL production incident:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!H7Oh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!H7Oh!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png 424w, /__u/substackcdn.com/image/fetch/$s_!H7Oh!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png 848w, /__u/substackcdn.com/image/fetch/$s_!H7Oh!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png 1272w, /__u/substackcdn.com/image/fetch/$s_!H7Oh!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!H7Oh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:302837,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/207394362?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!H7Oh!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png 424w, /__u/substackcdn.com/image/fetch/$s_!H7Oh!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png 848w, /__u/substackcdn.com/image/fetch/$s_!H7Oh!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png 1272w, /__u/substackcdn.com/image/fetch/$s_!H7Oh!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F883be65a-84f2-4561-90e6-e7b5bac412b8_1200x630.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><p>&#120298;&#120309;&#120302;&#120321; &#120305;&#120310;&#120305; &#120284; &#120302;&#120304;&#120321;&#120322;&#120302;&#120313;&#120313;&#120326; &#120324;&#120316;&#120319;&#120312; &#120316;&#120315; &#120321;&#120316;&#120305;&#120302;&#120326;?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Not for timesheets.</p><p>Not for tickets.</p><p>Just to understand what production engineers are really spending their time on.</p><p>Then I had another thought...</p><p>&#120298;&#120309;&#120302;&#120321; &#120310;&#120307; &#120324;&#120306; &#120302;&#120313;&#120313; &#120313;&#120316;&#120308;&#120308;&#120306;&#120305; &#120311;&#120322;&#120320;&#120321; &#120316;&#120315;&#120306; &#120291;&#120316;&#120320;&#120321;&#120308;&#120319;&#120306;&#120294;&#120292;&#120287; &#120310;&#120315;&#120304;&#120310;&#120305;&#120306;&#120315;&#120321; &#120302; &#120305;&#120302;&#120326;?</p><p>Imagine opening one page and seeing what engineers around the world are reporting today.</p><p>Deadlocks.</p><p>Replication lag.</p><p>Connection storms.</p><p>Checkpoint issues.</p><p>Not opinions.</p><p>Just real production problems engineers are choosing to share.</p><p>So I vibe-coded a small experiment called <a href="https://pgmoodring.com/">&#120291;&#120282; &#120288;&#120316;&#120316;&#120305; &#120293;&#120310;&#120315;&#120308;</a>.</p><p>The idea is simple:</p><p>&#8226; Log the PostgreSQL incident you worked on today.</p><p>&#8226; See what other PostgreSQL engineers are reporting.</p><p>&#8226; Build your own month-by-month production history.</p><p>For me, it also becomes a signal for &#120295;&#120309;&#120306; &#120294;&#120306;&#120323;-&#120813; &#120279;&#120302;&#120321;&#120302;&#120303;&#120302;&#120320;&#120306;.</p><p>If I consistently see the community struggling with deadlocks or replication, that&#8217;s where I know I should invest more time creating guides, labs, and troubleshooting content.</p><p>It&#8217;s an early experiment, so I&#8217;d genuinely love feedback from people running PostgreSQL in production.</p><p>&#127760; <a href="https://pgmoodring.com/">&#120317;&#120308;&#120314;&#120316;&#120316;&#120305;&#120319;&#120310;&#120315;&#120308;.&#120304;&#120316;&#120314;</a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[SQL Server → Postgres Migration Nobody Talks About]]></title><description><![CDATA[Fragmentation Bug That Can Cost You Weeks]]></description><link>https://haiderzdbre.substack.com/p/sql-server-postgres-migration-nobody</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/sql-server-postgres-migration-nobody</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Tue, 14 Jul 2026 04:00:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7Pgn!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30ab7b47-3853-4472-a161-41d35e82e7b6_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every migration conference talk, every vendor whitepaper, every <strong>how to move off proprietary databases</strong> thread on Reddit it&#8217;s all Oracle &#8594; Postgres. And fair enough, that&#8217;s the bigger, louder migration. Oracle licensing costs alone have pushed thousands of companies toward Postgres.</p><p>SQL Server &#8594; Postgres gets almost no airtime. Which is strange, because it&#8217;s not a small migration path plenty of shops are quietly doing it. They just aren&#8217;t writing about it.</p><p>I have done one. 9TB, production, ORM-heavy application, real users, real SLAs. And the thing that nearly broke us wasn&#8217;t on anyone&#8217;s migration checklist.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><h3><strong>Setup</strong></h3><p>Schema conversion: done. Datatype mapping: done. T-SQL stored procedures rewritten into PL/pgSQL: done, painfully, but done. Indexes recreated with equivalent definitions on the Postgres side: done.</p><p>By every standard pre-cutover checklist, we were green across the board.</p><p>Then the application went live on Postgres for stress testing and the ORM-generated queries exact same queries, same predicates, same expected index usage were slow. Not <strong>slightly slower, needs tuning</strong> slow. Materially, user-facing-latency slow.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>Part that wasted our time</h3><p>First instinct: missing indexes. Checked the indexes existed. Matched the predicates. The planner was even picking them. <strong>EXPLAIN ANALYZE</strong> showed index scans, not sequential scans. On paper, everything looked correct.</p><p>So if the right index is being used, why is it this slow?</p><p>We burned real hours chasing planner statistics, work_mem, connection pool settings the usual suspects when Postgres should be fast and isn&#8217;t. None of it moved the needle meaningfully.</p><p>Actual answer: the indexes were fragmented. Badly.</p><h3>Why this happens on this specific migration path, and not others</h3><p>If you&#8217;ve spent your career on SQL Server, fragmentation is a background concern. You&#8217;ve got scheduled maintenance plans doing index rebuilds/reorganizes, fill factor tuned per workload, and the app almost never feels it directly because the DBA tooling around SQL Server has been dealing with this problem for decades in a very visible, very manageable way.</p><p>Postgres doesn&#8217;t fragment in the same way, and it doesn&#8217;t get <strong>maintained</strong> the same way by default either.</p><p>Postgres uses MVCC. Every <strong>UPDATE</strong> doesn&#8217;t modify a row in place it writes a new row version and marks the old one dead. Those dead tuples sit there until VACUUM reclaims them. Indexes point at physical locations (tuple pointers), so when rows move around under update-heavy workloads, indexes accumulate dead entries and lose their tight, sequential layout. </p><p>That&#8217;s bloat, and bloat in an index behaves exactly like fragmentation the index is technically valid and technically used, but it&#8217;s now doing far more I/O per lookup than it should.</p><p>Now add the ORM into the picture. ORMs are notorious for generating update patterns that are far from bloat-friendly: wide UPDATE statements that touch indexed columns unnecessarily, no batching, no awareness of Postgres&#8217;s HOT (Heap-Only Tuple) update optimization, which only kicks in when the update doesn&#8217;t touch any indexed column. </p><p>Touch an indexed column on every update which most ORM-generated save() calls do without you asking them to and you lose HOT updates entirely. Every single update now forces an index update too, and bloat accumulates far faster than a lean, hand-written update path would produce.</p><p>So: correct schema, correct indexes, correct query plans on paper and still 5x the I/O it should be doing, because nobody told Postgres <strong>this index needs attention</strong> the way SQL Server&#8217;s maintenance plans quietly had been doing for years on the old system.</p><h3>What actually fixed it</h3><p>- Autovacuum tuning per table, not just database-wide defaults high-churn tables needed far more aggressive settings than Postgres ships with out of the box</p><p>- Bloat monitoring added to standard health checks, so this gets caught in staging next time, not three months post-cutover</p><p>- A longer-term conversation with the app team about batching updates and avoiding unnecessary writes to indexed columns</p><p>- Clean up index bloat where heap size was around 100GB but index size of table was 1TB+.</p><p>Currently we are reviewing each index def, validating if it requires it took us hours to reach this point.</p><p>Once Postgres migration is completed with expected results, will share complete series of challenges we went through.</p><p>None of this is exotic. It&#8217;s all standard Postgres operational knowledge. </p><p>The problem was never technical difficulty it was that this wasn&#8217;t on the migration checklist, because migration checklists are almost all written from an Oracle &#8594; Postgres lens, where this specific failure mode doesn&#8217;t show up the same way.</p><h3>Actual lesson</h3><p>If you&#8217;re migrating from SQL Server to Postgres, don&#8217;t just migrate the schema and the indexes. Migrate the maintenance mindset.</p><p>SQL Server has spent decades normalizing scheduled index maintenance as an invisible background job. Postgres expects you to understand MVCC, vacuum, and bloat as first-class operational concerns from day one not something you reach for after users start complaining.</p><p>The query plan being correct tells you the optimizer picked the right index. It tells you nothing about whether that index is healthy.</p>]]></content:encoded></item><item><title><![CDATA[[DBEng] How Instagram Sharded PostgreSQL With 2.5 Engineers]]></title><description><![CDATA[In September 2011, Instagram was serving 10 million+ users on EC2 with two-and-a-half engineers and no ops team.]]></description><link>https://haiderzdbre.substack.com/p/how-instagram-sharded-postgresql-with-2-engineers</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/how-instagram-sharded-postgresql-with-2-engineers</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Tue, 12 May 2026 04:50:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!78B7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Table of Contents</h2><ol><li><p>Constraint That Shaped Everything</p></li><li><p>64-Bit ID: Time + Shard + Sequence</p></li><li><p>Routing: Two Lookups, One Python Dict</p></li><li><p>Logical Shards via PostgreSQL Schemas</p></li><li><p>Moving Shards With Streaming Replication</p></li><li><p>Why Not Redis, HBase, or Composite Keys?</p></li><li><p>What Any PostgreSQL Team Can Apply Today</p></li></ol><div><hr></div><p>&#128232; If this was useful, share it with one engineer on your team who manages production databases.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. Each week I share practical takeaways from real incidents no theory, just what actually breaks and how to fix it. Upgrade to Premium 8$/month.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!78B7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!78B7!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!78B7!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!78B7!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!78B7!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!78B7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1830036,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/196516436?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!78B7!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!78B7!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!78B7!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!78B7!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61fc7f66-df35-49b7-85e9-aa91603eae57_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>1. Constraint That Shaped Everything</h2><p>Instagram launched in October 2010 on a single PostgreSQL server on EC2. By September 2011, they had over 10 million users. Team? Two-and-a-half engineers. No ops team. No DBA.</p><p>From co-founder Mike Krieger on Hacker News:</p><blockquote><p><em>We&#8217;re on EC2, which has its set of limitations but means we can run a 10 million+ user system with two-and-a-half engineers (and no ops team / overhead).</em></p></blockquote><p>Photos go to S3. PostgreSQL stores metadata, social graph, likes, comments, feed relationships. Almost every query is keyed by user. If you can guarantee a query touches exactly one user, you can guarantee it touches exactly one shard.</p><p>That&#8217;s the insight they exploited.</p><h2>2. 64-Bit ID: Time + Shard + Sequence</h2><p>Instagram designed a custom 64-bit ID generated entirely inside PostgreSQL via a PL/pgSQL function:</p><ul><li><p><strong>41 bits</strong> &#8212; milliseconds since a custom epoch (~69 years of runway)</p></li><li><p><strong>13 bits</strong> &#8212; logical shard ID (0&#8211;8191)</p></li><li><p><strong>10 bits</strong> &#8212; auto-increment sequence mod 1024</p></li></ul><p>Function fires on <code>DEFAULT nextval()</code>. No application code generates IDs. No external service. Database itself produces globally-unique, time-sortable identifiers.</p><p>Why time-sortable? &#8220;Newest first&#8221; feeds become a simple index scan no secondary sort on a timestamp column.</p><p>Why 64-bit single integer instead of a composite key? Krieger&#8217;s answer:</p><blockquote><p><em>&#8220;We often have to store keys in other systems like Redis, where having a single 64-bit integer makes it more portable and stored compactly (Redis, for example, has an optimization when storing integer values vs string values in its lists).&#8221;</em></p></blockquote><p>One integer. Portable across every cache layer. No serialization overhead.</p><h2>3. Routing: Two Lookups, One Python Dict</h2><p>Routing is surprisingly simple. From Krieger&#8217;s HN response:</p><blockquote><p><em>&#8220;Each lookup is: user_id % 1000 &#8594; schema ID, schema ID &#8594; database ID, then SELECT FROM schemaID.tablename on that particular database.&#8221;</em></p></blockquote><p>That mapping lives as a Python dictionary inside their Django application. Not a service. Not a database call. A dict.</p><pre><code><code># Conceptual representation of their routing
SHARD_MAP = {
    0: 'db-01',
    1: 'db-01',
    2: 'db-03',
    # ... through 999
}

def get_db(user_id):
    schema_id = user_id % NUM_SHARDS
    return SHARD_MAP[schema_id]</code></code></pre><p>When asked about routing tables vs algorithmic hashing, Krieger said:</p><blockquote><p><em>&#8220;Right now, it&#8217;s a lookup dict in our Django app which involves brief downtime just to update the shard map when moving the data.&#8221;</em></p></blockquote><p>O(1). Zero network cost. Tradeoff: updating the dict requires a brief deploy.</p><h2>4. Logical Shards via PostgreSQL Schemas</h2><p>Instagram pre-creates all logical shards as PostgreSQL schemas. Each physical server hosts many schemas:</p><pre><code><code>-- Each schema is a complete logical shard
CREATE SCHEMA insta5;
CREATE TABLE insta5.photos (
  id bigint PRIMARY KEY DEFAULT insta5.next_id(),
  user_id bigint NOT NULL,
  ...
);

CREATE SCHEMA insta6;
CREATE TABLE insta6.photos (
  id bigint PRIMARY KEY DEFAULT insta6.next_id(),
  user_id bigint NOT NULL,
  ...
);</code></code></pre><p>Why schemas instead of separate databases or table-name prefixes? Krieger explained the PostgreSQL advantage:</p><blockquote><p><em>&#8220;One huge benefit to PostgreSQL is the schema/tablespace feature, since it means all our logical shards all live inside one database.&#8221;</em></p></blockquote><p>Each schema has its own sequence. Database&#8217;s own <code>nextval()</code> guarantees no duplicate IDs within a shard. 13-bit shard ID in the key guarantees no duplicates across shards.</p><p>When a physical server gets hot, you move entire schemas to a new machine. The application dict update is the only code change. No data re-keying. No ID rewrites.</p><p>This is the detail most sharding projects get wrong. They shard physically first and cannot rebalance without rewriting keys.</p><div><hr></div><p>&#128640; <strong>PostgreSQL Health Report</strong> &#8212; One SQL file. 60+ diagnostic checks. Detects vacuum lag, bloat, lock chains, replication slot danger, unindexed FK columns, and more.</p><p><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></p><div><hr></div><h2>5. Moving Shards With Streaming Replication</h2><p>How do you move a logical shard from one physical server to another without extended downtime?</p><p>Krieger described their process:</p><blockquote><p><em>&#8220;The way we move shards is to use PostgreSQL&#8217;s built-in streaming replication to create an exact, in-sync copy of a set of tablespaces, then &#8216;fail over&#8217; to a new machine and start reading/writing to a subset of those tablespaces.&#8221;</em></p></blockquote><p>The process:</p><ol><li><p>Set up streaming replication from source to destination server</p></li><li><p>Wait for the replica to be fully in sync</p></li><li><p>Briefly stop writes (update the Django dict to point to the new server)</p></li><li><p>Start reading/writing to the new machine</p></li></ol><p>Downtime window is the dict update seconds, not minutes. Data copy happens in the background via PostgreSQL&#8217;s native replication. No custom migration tooling.</p><h3>Clock Drift and Duplicate Keys</h3><p>41-bit timestamp relies on clocks being roughly correct. Instagram uses <code>ntpd</code> in gradual-adjustment mode (no sudden jumps). But what if a clock is slightly behind after a shard move?</p><p>Krieger&#8217;s response:</p><blockquote><p><em>&#8220;At worst, you&#8217;d have a duplicate key when trying to insert, and can re-try without the risk of having a duplicate ID floating around your system.&#8221;</em></p></blockquote><p>Failure mode is a caught exception on insert, not silent data corruption. 10 bits of sequence give 1,024 IDs per millisecond per shard a shard would need to be creating more than 128 entries per second with clocks 8+ seconds off to trigger this.</p><h2>6. Why Not Redis, HBase, or Composite Keys?</h2><p>Instagram uses Redis heavily for other systems. They explicitly considered it for ID generation and rejected it:</p><blockquote><p><em>&#8220;[Redis] would have introduced a single point of failure, unless we split the load between several Redis instances, at which point it would be hard to make the IDs time-sortable. Also, most of our Redis systems are durable within a minute (we write to disk on a slave every minute), but if we were to lose the master and slave simultaneously (imagine an EC2 network issue), then it would be hard to know what the last known &#8216;good&#8217; ID was.&#8221;</em></p></blockquote><p>On HBase and NoSQL solutions:</p><blockquote><p><em>&#8220;The data we shard out is more suited to an RDBMS, and since we&#8217;re way more familiar with that world than with HBase and similar, it was the choice that let us make the most progress in a short time with a small team.&#8221;</em></p></blockquote><p>This is the underrated lesson. They didn&#8217;t pick PostgreSQL because it was technically optimal. <strong>They picked it because 2.5 engineers can&#8217;t afford the learning curve of a system they don&#8217;t deeply understand.</strong> Familiarity is a scaling strategy when your team is small.</p><h2>7. What Any PostgreSQL Team Can Apply Today</h2><p><strong>Encode shard identity into your primary keys.</strong> Single highest-leverage decision in a sharded system. It eliminates routing services, lookup tables, and entire categories of cross-shard bugs.</p><p><strong>Shard logically, not physically.</strong> Pre-create thousands of schemas on a handful of machines. When you need to rebalance, move schemas don&#8217;t re-key data.</p><p><strong>Use stock database features.</strong> PL/pgSQL functions, schemas, sequences, streaming replication Instagram&#8217;s entire sharding infrastructure used features that ship with PostgreSQL. No custom extensions. No external ID services.</p><p><strong>Choose what your team already knows deeply.</strong> Instagram explicitly rejected technologies they were less familiar with. At 2.5 engineers, the cost of learning a new system is measured in months of shipping velocity.</p><p><strong>Keep the routing layer dumb.</strong> A Python dict that maps shard&#8594;server is less elegant than consistent hashing. It&#8217;s also something any engineer can debug in 30 seconds at 3am during an incident.</p><div><hr></div><p>&#128231; <strong>Want the complete PostgreSQL Health Report?</strong> 60+ diagnostic queries &#8212; statistics freshness, extended stats coverage, bloat, vacuum health. Ready-to-run fixes.</p><p><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></p><p>&#128204; Upgrade to Premium ($8/month) &#8212; full SQL diagnostic packs, incident playbooks, and the queries I actually run during a Sev-1.</p><p><a href="/__u/haiderzdbre.substack.com/subscribe">Upgrade to Paid</a></p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/how-instagram-sharded-postgresql-with-2-engineers?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/how-instagram-sharded-postgresql-with-2-engineers?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/how-instagram-sharded-postgresql-with-2-engineers/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/how-instagram-sharded-postgresql-with-2-engineers/comments"><span>Leave a comment</span></a></p><p></p><h4>Sources</h4><ul><li><p>Instagram Engineering &#8212; <em>Sharding &amp; IDs at Instagram</em> (September 2011) &#8212; <a href="https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c">Original post (now dead)</a></p></li><li><p>Hacker News discussion with author responses from mikeyk (Mike Krieger) &#8212; <a href="https://news.ycombinator.com/item?id=3058327">news.ycombinator.com/item?id=3058327</a></p></li><li><p>Instagram Engineering &#8212; <em>Storing hundreds of millions of simple key-value pairs in Redis</em> &#8212; <a href="https://instagram-engineering.com/storing-hundreds-of-millions-of-simple-key-value-pairs-in-redis-1091ae80f74c">instagram-engineering.com</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[[PREMIUM] PostgreSQL Autovacuum Diagnostic: 10 Queries to Know It's Running or Winning]]></title><description><![CDATA[Autovacuum running and autovacuum winning are different things.]]></description><link>https://haiderzdbre.substack.com/p/postgresql-autovacuum-diagnostic-10-queries</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgresql-autovacuum-diagnostic-10-queries</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Fri, 08 May 2026 04:50:38 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!dKO4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Process can be active in pg_stat_activity while dead tuples climb toward 30% on your highest-write tables. Here&#8217;s the exact sequence I run to catch it.</em></p><h2>Table of Contents</h2><ol><li><p>Running vs Winning Distinction</p></li><li><p>First Wrong Diagnosis: Bump autovacuum_max_workers</p></li><li><p>Second Wrong Diagnosis: Run VACUUM FULL</p></li><li><p>What Autovacuum Actually Does (PG 18)</p></li><li><p>Query 1 &#8212; Dead Tuple Accumulation Rate</p></li><li><p>Query 2 &#8212; Current Autovacuum Activity</p></li><li><p>Query 3 &#8212; Worker Saturation</p></li><li><p>Query 4 &#8212; Tables That Never Autovacuum</p></li><li><p>Query 5 &#8212; Long-Running Transactions Blocking Cleanup</p></li><li><p>Query 6 &#8212; Scale Factor Audit on Large Tables</p></li><li><p>Query 7 &#8212; Cost-Limit Throttling</p></li><li><p>Query 8 &#8212; Aggressive vs Normal Autovacuum</p></li><li><p>Query 9 &#8212; Wraparound Worker Status</p></li><li><p>Query 10 &#8212; Per-Table Autovacuum Settings Override</p></li><li><p>Emergency Decision: When to Run Manual VACUUM</p></li><li><p>Prevention Checklist</p></li><li><p>Key Takeaway</p></li></ol><div><hr></div><p>&#128232; If this saved you time, share it with one engineer who manages production databases.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber 8$/month.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!dKO4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!dKO4!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!dKO4!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!dKO4!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!dKO4!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!dKO4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2016097,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/196513512?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!dKO4!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!dKO4!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!dKO4!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!dKO4!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2c2f0de-e079-4b16-a1dc-3dee60ab8530_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>1. Running vs Winning Distinction</h2><p>I&#8217;ve seen instances where autovacuum was technically active in <code>pg_stat_activity</code> while dead tuples climbed past 30% on the highest-write tables.</p><p>Running means process exists. Winning means it&#8217;s keeping pace with your write rate.</p><p>Confusing two is the most expensive autovacuum mistake. Queries below distinguish them.</p><h2>2. First Wrong Diagnosis: Bump autovacuum_max_workers</h2><p>Instinct is: autovacuum isn&#8217;t keeping up, give it more workers.</p><p>Problem: autovacuum throughput is gated by <code>autovacuum_vacuum_cost_limit</code> (shared across all workers by default). Adding workers without raising the cost limit just splits the same I/O budget more ways. Each worker goes slower. Net progress identical.</p><p><strong>Raise cost limit first. </strong>Then add workers if the limit isn&#8217;t the bottleneck.</p><h2>3. Second Wrong Diagnosis: Run VACUUM FULL</h2><p>VACUUM FULL takes an ACCESS EXCLUSIVE lock and rewrites the entire table. On a 500GB table in production, this means hours of downtime.</p><p>99% of bloat problems can be fixed with plain VACUUM, pg_repack (online), or tuning autovacuum to actually win. VACUUM FULL is almost always the wrong answer.</p><h2>4. What Autovacuum Actually Does (PG 18)</h2><p>From the PostgreSQL 18 manual, Chapter 25.1.6:</p><blockquote><p><em>The autovacuum daemon consists of multiple processes. There is a persistent daemon process, called the autovacuum launcher, which is in charge of starting autovacuum worker processes for all databases. The launcher will distribute the work across time.</em></p></blockquote><p>Two triggers fire autovacuum on a table:</p><ul><li><p><code>autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor &#215; n_live_tup</code> dead tuples normal vacuum</p></li><li><p><code>autovacuum_freeze_max_age</code> reached aggressive vacuum for wraparound prevention</p></li></ul><p>Default scale factor is 0.2 meaning <strong>autovacuum triggers at 20% dead tuples.</strong> On a 100M row table, that&#8217;s 20M dead tuples before autovacuum even starts. That&#8217;s not a setting designed for large tables.</p><h2>5. Query 1 Dead Tuple Accumulation Rate</h2><pre><code><code>SELECT
  schemaname,
  relname AS table_name,
  n_live_tup,
  n_dead_tup,
  round(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 2) AS dead_pct,
  pg_size_pretty(pg_total_relation_size(schemaname||'.'||relname)) AS total_size,
  last_autovacuum,
  last_autoanalyze,
  CASE
    WHEN n_dead_tup::float / NULLIF(n_live_tup, 0) &gt; 0.2
    THEN 'Autovacuum losing &#8212; manual VACUUM needed'
    WHEN n_dead_tup::float / NULLIF(n_live_tup, 0) &gt; 0.1
    THEN 'Bloat elevated &#8212; watch closely'
    WHEN last_autovacuum &lt; now() - interval '24 hours'
      AND n_live_tup &gt; 1000000
    THEN 'Large table not vacuumed in 24h'
    ELSE 'OK'
  END AS status
FROM pg_stat_user_tables
WHERE n_live_tup + n_dead_tup &gt; 10000
ORDER BY n_dead_tup DESC
LIMIT 20;</code></code></pre><p>Any row with <code>Autovacuum losing</code> is a table where autovacuum is running but not winning.</p>
      <p>
          <a href="/__u/haiderzdbre.substack.com/p/postgresql-autovacuum-diagnostic-10-queries">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[A Small Support From Me to You]]></title><description><![CDATA[Affected by layoffs? or in search of Job?]]></description><link>https://haiderzdbre.substack.com/p/a-small-support-from-me-to-you</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/a-small-support-from-me-to-you</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Thu, 07 May 2026 07:56:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7Pgn!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30ab7b47-3853-4472-a161-41d35e82e7b6_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the last few weeks, I&#8217;ve spoken with multiple PostgreSQL engineers and DBAs who were recently affected by layoffs or are going through a difficult financial phase while preparing for interviews.</p><p>I wanted to do something practical to help.</p><p>So I decided to reduce <strong>the premium membership price for The Sev-1 Database from $8/month to $5/month.</strong></p><p><strong><a href="/__u/haiderzdbre.substack.com/subscribe">Subscribe to The Sev-1 Database</a></strong></p><p>For existing premium members, the discounted price has already been applied automatically.</p><p>Premium content currently includes:</p><ul><li><p>20+ deep PostgreSQL troubleshooting guides</p></li><li><p>120+ diagnostic and debugging queries</p></li><li><p>Production incident workflows</p></li><li><p>Query tuning and planner analysis</p></li><li><p>Locking, WAL, replication, vacuum, and performance troubleshooting</p></li></ul><p><a href="/__u/haiderzdbre.substack.com/t/paid">Sev 1 Premium Content</a></p><p>I also recently launched:<br>PostgreSQL DBA Interview Help Kit &#8211; Part 1</p><p>Originally valued at $12.99, <strong>it&#8217;s now available from starting price $1</strong> for those preparing for interviews.</p><p>If you are already a premium member, drop me an email and I&#8217;ll send you a free copy.</p><p>Get it here:<br><a href="https://haiderdba.gumroad.com/l/postgresql-dba-interview?utm">PostgreSQL DBA Interview Help Kit</a></p><p>My goal is simple:<br>Help more engineers prepare better and get back into strong roles faster.</p><p>Newsletter:<br><a href="/__u/haiderzdbre.substack.com/?utm_source=chatgpt.com">The Sev-1 Database</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p>]]></content:encoded></item><item><title><![CDATA[[FREE] 1979 Paper Still Running Your PostgreSQL Optimizer ]]></title><description><![CDATA[Here's what Patricia Selinger designed, what survived 46 years, and what you need for PostgreSQL.]]></description><link>https://haiderzdbre.substack.com/p/postgresql-optimizer-selinger-1979-system</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgresql-optimizer-selinger-1979-system</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Tue, 05 May 2026 04:50:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!y2i6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Every PostgreSQL query plan you&#8217;ve ever read is built on a paper Patricia Selinger wrote at IBM in 1979. It&#8217;s 46 years old. It still runs your database. Here&#8217;s what she designed, what survived, and what PostgreSQL had to fix.</em></p><h2>Table of Contents</h2><ol><li><p>What We&#8217;re Working With</p></li><li><p>Paper</p></li><li><p>Four Ideas That Became Every SQL Optimizer</p></li><li><p>What PostgreSQL Kept From 1979</p></li><li><p>What PostgreSQL Had to Change</p></li><li><p>Where Selinger&#8217;s Assumptions Still Bite You in Production</p></li><li><p>5 Things You Can Do About It</p></li><li><p>Key Takeaway</p></li></ol><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication FREE and Premium 8$/month. Each week I share practical takeaways from real incidents no theory just what actually breaks and how to fix it.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>1. What We&#8217;re Working With</h2><p>Every time you run <code>EXPLAIN ANALYZE</code> and see a join order, a hash join chosen over a nested loop, or a cost estimate in arbitrary units you are looking at output from an algorithm designed by Patricia Selinger at IBM Research in 1979.</p><p>Not a distant ancestor of the algorithm. <em>Algorithm.</em> Paper&#8217;s pseudocode is close enough to PostgreSQL&#8217;s planner source code that reading one helps you read the other.</p><p>46 years later, SQL Server, Oracle, DB2, MySQL, and PostgreSQL all run variations of  same design. Understanding what Selinger got right and what she didn&#8217;t tells you why your database behaves the way it does.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!y2i6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!y2i6!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!y2i6!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!y2i6!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!y2i6!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!y2i6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2220256,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/195920733?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!y2i6!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!y2i6!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!y2i6!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!y2i6!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b32e19e-5d77-444d-b583-2b38de66803c_1024x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>2. Paper</h2><p><strong>&#8220;Access Path Selection in a Relational Database Management System&#8221;</strong><br>Patricia G. Selinger, Morton M. Astrahan, Donald D. Chamberlin, Raymond A. Lorie, Thomas G. Price.<br>ACM SIGMOD International Conference on Management of Data, 1979.</p><p>Paper describes the optimizer for System R IBM&#8217;s research prototype that became DB2. It solves a problem that didn&#8217;t exist before 1979: given a SQL query with multiple joins, <strong>how does the database decide which order to join the tables, which access method to use, and which algorithm to apply at each step?</strong></p><blockquote><p>Before Selinger, databases picked join orders heuristically the order the programmer wrote them. Selinger&#8217;s insight was simple and has never been improved on: enumerate all plans, cost each one, pick the cheapest.</p></blockquote><p><a href="https://dl.acm.org/doi/10.1145/582095.582099">Paper Link</a></p><div><hr></div><p>&#128232; If this was useful, share it with one engineer on your team who manages production databases.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><h2>3. Four Ideas That Became Every SQL Optimizer</h2><h3>Idea 1: Cost as a Currency</h3><p>Selinger introduced <strong>concept of </strong><em><strong>cost units</strong></em> an abstract currency combining I/O pages read and CPU work, blended into one number so the optimizer can compare any two plans.</p><p>In PostgreSQL today, cost 1.0 is the price of fetching one 8KB page sequentially. Every parameter <code>seq_page_cost</code>, <code>random_page_cost</code>, <code>cpu_tuple_cost</code> is a descendant of Selinger&#8217;s original units.</p><h3>Idea 2: Dynamic Programming for Join Order</h3><p>Rather than trying every permutation of N tables (which is N factorial untenable beyond 8 tables), Selinger applied dynamic programming. Build up optimal plans for 2-table joins, then 3-table joins, reusing the results.</p><p>PostgreSQL uses this exact approach. Source file <a href="https://github.com/postgres/postgres/blob/master/src/backend/optimizer/path/joinrels.c">PostgreSQL: joinrels.c</a> is named after the concept. Query plans you see in <code>EXPLAIN</code> are the winners of a dynamic-programming search.</p><h3>Idea 3: Interesting Orders</h3><p>An ordered intermediate result is worth more than an unordered one it can feed a merge join, avoid a sort, or support ORDER BY for free. Selinger called these &#8220;interesting orders&#8221; and proved the optimizer must track them separately.</p><p>PostgreSQL&#8217;s <code>pathkeys</code> are the direct descendant. When you see a plan that skips a sort because an index returned data in the right order.</p><h3>Idea 4: Selectivity Estimation via Statistics</h3><p>Selinger introduced the idea that the optimizer must estimate how many rows pass each filter. To do this, database maintains statistics about column distributions.</p><p>Her 1979 formulas used simple assumptions: uniform distribution, column independence, constant join selectivity factors. Every one of those assumptions is wrong on real data.</p><p>Which brings us to the second half of this post.</p><h2>4. What PostgreSQL Kept From 1979</h2><p>Read <a href="https://github.com/postgres/postgres/blob/master/src/backend/optimizer/README">src/backend/optimizer/README</a> in the PostgreSQL source. It reads like a modernized version of Selinger&#8217;s paper.</p><p>Preserved almost unchanged:</p><ul><li><p>Dynamic programming for join enumeration (up to <code>geqo_threshold</code>, default 12 tables)</p></li><li><p>Cost units as an abstract currency</p></li><li><p>Interesting-order tracking via pathkeys</p></li><li><p>Per-node cost estimation composed from child node costs</p></li><li><p>Overall pattern: enumerate plans, cost them, pick the cheapest</p></li></ul><h2>5. What PostgreSQL Had to Change</h2><p>The 1979 paper assumed:</p><ul><li><p>Columns are independent (wrong)</p></li><li><p>Data is uniformly distributed (wrong)</p></li><li><p>Join selectivity can be estimated from single-table stats (wrong past 2 joins)</p></li><li><p>The database is small enough to cost every plan (wrong past ~12 tables)</p></li></ul><p>Every major PostgreSQL statistics feature is a patch on one of these assumptions:</p><ul><li><p><strong>Histograms</strong> (for non-uniform distributions)</p></li><li><p><strong>Most-common-value lists</strong> (for skewed data)</p></li><li><p><strong>GEQO genetic query optimization</strong> (for queries with too many tables for dynamic programming)</p></li><li><p><strong>Extended statistics</strong> via <code>CREATE STATISTICS</code> (for correlated columns)</p></li><li><p><strong>Planner hooks and custom scan nodes</strong> (for when the default costing fails)</p></li></ul><p>The <a href="/__u/haiderzdbre.substack.com/p/postgresql-bad-query-plans-vldb-paper?r=6lq3qw">VLDB 2015 paper </a>I wrote about last week measured what happens when these patches aren&#8217;t enough. Answer: plans that are 1,000x to 100,000,000x off on large join queries.</p><h2>6. Where Selinger&#8217;s Assumptions Still Bite You in Production</h2><p>Three patterns I see repeatedly:</p><h3>Pattern 1: Correlated predicates at the leaf</h3><p><code>WHERE region = 'US' AND currency = 'USD'</code> the planner multiplies selectivities as if they&#8217;re independent. They aren&#8217;t. CREATE STATISTICS is the direct fix, and the PostgreSQL 18 manual says so in Chapter 14.2.2.</p><h3>Pattern 2: Join cardinality explosion</h3><p>Selinger&#8217;s join selectivity formula was a constant. PostgreSQL refined it but the error compounds with join depth. By the fifth join the estimate is meaningless. This is why big analytical queries against real schemas go wildly wrong.</p><h3>Pattern 3: GEQO kicks in at 12 joins and plan becomes non-deterministic</h3><p>When your query has more than <code>geqo_threshold</code> tables, PostgreSQL switches from Selinger&#8217;s dynamic programming to a genetic algorithm. Same query can produce different plans between executions. <strong>On Sev-1 calls this is what &#8220;the query was fine yesterday&#8221; usually means.</strong></p><h2>7. 5 Things You Can Do About It</h2><h3>Fix 1 &#8212; Read your EXPLAIN plans with Selinger&#8217;s framework in mind</h3><p>Every node has a cost. Every cost came from a row estimate. Every row estimate came from statistics. When a plan is bad, walk backward through that chain.</p><h3>Fix 2 &#8212; Raise geqo_threshold if your queries routinely touch 12+ tables</h3><p>Set <code>geqo_threshold</code> to 14 or 16 if you have CPU budget. Deterministic dynamic-programming plans beat genetic-algorithm plans almost every time.</p><h3>Fix 3 &#8212; CREATE STATISTICS on the column pairs Selinger&#8217;s math can&#8217;t handle</h3><p>This is direct fix for Pattern 1. Eight years old, still the biggest planner win you can give yourself in one DDL statement.</p><h3>Fix 4 &#8212; Use the EXPLAIN ANALYZE, BUFFERS output to validate cost model inputs</h3><p>If <code>shared_blks_read</code> is high and your <code>random_page_cost</code> is 4.0, the planner thinks random reads are slow when they aren&#8217;t. Fix <code>random_page_cost</code> on SSDs.</p><h3>Fix 5 &#8212; Stop fighting the planner. Fix its inputs.</h3><p>Selinger&#8217;s algorithm is correct given accurate statistics. The VLDB paper proved this empirically in 2015. Hints, query rewrites, and <code>enable_seqscan = OFF</code> are not the answer. Fresh ANALYZE, correct statistics target, extended statistics, and correct cost parameters are.</p><h2>8. Key Takeaway</h2><p>Your PostgreSQL optimizer is a 46-year-old algorithm with 30 years of patches on top.</p><p>Algorithm is almost always right. Patches cover the places where Selinger&#8217;s 1979 assumptions fail on real data.</p><p>If you know which assumption is failing, you know which patch to apply.</p><div><hr></div><p>&#128640; <strong>PostgreSQL Health Report</strong> &#8212; 60+ diagnostic checks in one SQL file. Statistics freshness, extended stats coverage, cost parameter audit, bloat, vacuum health.</p><p><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></p><p>&#128204; Upgrade to Premium ($8/month) &#8212; Premium subscribers get the full SQL diagnostic packs, step-by-step playbooks, and the queries I actually run during a Sev-1.</p><p><a href="/__u/haiderzdbre.substack.com/subscribe">Upgrade to Paid</a></p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/postgresql-optimizer-selinger-1979-system?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/postgresql-optimizer-selinger-1979-system?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/postgresql-optimizer-selinger-1979-system/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/postgresql-optimizer-selinger-1979-system/comments"><span>Leave a comment</span></a></p><p></p><h4>References</h4><ul><li><p>Selinger, P. G., Astrahan, M. M., Chamberlin, D. D., Lorie, R. A., Price, T. G. (1979). <em>Access Path Selection in a Relational Database Management System.</em> ACM SIGMOD. &#8212; <a href="https://dl.acm.org/doi/10.1145/582095.582099">https://dl.acm.org/doi/10.1145/582095.582099</a></p></li><li><p>PostgreSQL 18 Source &#8212; <em>src/backend/optimizer/README</em> &#8212; <a href="https://github.com/postgres/postgres/blob/master/src/backend/optimizer/README">GitHub</a></p></li><li><p>PostgreSQL 18 Documentation &#8212; <em>Genetic Query Optimizer (GEQO)</em> &#8212; <a href="https://www.postgresql.org/docs/18/geqo.html">https://www.postgresql.org/docs/18/geqo.html</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[I know this stuff. But in the interview I freeze. Kit I built for this]]></title><description><![CDATA[31 questions. Weak answer vs strong answer. What the interviewer is actually scoring]]></description><link>https://haiderzdbre.substack.com/p/postgresql-interview-prep-kit</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgresql-interview-prep-kit</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Fri, 01 May 2026 04:50:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nreg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Two types of emails land in my inbox every week.</p><p>Engineers actively interviewing for DBA or SRE roles. They all say the same thing: </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><blockquote><p>I know this stuff. I&#8217;ve run Postgres in production. But in the interview I freeze.</p></blockquote><p>Premium subscribers who&#8217;ve gone deep into the incident write-ups, asking: </p><blockquote><p>How do I turn this into something I can actually perform at a senior level?</p></blockquote><p>This kit is built for both.</p><p>Value isn&#8217;t the questions. </p><p>It&#8217;s the language clarifying questions, reasoning patterns, way senior engineers communicate under pressure that signals: this person has handled a Sev-1 in production.</p><p>That&#8217;s the gap most candidates underestimate.</p><p>Part 1 covers what eliminates most candidates before the offer stage: </p><p>incident diagnosis, migration safety, and query plan reading. </p><p>Part 2 is where offers are won or lost. Work in progress.</p><p><a href="https://haiderdba.gumroad.com/l/postgresql-dba-interview">&#8594; PostgreSQL DBA Interview Prep Kit Part 1</a></p><p>Price: &#8364;12.99 </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!nreg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!nreg!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png 424w, /__u/substackcdn.com/image/fetch/$s_!nreg!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png 848w, /__u/substackcdn.com/image/fetch/$s_!nreg!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png 1272w, /__u/substackcdn.com/image/fetch/$s_!nreg!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!nreg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png" width="742" height="817" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:817,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66030,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/195919086?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!nreg!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png 424w, /__u/substackcdn.com/image/fetch/$s_!nreg!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png 848w, /__u/substackcdn.com/image/fetch/$s_!nreg!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png 1272w, /__u/substackcdn.com/image/fetch/$s_!nreg!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe209d8b2-2485-4c94-94aa-fec90ea92fd6_742x817.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Haider</p>]]></content:encoded></item><item><title><![CDATA[[PREMIUM] PostgreSQL Extended Statistics Diagnostic with 8 Queries]]></title><description><![CDATA[Last week I wrote about the VLDB paper that proved PostgreSQL&#8217;s independence assumption drives most bad plans. Here are the 8 queries I use to find every correlated column pair.]]></description><link>https://haiderzdbre.substack.com/p/postgresql-extended-statistics-diagnostic</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgresql-extended-statistics-diagnostic</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Tue, 28 Apr 2026 04:50:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JKTd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>CREATE STATISTICS Audit Nobody Runs</h1><div><hr></div><h2>Table of Contents</h2><ol><li><p>Why This Audit Matters</p></li><li><p>First Wrong Response: CREATE STATISTICS On Every Table</p></li><li><p>Second Wrong Response: Trust Autoanalyze to Catch It</p></li><li><p>How PostgreSQL Uses Extended Statistics Internally</p></li><li><p>Query 1 &#8212; Existing Extended Statistics Inventory</p></li><li><p>Query 2 &#8212; Top Slow Queries with Multi-Column WHERE Clauses</p></li><li><p>Query 3 &#8212; Detect Functional Dependencies Automatically</p></li><li><p>Query 4 &#8212; Find Estimate Divergence in pg_stat_statements</p></li><li><p>Query 5 &#8212; Column Correlation Via Cross-Tabulation</p></li><li><p>Query 6 &#8212; Extended Stats Coverage on Partitioned Tables</p></li><li><p>Query 7 &#8212; Statistics Target Audit</p></li><li><p>Query 8 &#8212; Before/After Plan Validation</p></li><li><p>Diagnostic Sequence to Follow</p></li><li><p>Key Takeaway</p></li></ol><div><hr></div><p>&#128232; If this saved you time, share it with one engineer who manages production databases.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a paid subscriber 8$/month.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>1. Why This Audit Matters</h2><p>If you read last week&#8217;s post, you already know the finding: PostgreSQL multiplies selectivities as if every column is independent. On real data, columns are correlated. Planner underestimates. Nested loops get chosen where hash joins should win. Queries that ran in seconds start taking hours.</p><p>Fix <code>CREATE STATISTICS</code> has been in PostgreSQL since version 10. Most production databases have zero extended statistics objects. This audit finds where you need them and where you don&#8217;t.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!JKTd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!JKTd!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!JKTd!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!JKTd!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!JKTd!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!JKTd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1926026,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/195178739?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!JKTd!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!JKTd!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!JKTd!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!JKTd!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc57adb1-5e8e-4948-a38f-abca564a1704_1024x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>2. First Wrong Response: CREATE STATISTICS On Every Table</h2><p>Don&#8217;t. Extended statistics have a real cost. ANALYZE time grows. Planner work per query grows. Defining <code>mcv</code> across five columns on a 500M-row table can add seconds to every ANALYZE run and measurable overhead to every query plan.</p><p>Target only where the planner is getting it wrong. Query 4 tells you where.</p><div><hr></div><h2>3. Second Wrong Response: Trust Autoanalyze to Catch It</h2><p>Autoanalyze updates single-column statistics. It does not create extended statistics objects. An object only exists after you run <code>CREATE STATISTICS</code> manually. Until then, autoanalyze can refresh its values but it cannot invent the object itself.</p><div><hr></div><h2>4. How PostgreSQL Uses Extended Statistics Internally</h2><p>From the PostgreSQL 18 manual, Chapter 14.2.2:</p><blockquote><p><em>Extended statistics objects track data about the specified table, foreign table, or materialized view. The object does not generate statistics until ANALYZE is run on the table.</em></p></blockquote><p>Three kinds of extended statistics:</p><ul><li><p><strong>dependencies</strong> &#8212; functional dependency between columns. Cheapest to compute, smallest storage.</p></li><li><p><strong>ndistinct</strong> &#8212; number of distinct combinations. Critical for GROUP BY on multiple columns.</p></li><li><p><strong>mcv</strong> &#8212; most common value combinations. Largest storage, highest accuracy on skewed data.</p></li></ul><p>Planner reads these from <code>pg_statistic_ext_data</code> at plan time.</p><div><hr></div><h2>5. Query 1 &#8212; Existing Extended Statistics Inventory</h2><p>Start here. You need to know what already exists before creating more.</p><pre><code><code>SELECT
  n.nspname AS schema_name,
  c.relname AS table_name,
  s.stxname AS stats_name,
  pg_catalog.array_to_string(
    ARRAY(
      SELECT a.attname
      FROM unnest(s.stxkeys) AS k
      JOIN pg_attribute a ON a.attrelid = s.stxrelid AND a.attnum = k
    ),
    ', '
  ) AS columns,
  s.stxkind AS kinds,
  CASE
    WHEN 'd' = ANY(s.stxkind) THEN 'dependencies '
    ELSE ''
  END ||
  CASE
    WHEN 'f' = ANY(s.stxkind) THEN 'ndistinct '
    ELSE ''
  END ||
  CASE
    WHEN 'm' = ANY(s.stxkind) THEN 'mcv'
    ELSE ''
  END AS stat_types
FROM pg_statistic_ext s
JOIN pg_class c ON c.oid = s.stxrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
ORDER BY n.nspname, c.relname;</code></code></pre><p>If this returns zero rows, your database is running with the exact limitation the VLDB paper measured in 2015.</p><h2>6. Query 2 &#8212; Top Slow Queries with Multi-Column WHERE Clauses</h2><p>The queries most likely to benefit from extended statistics are the ones with two or more columns in the WHERE clause on the same table.</p>
      <p>
          <a href="/__u/haiderzdbre.substack.com/p/postgresql-extended-statistics-diagnostic">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[[FREE] VLDB Paper Explains Why PostgreSQL Chooses Bad Plans]]></title><description><![CDATA[In 2015, four researchers proved what every DBA already knew. Ten years later, PostgreSQL 18 ships with the same fundamental limitation. Here&#8217;s what they found and how to work around it.]]></description><link>https://haiderzdbre.substack.com/p/postgresql-bad-query-plans-vldb-paper</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgresql-bad-query-plans-vldb-paper</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Fri, 24 Apr 2026 04:51:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!chnd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Why PostgreSQL Chooses Bad Plans Still True in PostgreSQL 18</h1><div><hr></div><h2>Table of Contents</h2><ol><li><p>What We&#8217;re Working With</p></li><li><p>Paper</p></li><li><p>Four Findings Matter</p></li><li><p>What PostgreSQL 18 Chapter 70 Actually Promises</p></li><li><p>Gap Between Manual and Reality</p></li><li><p>6 Things You Can Fix (Paper Says Will Actually Help)</p></li><li><p>Checklist What to Verify Before You Blame Planner</p></li><li><p>Skip the Guesswork &#8594; PostgreSQL Health Check</p></li></ol><div><hr></div><p>&#128232; If this was useful, share it with one engineer on your team who manages production databases.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication FREE and Premium 8$/month. Each week I share practical takeaways from real incidents no theory just what actually breaks and how to fix it.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><p>Last week&#8217;s post walked through an EXPLAIN ANALYZE plan line by line. The question I got back most often came in the same form every time:</p><p><em>&#8220;My estimate says 1,000 rows. Actual says 8 million. Why does PostgreSQL keep doing this?&#8221;</em></p><p>Short answer: <strong>PostgreSQL&#8217;s planner is rarely wrong about math.</strong> It&#8217;s wrong about your data. And that&#8217;s not a bug anyone is going to fix in PostgreSQL 18, 19, or 20 because the problem is deeper than a code path.</p><p>Four researchers proved this in 2015. Paper is public. Findings still apply to PostgreSQL 18 today. Every DBA should read it once. This post is that reading.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!chnd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!chnd!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!chnd!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!chnd!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!chnd!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!chnd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1941034,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/195170657?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!chnd!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!chnd!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!chnd!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!chnd!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa643bf51-5818-40b3-b4cb-8a07da73b3b2_1024x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><h2>1. What We&#8217;re Working With</h2><p>Before we get to the paper, here&#8217;s the thing it tried to measure.</p><p>Every query plan in PostgreSQL is a tree. Each node has two numbers the planner cares about:</p><ul><li><p><strong>Rows</strong> &#8212; how many rows this node will produce</p></li><li><p><strong>Cost</strong> &#8212; how expensive this node is to execute</p></li></ul><p>Cost is calculated from rows. Rows is calculated from statistics. Statistics are gathered by ANALYZE. If the row estimate is wrong, every cost decision downstream is wrong. Wrong index choice. Wrong join algorithm. Wrong memory allocation.</p><p>Question nobody had measured at scale until 2015: how wrong are these estimates on real workloads?</p><h2>2. Paper</h2><p><strong>&#8220;How Good Are Query Optimizers, Really?&#8221;</strong><br>Viktor Leis, Andrey Gubichev, Atanas Mirchev, Peter Boncz, Alfons Kemper, Thomas Neumann.<br>Proceedings of the VLDB Endowment, Vol. 9, No. 3 (2015).</p><p><a href="https://www.vldb.org/pvldb/vol9/p204-leis.pdf">Paper</a></p><p>They built a benchmark called <strong>JOB the Join Order Benchmark</strong>. 113 real queries over the public IMDB dataset. Not TPC-H. Not synthetic. Real schemas with real skew and real correlations.</p><p>They ran JOB against PostgreSQL, two commercial databases (anonymized as DBMS A and DBMS B), and HyPer (a research system). At every operator in every plan, they recorded the estimated cardinality and the actual cardinality.</p><p>Then they asked four questions:</p><ol><li><p>How accurate are cardinality estimates in practice?</p></li><li><p>If estimates are wrong, how much does it actually cost?</p></li><li><p>Is PostgreSQL&#8217;s cost model the problem, or are the inputs?</p></li><li><p>Do better join-enumeration algorithms help?</p></li></ol><p>The answers changed how I diagnose slow queries.</p><div><hr></div><h2>3. Four Findings Matter</h2><h3>Finding 1: Estimates get wrong fast, and then exponentially wrong</h3><p>Single-table filters were within a factor of 10 most of the time. Reasonable.</p><p>Two-way joins were already off by factors up to 100.</p><p>By the time you reach six-way joins, estimate-to-actual ratios hit <strong>10&#8310; to 10&#8312;</strong>. That&#8217;s six to eight orders of magnitude.</p><p>From the paper:</p><blockquote><p><em>Cardinality estimates are routinely wrong by factors of 1,000 or more. The errors grow super-linearly with the number of joins.</em></p></blockquote><p>If you&#8217;ve ever seen a query that runs fine on a 3-table join and collapses when you add a fourth, this is why. Error isn&#8217;t linear. Adding one more join doesn&#8217;t add 10% more risk. It multiplies the existing error.</p><h3>Finding 2: PostgreSQL&#8217;s cost model is not the problem</h3><p>This is the finding that changed my mental model completely.</p><p>Researchers ran every query with <strong>actual</strong> cardinalities substituted in, then let each database pick a plan using that correct information.</p><blockquote><p><em>When given accurate cardinalities, PostgreSQL&#8217;s cost model chooses near-optimal plans.</em></p></blockquote><p>Cost math is fine. Inputs are not.</p><p>If you&#8217;re reaching for hints, query rewrites, or <code>SET enable_seqscan = OFF</code>, stop. Paper says PostgreSQL already knows how to pick the right plan. You just need to fix what it believes about your data.</p><h3>Finding 3: Independence assumption is where it breaks</h3><p>PostgreSQL assumes that predicates on different columns are statistically independent.</p><p><code>WHERE country = 'US' AND state = 'California'</code></p><p>It computes:</p><p><code>selectivity(country='US') &#215; selectivity(state='California')</code></p><p>Which is only correct if knowing the country tells you nothing about the state. California exists only in the US. Knowing one determines the other. Multiplication is wrong, and planner has no way to know.</p><p>Paper&#8217;s finding is that this single assumption drives most of the underestimate errors in JOB. It&#8217;s also exactly the problem PostgreSQL manual documents. Which brings us to section 4.</p><h3>Finding 4: Join enumeration is a smaller problem than people think</h3><p>Most engineers, when a query is slow, blame the join order. Paper tested this directly. Given correct cardinalities, even a basic dynamic-programming join enumerator produced near-optimal plans.</p><p>Join order matters. But it matters much less than your estimates being right in the first place. Fix cardinality first. Then worry about whether the planner is picking the wrong algorithm.</p><div><hr></div><p>&#128640; <strong>PostgreSQL Health Report</strong> &#8212; One SQL file. 60+ diagnostic checks. Detects stale statistics, bloat, lock chains, replication slot danger, unindexed FK columns, and more. Used by DBAs managing production at scale.</p><p><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></p><div><hr></div><h2>4. What PostgreSQL 18 Chapter 69 Actually Promises</h2><p>Open the PostgreSQL 18 manual. Go to <em>Chapter 70 How the Planner Uses Statistics</em>. Read the first section carefully.</p><blockquote><p><em>By default, the planner assumes that values of different columns are independent.</em></p></blockquote><p>That is the manual telling you, in the current documentation for the current version, that default behavior is the problem Leis et al measured ten years ago.</p><p>Then manual tells you the fix exists:</p><blockquote><p><em>Since PostgreSQL 10, it has been possible to tell the planner about multi-column correlations by creating extended statistics objects with the CREATE STATISTICS command.</em></p></blockquote><p>And in Chapter 14.2.2 &#8212; <em>Extended Statistics</em>:</p><blockquote><p><em>It is common to see slow queries running bad execution plans because multiple columns used in the query clauses are correlated. The planner normally assumes that multiple conditions are independent of each other, an assumption that does not hold when column values are correlated.</em></p></blockquote><p>PostgreSQL 18 did not remove the independence assumption. It still ships. Fix is a feature you have to turn on, per table, per column set.</p><div><hr></div><h2>5. Gap Between Manual and Reality</h2><p>Here&#8217;s the part that frustrates me. Problem is documented. Fix is documented. Eight years after <code>CREATE STATISTICS</code> shipped, I still walk into production PostgreSQL instances where it has never been used on a single table.</p><p>Run this against your database right now:</p><pre><code><code>SELECT count(*) FROM pg_statistic_ext;</code></code></pre><p>If the answer is 0 and for most PostgreSQL instances I audit, it is you are running with the exact limitation the VLDB paper measured in 2015. You haven&#8217;t opted out of it. You just never opted in to the fix.</p><p>This isn&#8217;t a PostgreSQL-is-bad story. It&#8217;s a tooling gap. Default is safe. Optimization is opt-in. And opt-in features in databases are the ones that almost never get turned on.</p><div><hr></div><h2>6. 6 Things You Can Fix (Paper Says Will Actually Help)</h2><p>In order of impact, based directly on the paper&#8217;s findings and PostgreSQL 18 manual&#8217;s recommendations.</p><h3>Fix 1 &#8212; Raise statistics target on high-cardinality filter columns</h3><p>Default is 100. Meaning PostgreSQL samples 100 values for the most-common-values list and 100 histogram buckets. On a 100M row table with skewed data, that is not enough.</p><pre><code><code>ALTER TABLE orders ALTER COLUMN customer_id SET STATISTICS 1000;
ANALYZE orders;</code></code></pre><p>Raise it to 1000 on columns that appear in the WHERE clause of slow queries. 10,000 is the maximum and rarely needed.</p><h3>Fix 2 &#8212; CREATE STATISTICS on correlated columns</h3><p>This is direct fix for Finding 3.</p><pre><code><code>CREATE STATISTICS stats_orders_region_status (dependencies, ndistinct, mcv)
ON region, status
FROM orders;

ANALYZE orders;</code></code></pre><p>Three kinds of extended statistics:</p><ul><li><p><strong>dependencies</strong> &#8212; for columns where one determines another (zip &#8594; city)</p></li><li><p><strong>ndistinct</strong> &#8212; for correct row estimates on GROUP BY multiple columns</p></li><li><p><strong>mcv</strong> &#8212; for skewed distributions where certain combinations dominate</p></li></ul><p>Combine all three unless storage is a concern.</p><h3>Fix 3 &#8212; ANALYZE after bulk changes</h3><p>Autovacuum&#8217;s autoanalyze trigger is default 10% of the table. On a 1 billion row table, that&#8217;s 100 million changes before autoanalyze fires. By then your statistics have been wrong for hours or days.</p><p><strong>After any large migration, backfill, or DELETE run ANALYZE manually. Don&#8217;t wait.</strong></p><h3>Fix 4 &#8212; Fix function-wrapped WHERE clauses</h3><p>Paper found function wrapping disables index use silently. <code>WHERE UPPER(email) = 'USER@X.COM'</code> cannot use a B-tree index on <code>email</code>.</p><p>Two fixes:</p><pre><code><code>-- Option 1: Functional index
CREATE INDEX idx_users_email_upper ON users (UPPER(email));

-- Option 2: Rewrite to use the existing index
SELECT * FROM users WHERE email = lower('USER@X.COM');</code></code></pre><h3>Fix 5 &#8212; Set random_page_cost correctly for SSD</h3><p>Default is 4.0. That assumes spinning disk. On SSD or cloud block storage, random reads are close to sequential reads.</p><pre><code><code>ALTER SYSTEM SET random_page_cost = 1.1;
SELECT pg_reload_conf();</code></code></pre><p>This single change flips the planner&#8217;s cost math for thousands of queries. On cloud-hosted PostgreSQL it is the highest-impact one-line change you can make.</p><h3>Fix 6 &#8212; Use EXPLAIN (ANALYZE, BUFFERS) not EXPLAIN ANALYZE</h3><p>BUFFERS tells you whether the query hit the page cache or read from disk. Without BUFFERS, you&#8217;re debugging with half the information.</p><pre><code><code>EXPLAIN (ANALYZE, BUFFERS) SELECT ...;</code></code></pre><p>If <code>shared_blks_read</code> is much higher than <code>shared_blks_hit</code>, your working set doesn&#8217;t fit in <code>shared_buffers</code>, and no amount of query tuning fixes that.</p><div><hr></div><h2>7. Checklist What to Verify Before You Blame Planner</h2><p>Run through these every time a plan looks wrong. Paper&#8217;s findings map directly.</p><ul><li><p>Estimated rows vs actual rows if off by more than 10&#215;, statistics are the problem</p></li><li><p>Last ANALYZE on the table if stale, fix that before anything else</p></li><li><p>WHERE clause has multiple columns from the same table candidate for CREATE STATISTICS</p></li><li><p>Any column wrapped in a function index bypass guaranteed</p></li><li><p><code>random_page_cost</code> set for your storage type</p></li><li><p><code>shared_buffers</code> sized for your working set</p></li><li><p><code>pg_statistic_ext</code> does it even have entries? If not, you&#8217;re running with the paper&#8217;s limitation</p></li></ul><p>If every item checks out and the plan is still wrong, then and only thene planner might genuinely be wrong. That&#8217;s rare.</p><div><hr></div><h2>8. Skip the Guesswork &#8594; PostgreSQL Health Check</h2><p>Reading one EXPLAIN plan teaches you. Reading one paper changes how you think. But your database has hundreds, maybe thousands, of active queries. Every one of them has a row-estimate number. Most of them you&#8217;ve never looked at.</p><p>PostgreSQL Health Check runs 60+ diagnostic queries against your database and surfaces:</p><ul><li><p>Every table with stale statistics</p></li><li><p>Extended statistics coverage</p></li><li><p>The top time-consuming queries</p></li><li><p>Missing and unused indexes</p></li><li><p>Table bloat, vacuum health, and statistics freshness</p></li><li><p>Everything ranked by severity with specific fix recommendations</p></li></ul><p>One report. 60+ queries. Every blind spot uncovered.</p><p>&#128073; <a href="https://haiderdba.gumroad.com/l/ccget">Get Your PostgreSQL Health Check &#8594;</a></p><div><hr></div><h3>Key Takeaway</h3><p>PostgreSQL&#8217;s cost model is not the problem.</p><p>Assumptions about your data are.</p><p>VLDB proved it in 2015. PostgreSQL 18 manual confirms it. Fix CREATE STATISTICS has existed for eight years. Most databases have never used it.</p><p>Now yours doesn&#8217;t have to be one of them.</p><p>&#128231; <strong>Want the deep-dive playbook?</strong></p><p><a href="/__u/haiderzdbre.substack.com/subscribe">Upgrade to Paid</a></p><p>Enjoyed reading? Restack, share with your audience.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/postgresql-bad-query-plans-vldb-paper?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/postgresql-bad-query-plans-vldb-paper?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/postgresql-bad-query-plans-vldb-paper/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/postgresql-bad-query-plans-vldb-paper/comments"><span>Leave a comment</span></a></p><p></p><h4>References</h4><ul><li><p>Leis, V., Gubichev, A., Mirchev, A., Boncz, P., Kemper, A., Neumann, T. (2015). <em>How Good Are Query Optimizers, Really?</em> Proceedings of the VLDB Endowment, Vol. 9, No. 3. &#8212; <a href="http://www.vldb.org/pvldb/vol9/p204-leis.pdf">http://www.vldb.org/pvldb/vol9/p204-leis.pdf</a></p></li><li><p>PostgreSQL 18 Documentation, Chapter 69 &#8212; <em>How the Planner Uses Statistics</em> &#8212; <a href="https://www.postgresql.org/docs/18/planner-stats-details.html">https://www.postgresql.org/docs/18/planner-stats-details.html</a></p></li><li><p>PostgreSQL 18 Documentation, Chapter 14.2 &#8212; <em>Statistics Used by the Planner</em> &#8212; <a href="https://www.postgresql.org/docs/18/planner-stats.html">https://www.postgresql.org/docs/18/planner-stats.html</a></p></li><li><p>PostgreSQL 18 Documentation, Chapter 14.2.2 &#8212; <em>Extended Statistics</em> &#8212; <a href="https://www.postgresql.org/docs/18/planner-stats.html#PLANNER-STATS-EXTENDED">https://www.postgresql.org/docs/18/planner-stats.html#PLANNER-STATS-EXTENDED</a></p></li><li><p>Join Order Benchmark (JOB) &#8212; <a href="https://github.com/gregrahn/join-order-benchmark">https://github.com/gregrahn/join-order-benchmark</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[[PREMIUM] PostgreSQL Query Plan Diagnostic with 9 Queries]]></title><description><![CDATA[When PostgreSQL Ignores Your Index and Chooses a Sequential Scan]]></description><link>https://haiderzdbre.substack.com/p/postgresql-index-not-used-diagnostic-queries</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgresql-index-not-used-diagnostic-queries</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Tue, 21 Apr 2026 04:50:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zsDv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3><em>Index exists. Column is in the WHERE clause. EXPLAIN shows a Seq Scan on 80 million rows.</em></h3><div><hr></div><h4><strong>Table of Contents</strong></h4><ol><li><p>Why Engineers Get This Wrong</p></li><li><p>First Wrong Response: Force Index</p></li><li><p>Second Wrong Response: Add Another Index</p></li><li><p>How PostgreSQL Actually Decides Between Index Scan and Sequential Scan</p></li><li><p>Eight Causes of Index Bypass In Order of Frequency</p></li><li><p>Query 1 Index Usage by Table: Which Indexes Are Never Used?</p></li><li><p>Query 2 Table Statistics Freshness: Is Planner Working From Stale Data?</p></li><li><p>Query 3 Column Statistics Detail: What Planner Believes About Your Data</p></li><li><p>Query 4 Function-Wrapped Columns: Index Bypass Nobody Expects</p></li><li><p>Query 5 Table Bloat Distorting Cost Estimates</p></li><li><p>Query 6 Storage Cost Parameters: Is random_page_cost Wrong for Your Storage?</p></li><li><p>Query 7 Actual vs Estimated Rows: Finding Planner&#8217;s Worst Mistakes</p></li><li><p>Query 8 Force Index Usage for Testing (NOT for Production)</p></li><li><p>Query 9 Full Query Plan Audit for Your Slowest Queries</p></li><li><p>Diagnostic Sequence to Follow</p></li><li><p>Key Takeaway</p></li></ol><p>&#128232; <strong>If this saved you time, share it with one engineer who manages production databases.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber 8$/Month.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!zsDv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!zsDv!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!zsDv!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!zsDv!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!zsDv!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!zsDv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2007810,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/192159841?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!zsDv!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!zsDv!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!zsDv!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!zsDv!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a4dd466-7063-4f26-ba08-0ea21e4de2c7_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p>This is a post I&#8217;ve been asked about more than almost any other PostgreSQL topic. Question always comes in the same form: <em><strong>Index is there. Query uses the column. Why is PostgreSQL doing a sequential scan?</strong></em></p><p>Frustrating answer: PostgreSQL is almost always right. It&#8217;s choosing the sequential scan because its math says it&#8217;s cheaper. Problem is usually that the math is based on wrong inputs <strong>stale statistics, wrong cost parameters, or a query structure that prevents index use.</strong></p><p>Before the queries, two responses that make the situation worse.</p><div><hr></div><h2>Why Engineers Get This Wrong</h2><p>Instinct is to fight the planner. Add a hint. Force the index. Rewrite the query to trick PostgreSQL into using it. These responses treat the planner as an obstacle rather than a tool that&#8217;s working with incorrect information.</p><p>PostgreSQL doesn&#8217;t have index hints in the traditional sense. </p><p><code>SET enable_seqscan = OFF</code> exists, but using it in production is almost always wrong it forces index use even when it&#8217;s genuinely more expensive. You&#8217;ll speed up one query and slow down ten others.</p><p>Right response is to fix what the planner is getting wrong, then let it choose. Query 7 is designed exactly for this: it shows you where the planner&#8217;s estimates diverge most from reality. Fix the estimate; the plan fixes itself.</p><div><hr></div><h2>First Wrong Response: Force the Index</h2><p><code>SET enable_seqscan = OFF</code> forces PostgreSQL to use indexes even when they&#8217;re more expensive. This works temporarily on the query you&#8217;re debugging. It breaks other queries in the same session that legitimately benefit from sequential scans. <strong>Never set this globally. Never set it in application connection strings.</strong> Use it only in a debugging session to confirm the index would actually be used if the planner chose it.</p><div><hr></div><h2>Second Wrong Response: Add Another Index</h2><p>If PostgreSQL isn&#8217;t using the existing index, adding another index on the same or related columns is almost never the fix. PostgreSQL has one index to choose from and isn&#8217;t using it adding a second gives it two to ignore. Problem is why it&#8217;s not using the first one, not how many indexes are available.</p><div><hr></div><h2>How PostgreSQL Actually Decides Between Index Scan and Sequential Scan</h2><p>PostgreSQL&#8217;s cost-based optimizer calculates a cost estimate for every possible execution plan. It chooses the plan with the lowest estimated cost. <em><strong>Cost is measured in arbitrary units, where 1.0 = time to fetch one 8KB page sequentially from disk.</strong></em></p><p><strong>An index scan costs: <br>(pages traversed in index) &#215; random_page_cost + (rows fetched from heap) &#215; random_page_cost.<br></strong><br><strong>A sequential scan costs: <br>(total table pages) &#215; seq_page_cost.</strong></p><p>If the planner estimates 90% of rows will match the WHERE clause even with a correct index a sequential scan is genuinely cheaper. </p><p>If the planner&#8217;s statistics are stale and it thinks 90% match when only 0.1% actually match it <em><strong>chooses a sequential scan incorrectly. That&#8217;s the case you&#8217;re diagnosing.</strong></em></p><div><hr></div><p>&#128640; <strong>PostgreSQL Health Report</strong> 60+ diagnostic checks. Unused indexes, vacuum lag, replication slot danger, and more.</p><p><strong><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></strong></p><div><hr></div><h2>Eight Causes of Index Bypass In Order of Frequency</h2><ol><li><p><strong>Stale statistics</strong> planner underestimates selectivity, thinks too many rows match</p></li><li><p><strong>Low selectivity</strong> column actually has few distinct values; sequential scan is correct</p></li><li><p><strong>Function wrap on the column</strong> <code>WHERE UPPER(email) = ?</code> breaks index on <code>email</code></p></li><li><p><strong>Wrong random_page_cost</strong> default 4.0 assumes spinning disk; SSDs need 1.1</p></li><li><p><strong>Table too small</strong> PostgreSQL always seq scans tables under ~8 pages</p></li><li><p><strong>Implicit type cast</strong> <code>WHERE id = '123'</code> when id is integer forces a cast, index may not apply</p></li><li><p><strong>Table bloat</strong> dead tuples inflate estimated row count, making seq scan look relatively cheaper</p></li><li><p><strong>LIKE with leading wildcard</strong> <code>WHERE name LIKE '%smith'</code> cannot use B-tree index</p></li></ol><div><hr></div><h2>Query 1 Index Usage by Table: Which Indexes Are Never Used?</h2>
      <p>
          <a href="/__u/haiderzdbre.substack.com/p/postgresql-index-not-used-diagnostic-queries">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[[DBEng] How Figma Keeps PostgreSQL Fast at Scale]]></title><description><![CDATA[Figma&#8217;s core product is a real-time multiplayer collaboration database problem.]]></description><link>https://haiderzdbre.substack.com/p/how-figma-keeps-postgresql-scale</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/how-figma-keeps-postgresql-scale</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Tue, 14 Apr 2026 04:50:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ial-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Millions of cursor movements and object changes hit PostgreSQL every second. Here is the architecture that keeps it fast.</h3><div><hr></div><h4><strong>Table of Contents</strong></h4><ol><li><p>Core Challenge: Multiplayer at Database Speed</p></li><li><p>Decision 1: Separate Real-Time Collaboration from Persistence</p></li><li><p>Decision 2: Read Replicas Routed by Staleness Tolerance</p></li><li><p>Decision 3: Aggressive Connection Pooling With PgBouncer</p></li><li><p>Decision 4: Partitioning Long-Lived Tables</p></li><li><p>What&#8217;s Universally Applicable</p></li></ol><p>&#128232; <strong>If this was useful, share it with one engineer on your team who manages production databases.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><p><strong>The Sev-1 Database</strong> is a reader-supported publication. Each week I share practical takeaways from real incidents no theory, just what actually breaks and how to fix it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!ial-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!ial-!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!ial-!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!ial-!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ial-!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!ial-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1877039,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/192157450?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!ial-!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!ial-!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!ial-!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!ial-!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F033c9ad4-e728-4fe0-a88b-a4bd98b43d0c_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Subscribe now</span></a></p><div><hr></div><p>Figma is unusual among design tools in that its core value proposition is fundamentally a database problem. Real-time multiplayer editing where multiple users see each other&#8217;s changes instantly requires a data architecture that can handle concurrent writes, deliver fast reads, and maintain consistency across sessions.</p><p>Here&#8217;s how they&#8217;ve built and evolved that architecture over time.</p><p>Based on Figma&#8217;s engineering blogs and talks by Evan Wallace, here&#8217;s a <strong>grounded breakdown of how their PostgreSQL architecture evolves at scale.</strong></p><div><hr></div><h2>Core Challenge: Multiplayer Collaboration as a PostgreSQL Performance Problem</h2><p>Most web applications are read-heavy. Figma&#8217;s workload is different. In an active design session, every cursor movement, every property change, every layer modification generates a database write. </p><p>Multiply that by hundreds of simultaneous users in a shared file and you have a write workload that few relational databases are designed to absorb directly.</p><p>Figma&#8217;s answer <strong>wasn&#8217;t to move away from PostgreSQL</strong> it was to be thoughtful about what hits PostgreSQL and what doesn&#8217;t.</p><div><hr></div><h2>Decision 1: Separate Real-Time Collaboration from Persistence</h2><p>Figma separates the live, in-session document state from the durable version stored in PostgreSQL.</p><p>Real-time collaboration is handled by a custom in-memory engine, where updates from multiple users are processed and synchronized.</p><p>PostgreSQL is used to persist the canonical document state rather than storing every individual user event.</p><p><strong>Principle:</strong> the database should store a consistent, durable representation of state, not every intermediate change.</p><div><hr></div><h2>Decision 2: PostgreSQL Read Replica Routing by Staleness Tolerance</h2><p>Figma routes queries based on whether they need fresh data or can tolerate a small replication lag.</p><p>Queries for file metadata, user settings, and non-real-time data go to read replicas. Queries that require the most current state of an active document go to the primary.</p><p>They built logic to detect when a replica&#8217;s lag exceeds a threshold and automatically fail that query to the primary. Replica lag threshold isn&#8217;t fixed it adapts based on the type of operation and the user&#8217;s context.</p><div><hr></div><p>&#128640; <strong>PostgreSQL Health Report</strong> One SQL file. 60+ diagnostic checks. Detects vacuum lag, bloat, lock chains, replication slot danger, and more. Used by DBAs managing production at scale.</p><p><strong><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></strong></p><div><hr></div><h2>Decision 3: PgBouncer Transaction-Mode Connection Pooling at Figma Scale</h2><p>At Figma&#8217;s scale, the number of concurrent active users far exceeds what PostgreSQL can handle as individual connections. PostgreSQL allocates ~10MB per connection; with tens of thousands of concurrent users, a direct connection-per-user model is impossible.</p><p>Figma runs <strong>PgBouncer in transaction-pooling mode.</strong> Rather than each user holding a dedicated PostgreSQL connection, connections are shared a user gets a connection for the duration of a transaction, then it&#8217;s returned to the pool.</p><p>Transaction-pooling requires careful application design. <strong>Prepared statements, advisory locks, and session-level features don&#8217;t work reliably in this mode</strong>. Figma&#8217;s application layer was designed with this constraint from relatively early on.</p><div><hr></div><h2>Decision 4: PostgreSQL Range Partitioning for Long-Lived Tables</h2><p>As Figma&#8217;s document history grew years of changes across millions of files some tables became large enough that even indexed queries started slowing down simply due to the volume of data being scanned.</p><p>Figma adopted range partitioning on time-based tables (document versions, event history, audit logs). Queries filtering by time &#8220;show me changes from the last 30 days&#8221; only scan the relevant partitions.</p><p>Operational benefit beyond query speed: <strong>older partitions can be archived or moved to cheaper storage</strong> without touching the hot partition that current queries hit.</p><div><hr></div><h2>What Any PostgreSQL Engineering Team Can Apply From Figma&#8217;s Architecture</h2><p><strong>Coalesce writes at the application layer.</strong> If your application generates events faster than you want to write to the database, batching is simpler to implement than it sounds and pays off immediately.</p><p><strong>Not all reads need to be fresh.</strong> Categorizing your read queries by staleness tolerance is low-effort design work that pays off in reduced primary load.</p><p><strong>Design for your connection pooler from the start.</strong> Retrofitting PgBouncer onto an application that uses session-level features is painful. If you&#8217;re not running a pooler yet, plan for transaction-pooling mode constraints before you need them.</p><h3>&#128231; Want the complete PostgreSQL Health Report?</h3><p>Includes MultiXact checks, vacuum monitoring, checkpoint diagnostics, WAL slot health, and 60+ diagnostic queries &#8212; all in one SQL file.</p><p><strong><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></strong></p><p>&#128204; <strong>Upgrade to Premium ($8/month)</strong> &#8212; Premium subscribers get the full SQL diagnostic packs, step-by-step playbooks, and the queries I actually run during a Sev-1.</p><p><strong><a href="/__u/haiderzdbre.substack.com/subscribe">Upgrade to Paid</a></strong></p><p><strong>References:</strong></p><p><strong><a href="https://www.figma.com/blog/how-figmas-databases-team-lived-to-tell-the-scale">How Figma's Databases Team Lived to Tell the Scale | Figma Blog</a></strong></p><p><strong><a href="https://madebyevan.com/figma/how-figmas-multiplayer-technology-works/">How Figma&#8217;s multiplayer technology works - Made by Evan</a></strong></p><p><a href="https://www.figma.com/blog/how-figma-scaled-to-multiple-databases/">The growing pains of database architecture | Figma Blog</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/how-figma-keeps-postgresql-scale?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/how-figma-keeps-postgresql-scale?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/how-figma-keeps-postgresql-scale/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/how-figma-keeps-postgresql-scale/comments"><span>Leave a comment</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[[PREMIUM] PostgreSQL Sev-1 Prevention Checklist]]></title><description><![CDATA[Slow query checklist tells you what&#8217;s broken right now. This one tells you what will break next week before your users find out first.]]></description><link>https://haiderzdbre.substack.com/p/postgresql-sev-1-prevention-checklist</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/postgresql-sev-1-prevention-checklist</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Fri, 10 Apr 2026 04:50:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!EbUq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>PostgreSQL Sev-1 Prevention Checklist</h1><div><hr></div><h4><strong>Table of Contents</strong></h4><ol><li><p>Phase 1 Replication Slot Health (5 min)</p></li><li><p>Phase 2 Transaction ID Wraparound Risk (3 min)</p></li><li><p>Phase 3 Autovacuum Winning vs Running (10 min)</p></li><li><p>Phase 4 Lock Risk Audit (5 min)</p></li><li><p>Phase 5 Connection Layer Health (5 min)</p></li><li><p>Phase 6 WAL and Checkpoint Configuration (5 min)</p></li><li><p>Phase 7 Index Health and Bloat (10 min)</p></li><li><p>Phase 8 Schema Risk Unindexed Foreign Keys (5 min)</p></li><li><p>Phase 9 Security and Access Audit (5 min)</p></li><li><p>Phase 10 Monitoring Coverage Gaps (5 min)</p></li></ol><p><em><strong>Share this with one engineer on your team who owns a production PostgreSQL instance.</strong></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><p><em><strong>The Sev-1 Database Real incidents. Root causes. The queries I actually run. No theory.</strong></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Subscribe now</span></a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!EbUq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!EbUq!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!EbUq!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!EbUq!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!EbUq!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!EbUq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1831174,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/192150540?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!EbUq!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!EbUq!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!EbUq!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!EbUq!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78cb2b59-b105-4427-b324-0928948af50e_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><p>Performance troubleshooting checklist I published in February tells you what is broken right now. This one is different.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;1709d45f-6291-4eb1-979e-e155bbfca78e&quot;,&quot;caption&quot;:&quot;Table of Contents&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;[PREMIUM] PostgreSQL Performance Troubleshooting Checklist&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:399286904,&quot;name&quot;:&quot;Haider Z @ Microsoft&quot;,&quot;bio&quot;:&quot;Database Engineer @ MSFT. I turn global outages into architectural edge. Get the root causes, performance hacks, and resilience tactics used at scale. Subscribe to The Sev-1 Database.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/489a1c96-476d-4a02-a09b-fb53d150dec2_1167x1167.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-02-26T13:43:08.485Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!CKMD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b8c1978-3295-43d8-a103-8388d3347054_1024x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://haiderzdbre.substack.com/p/postgresql-performance-troubleshooting-checklist&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:189248213,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:4,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6456877,&quot;publication_name&quot;:&quot;The Sev-1 Database&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!7Pgn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30ab7b47-3853-4472-a161-41d35e82e7b6_608x608.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>This is the checklist I run on a <strong>PostgreSQL instance before it breaks</strong>. The checks that catch replication slots hours before the disk fills. The checks that catch wraparound risk weeks before PostgreSQL stops accepting writes.<br><br>Most of these take under 60 seconds each. All of them have found real problems on production instances I&#8217;ve inherited or audited.</p><p>Run every phase.</p><div><hr></div><h2>Phase 1 Replication Slot Health (5 minutes)</h2><p>Replication slots are the most common source of surprise disk-full incidents I&#8217;ve handled. An inactive slot retains WAL indefinitely with no cap by default. The disk fills over days, not hours. By the time the alert fires you have minutes.</p><h4>Check 1.1 Slot inventory and retained WAL size</h4><pre><code><code>SELECT
  slot_name,
  slot_type,
  database,
  active,
  active_pid,
  pg_size_pretty(
    pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
  ) AS retained_wal,
  pg_size_pretty(
    pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)
  ) AS unconfirmed_wal,
  CASE
    WHEN active = false
    THEN 'INACTIVE &#8212; retaining WAL indefinitely'
    WHEN pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) &gt; 10737418240
    THEN 'CRITICAL &#8212; &gt;10GB retained'
    WHEN pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) &gt; 1073741824
    THEN 'WARNING &#8212; &gt;1GB retained'
    ELSE 'OK'
  END AS status
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC NULLS LAST;
</code></code></pre><p><strong>Flag:</strong> Any slot where <code>active = false</code> investigate immediately. Find the slot owner. Either restart the consumer or drop the slot.</p><p><strong>Flag:</strong> retained_wal in gigabytes on any slot disk fill is a function of your write rate and time, not a fixed threshold.</p><p>All slots active and retained WAL under 500MB healthy.</p><h4>Check 1.2 WAL retention cap</h4><pre><code><code>SELECT name, setting, unit
FROM pg_settings
WHERE name = 'max_slot_wal_keep_size';
</code></code></pre><p><strong>Flag:</strong> <code>setting = -1</code> no cap. A single inactive slot can fill your entire disk. </p><p>Set this immediately: </p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;65a78fe0-c459-4ef7-b0dd-7b18f36a4592&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">ALTER SYSTEM SET max_slot_wal_keep_size = '10GB'; then SELECT pg_reload_conf();</code></pre></div><p><em>Specify the maximum size of WAL files that replication slots are allowed to retain in the pg_wal directory at checkpoint time. If max_slot_wal_keep_size is -1 (the default), replication slots may retain an unlimited amount of WAL files.</em></p><p>Source: <a href="https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-SLOT-WAL-KEEP-SIZE">PostgreSQL: Documentation: 18: 19.6. Replication</a></p><h4>Check 1.3 Physical standby lag</h4><pre><code><code>SELECT
  application_name,
  client_addr,
  state,
  pg_size_pretty(
    pg_wal_lsn_diff(sent_lsn, replay_lsn)
  ) AS total_lag,
  pg_size_pretty(
    pg_wal_lsn_diff(write_lsn, flush_lsn)
  ) AS disk_write_lag,
  pg_size_pretty(
    pg_wal_lsn_diff(flush_lsn, replay_lsn)
  ) AS apply_lag,
  sync_state
FROM pg_stat_replication
ORDER BY pg_wal_lsn_diff(sent_lsn, replay_lsn) DESC;
</code></code></pre><p><strong>Flag:</strong> total_lag above 100MB on a synchronous standby your primary commits are waiting for a slow standby.</p><p><strong>Flag:</strong> No rows returned when you expect standbys standby has disconnected.</p>
      <p>
          <a href="/__u/haiderzdbre.substack.com/p/postgresql-sev-1-prevention-checklist">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[[DBEng] How Stripe Handles 500M+ Database Writes Per Day]]></title><description><![CDATA[Stripe didn&#8217;t use MySQL or PostgreSQL for their core storage layer. They built their own a MongoDB-compatible distributed database called DocDB.]]></description><link>https://haiderzdbre.substack.com/p/stripe-docdb-mongodb-sharding-idempotency-500m-write</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/stripe-docdb-mongodb-sharding-idempotency-500m-write</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Tue, 07 Apr 2026 04:50:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cdua!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3><em>Here&#8217;s the architecture behind it and what every high-write database team can take from it.</em></h3><div><hr></div><h4><strong>Table of Contents</strong></h4><ol><li><p>Why Financial Writes Are a Different Problem</p></li><li><p>What DocDB Is In Stripe&#8217;s Own Words</p></li><li><p>The Core Architecture: Shards, Proxies, and Chunk Metadata</p></li><li><p>The Data Movement Platform: Zero-Downtime Migrations at Scale</p></li><li><p>Idempotency Keys: Exactly-Once Semantics at the API Layer</p></li><li><p>What the Idempotency Pattern Looks Like at the Database Layer</p></li><li><p>What Database Engineers Can Take From This</p></li></ol><div><hr></div><p>&#128232; <strong>If this was useful, share it with one engineer on your team who manages production databases.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share The Sev-1 Database&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share The Sev-1 Database</span></a></p><p><strong>The Sev-1 Database</strong> is a reader-supported publication. Each week I share practical takeaways from real incidents no theory, just what actually breaks and how to fix it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Subscribe now</span></a></p><div><hr></div><p>In 2023, Stripe processed <strong>$1 trillion</strong> in total payments volume. <br>Their database infrastructure a system they built themselves <strong>maintained 99.999% </strong>uptime throughout. This post covers what that infrastructure actually looks like, drawn directly from their engineering blog and API documentation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!cdua!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!cdua!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!cdua!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!cdua!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!cdua!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_webp, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!cdua!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1948890,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://haiderzdbre.substack.com/i/191356029?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!cdua!, /__u/haiderzdbre.substack.com/w_424, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png 424w, /__u/substackcdn.com/image/fetch/$s_!cdua!, /__u/haiderzdbre.substack.com/w_848, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png 848w, /__u/substackcdn.com/image/fetch/$s_!cdua!, /__u/haiderzdbre.substack.com/w_1272, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png 1272w, /__u/substackcdn.com/image/fetch/$s_!cdua!, /__u/haiderzdbre.substack.com/w_1456, /__u/haiderzdbre.substack.com/c_limit, /__u/haiderzdbre.substack.com/f_auto, /__u/haiderzdbre.substack.com/q_auto:good, /__u/haiderzdbre.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F222818ad-6552-4a4f-a05b-4d716042af27_1024x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h2>Why Financial Writes Are a Different Problem</h2><p>Most high-scale systems accept some degree of eventual consistency. Stripe cannot. A payment either happened or it didn&#8217;t. Duplicate charges are a catastrophic failure mode, not an edge case.</p><p>Two hard requirements shaped every architectural decision:</p><ul><li><p><strong>Every write must be durable.</strong> A payment that appears to succeed but isn&#8217;t committed is a financial integrity failure.</p></li><li><p><strong>Every write must be idempotent.</strong> At scale, network retries are inevitable. The same request will arrive more than once. The system must handle this without creating duplicates.</p></li></ul><p>Standard databases handle the first constraint. The second required a deliberate design decision at both the API and storage layers.</p><div><hr></div><h2>What DocDB Is In Stripe&#8217;s Own Words</h2><p>From Stripe&#8217;s engineering blog (June 2024):</p><blockquote><p><em><strong>Stripe&#8217;s DocDB is an extension of MongoDB Community a popular open-source database and consists of a set of services that we built in-house. It serves over five million queries per second from Stripe&#8217;s product applications.</strong></em></p></blockquote><p>And on why they built it rather than using an existing service:</p><blockquote><p><em><strong>We chose to build DocDB on top of MongoDB Community because of the flexibility of its document model and its ability to handle massive volumes of real-time data at scale. MongoDB Atlas didn&#8217;t exist in 2011, so we built a self-managed cluster of MongoDB instances running in the cloud.</strong></em></p></blockquote><p>The key numbers from the same post:</p><ul><li><p><strong>5 million queries per second</strong></p></li><li><p><strong>10,000+ distinct query shapes</strong></p></li><li><p><strong>Petabytes of financial data</strong></p></li><li><p><strong>5,000+ collections across 2,000+ database shards</strong></p></li></ul><p>This is not MySQL. It is not PostgreSQL. It is not MongoDB itself. It is a custom database-as-a-service layer built on top of MongoDB Community&#8217;s storage engine, with Stripe&#8217;s own proxy layer, replication system, and migration infrastructure on top.</p><div><hr></div><p>&#128640; <strong>PostgreSQL Health Report</strong> &#8212; One SQL file. 60+ diagnostic checks. Detects vacuum lag, bloat, lock chains, replication slot danger, unindexed FK columns, and more.</p><p><strong><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></strong></p><div><hr></div><h2>Core Architecture: Shards, Proxies, and Chunk Metadata</h2><p>Stripe describes their architecture this way in the blog post:</p><blockquote><p><em>Thousands of database shards, each housing a small chunk of the cumulative data, now underlie all of Stripe&#8217;s products. When an application sends a query to a database proxy server, it parses the query, routes it to one or more shards, combines the results from the shards, and returns them back to the application.</em></p></blockquote><p>The routing layer works through a chunk metadata service:</p><blockquote><p><em>They rely on a chunk metadata service that maps chunks to database shards, making it easy to look up the relevant shards for a given query.</em></p></blockquote><p>Each physical database shard is deployed as a replica set primary node plus multiple secondary nodes with replication and automated failover. <strong>Product applications at Stripe never connect directly to database shards.</strong> They go through a fleet of database proxy servers that Stripe built internally in Go.</p><p>The proxy layer handles: reliability, scalability, admission control, and access control. It enforces query shape restrictions what Stripe <em>calls a minimal set of database functions to avert self-inflicted issues due to suboptimal queries from client applications.</em></p><p>That last point is worth pausing on. Stripe&#8217;s proxy actively rejects queries that violate their access patterns. They do not give application teams open access to the database. This is an explicit architectural constraint, not just a convention.</p><div><hr></div><h2>Data Movement Platform: Zero-Downtime Migrations at Scale</h2><p>The most technically detailed part of Stripe&#8217;s blog post covers their Data Movement Platform the system that allows them to split, merge, and migrate database shards with zero downtime.</p><p>From the post:</p><blockquote><p><em>The Data Movement Platform enabled our transition from running a small number of database shards (each with tens of terabytes of data) to thousands of database shards (each with a fraction of the original data). It also provides client-transparent migrations with zero downtime, which makes it possible to build a highly elastic DBaaS offering.</em></p></blockquote><p>In 2023 alone they used this to bin-pack underutilized databases:</p><p>From the post:</p><blockquote><p><em>We bin-packed thousands of underutilized databases by migrating 1.5 petabytes of data transparent to product applications, and reduced the total number of underlying DocDB shards by approximately three quarters.</em></p></blockquote><p>The traffic switch at the core of a shard migration takes under two seconds:</p><blockquote><p><em>The entire traffic switch protocol takes less than two seconds to execute, and all failed reads and writes directed to the source shard succeed on retries.</em></p></blockquote><p>Migration sequence has six steps: chunk registration, bulk data import, async replication from source to target, correctness check via point-in-time snapshots, traffic switch, and deregistration. <br><br>Correctness check compares snapshots rather than doing a live row-by-row comparison a deliberate choice to avoid impacting shard throughput during the verification step.</p><p>One specific optimization they mention that improved bulk load performance significantly:</p><blockquote><p><em>By sorting the data based on the most common index attributes in the collections and inserting it in sorted order, we significantly enhanced the proximity of writes boosting write throughput by 10x.</em></p></blockquote><div><hr></div><h2>Idempotency Keys: Exactly-Once Semantics at the API Layer</h2><p>Second design pattern this post covers comes from a separate Stripe engineering post on idempotency (2017) and their official API documentation.</p><p>Stripe&#8217;s framing of the problem:</p><blockquote><p><em>To overcome this sort of inherently unreliable environment, it&#8217;s important to design APIs and clients that will be robust in the event of failure, and will predictably bring a complex integration to a consistent state despite them.</em></p></blockquote><p>Their definition of how idempotency keys work at the API layer, directly from their docs:</p><blockquote><p><em>Stripe&#8217;s idempotency works by saving the resulting status code and body of the first request made for any given idempotency key, regardless of whether it succeeds or fails. Subsequent requests with the same key return the same result, including 500 errors.</em></p></blockquote><p>And on key generation:</p><blockquote><p><em>A client generates an idempotency key, which is a unique key that the server uses to recognize subsequent retries of the same request. How you create unique keys is up to you, but we suggest using V4 UUIDs, or another random string with enough entropy to avoid collisions.</em></p></blockquote><p>Key expiration, from their docs:</p><blockquote><p><em>You can remove keys from the system automatically after they&#8217;re at least 24 hours old. We generate a new request if a key is reused after the original is pruned.</em></p></blockquote><p>Their three-principle summary from the idempotency blog post:</p><blockquote><p><em>Make sure that failures are handled consistently. Have clients retry operations against remote services. Make sure that failures are handled safely. Use idempotency and idempotency keys to allow clients to pass a unique value and retry requests as needed. Make sure that failures are handled responsibly. Use techniques like exponential backoff and random jitter.</em></p></blockquote><div><hr></div><h2>What the Idempotency Pattern Looks Like at the Database Layer</h2><p>Stripe&#8217;s blog and docs describe the pattern at the API level. The database implementation for PostgreSQL or MySQL that makes this work is a unique constraint on the idempotency key column. This is standard SQL not Stripe-specific but it&#8217;s the mechanism that enforces exactly-once semantics at the storage layer.</p><pre><code><code>-- PostgreSQL: table design that supports idempotency
CREATE TABLE payments (
  id               BIGSERIAL PRIMARY KEY,
  idempotency_key  VARCHAR(255) NOT NULL,
  customer_id      BIGINT NOT NULL,
  amount_cents     BIGINT NOT NULL,
  status           TEXT NOT NULL DEFAULT 'pending',
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- The constraint that enforces exactly-once semantics
CREATE UNIQUE INDEX idx_payments_idempotency_key
  ON payments (idempotency_key);
</code></code></pre><pre><code><code>-- Application insert &#8212; fails cleanly on duplicate key
INSERT INTO payments (idempotency_key, customer_id, amount_cents, status)
VALUES ($1, $2, $3, 'pending')
ON CONFLICT (idempotency_key) DO NOTHING
RETURNING *;

-- If ON CONFLICT returns nothing:
-- &#8594; record already exists, fetch and return it
-- &#8594; no duplicate charge created
</code></code></pre><pre><code><code>-- MySQL equivalent
INSERT INTO payments (idempotency_key, customer_id, amount_cents, status)
VALUES (?, ?, ?, 'pending')
ON DUPLICATE KEY UPDATE idempotency_key = idempotency_key;
-- affected rows = 0 means duplicate &#8212; application fetches existing record
</code></code></pre><p>This pattern converts at-least-once delivery at the network layer into exactly-once semantics at the database layer without distributed transactions, without application-level deduplication that fails under retry pressure.</p><div><hr></div><h2>What Database Engineers Can Take From This</h2><p><strong>Building a custom database is not the takeaway.</strong> That decision was made in 2011 when MongoDB Atlas didn&#8217;t exist and Stripe had specific requirements no off-the-shelf tool satisfied. It required years of infrastructure investment. It is not a pattern to copy.</p><p><strong>The proxy layer pattern is underused.</strong> Stripe routes all application traffic through a proxy that enforces query shape, admission control, and access control. Most teams allow applications to connect directly to the database. </p><p>Result: suboptimal queries, missing indexes, and connection pool exhaustion incidents that a proxy layer would have caught earlier.</p><p><strong>Design for shard migration before you need it.</strong> Stripe&#8217;s Data Movement Platform exists because they needed to split shards under load without downtime. If your data has a natural tenant dimension and you haven&#8217;t modeled it that way from the start, the eventual migration will be significantly more expensive than building for it early.</p><p><strong>Idempotency keys at the database layer are underused.</strong> Any table that receives writes over a network is vulnerable to duplicate writes on retry. A unique constraint on an operation identifier is a one-time addition. It prevents an entire class of data integrity incidents. The Stripe docs describe this as a fundamental reliability primitive, not an advanced feature.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><div><hr></div><h4><strong>Key Takeaway</strong></h4><p>Stripe&#8217;s core storage layer is DocDB built on MongoDB Community in 2011 because MongoDB Atlas didn&#8217;t exist yet and no off-the-shelf DBaaS met their requirements. </p><p>It now serves 5M queries/second across 2,000+ shards. The two patterns with direct applicability to every database team: a proxy layer that enforces query constraints before they reach the database, and idempotency keys at the database layer on any table that receives writes over a network. Both are design decisions, not scale decisions.</p><div><hr></div><p></p><h3><em>&#128231; Want the complete PostgreSQL Health Report?</em></h3><p>Includes MultiXact checks, vacuum monitoring, checkpoint diagnostics, WAL slot health, and 60+ diagnostic queries &#8212; all in one SQL file.</p><p><strong><a href="https://haiderdba.gumroad.com/l/ccget">Get it here &#8594; $29</a></strong></p><p>&#128204; <strong>Upgrade to Premium ($8/month)</strong> &#8212; Premium subscribers get the full SQL diagnostic packs, step-by-step playbooks, and the queries I actually run during a Sev-1.</p><p><strong><a href="/__u/haiderzdbre.substack.com/subscribe">Upgrade to Paid</a></strong></p><p><strong>Sources &#8212; everything in this post is drawn directly from:</strong><br><br>&#8594; <a href="https://stripe.com/blog/how-stripes-document-databases-supported-99.999-uptime-with-zero-downtime-data-migrations">stripe.com/blog &#8212; How Stripe&#8217;s document databases supported 99.999% uptime with zero-downtime data migrations (June 2024)</a><br><br>&#8594; <a href="https://stripe.com/blog/idempotency">stripe.com/blog &#8212; Designing robust and predictable APIs with idempotency (2017)</a><br><br>&#8594; <a href="https://docs.stripe.com/api/idempotent_requests">docs.stripe.com &#8212; Idempotent requests (official API docs)</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/stripe-docdb-mongodb-sharding-idempotency-500m-write?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/stripe-docdb-mongodb-sharding-idempotency-500m-write?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/p/stripe-docdb-mongodb-sharding-idempotency-500m-write/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/p/stripe-docdb-mongodb-sharding-idempotency-500m-write/comments"><span>Leave a comment</span></a></p>]]></content:encoded></item><item><title><![CDATA[Unlock Full Sev1 Database Premium Access 🚀]]></title><description><![CDATA[Hey, You&#8217;ve seen the free insights.]]></description><link>https://haiderzdbre.substack.com/p/unlock-full-sev1-database-premium</link><guid isPermaLink="false">https://haiderzdbre.substack.com/p/unlock-full-sev1-database-premium</guid><dc:creator><![CDATA[Haider Z @ Microsoft]]></dc:creator><pubDate>Sat, 04 Apr 2026 06:06:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7Pgn!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F30ab7b47-3853-4472-a161-41d35e82e7b6_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey,</p><p>You&#8217;ve seen the free insights. Now imagine having <strong>full, unrestricted access</strong> to the <strong>Sev1 Database Premium Newsletter</strong> the same insights that serious database professionals rely on to stay ahead.</p><p>&#128161; <strong>Premium Membership :</strong></p><ul><li><p>Monthly: <strong>$7.99/month</strong></p></li><li><p>Annual: <strong>$80/year not <s>96</s>$ (early supporter rate) </strong></p></li></ul><p>This isn&#8217;t a trial. It&#8217;s the <strong>real deal</strong>, giving you:</p><ul><li><p>Exclusive database insights</p></li><li><p>Premium-only content delivered</p></li><li><p>RCA with actionable scrips + queries + explanation</p></li><li><p>Ask me anything over the Email</p></li></ul><p>Premium Content:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/t/paid&quot;,&quot;text&quot;:&quot;PREMIUM Content&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/t/paid"><span>PREMIUM Content</span></a></p><p>Thousands of free readers rely on these insights. Now it&#8217;s your chance to <strong>step up to full access</strong> and stay ahead of the curve.</p><p>&#128073; <strong>Join today and unlock your premium membership.</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe&quot;,&quot;text&quot;:&quot;Upgrade to PREMIUM&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/haiderzdbre.substack.com/subscribe"><span>Upgrade to PREMIUM</span></a></p><p><br>Full access is ready all you need to do is start.</p><p>See you inside,<br>Haider</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://haiderzdbre.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Sev-1 Database is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>