<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>The WAL Street Journal</title>
<link>https://thewalstreetjournal.github.io/</link>
<atom:link href="https://thewalstreetjournal.github.io/index.xml" rel="self" type="application/rss+xml"/>
<description>Database internals, read from the source code.</description>
<generator>quarto-1.8.26</generator>
<lastBuildDate>Tue, 06 Oct 2026 18:30:00 GMT</lastBuildDate>
<item>
  <title>How ClickHouse Keeper Takes Non-Blocking Snapshots</title>
  <link>https://thewalstreetjournal.github.io/posts/clickhouse-keeper-non-blocking-snapshots/</link>
  <description><![CDATA[ 






<p>Let’s say we have an in-memory database, and it’s serving thousands of writes per second. How do we save a consistent snapshot of it to disk, without locking the whole database while we do it?</p>
<p>Redis famously solves this at the OS level, using the <code>fork()</code> system call and copy-on-write virtual memory pages. ClickHouse Keeper does it completely in user space. It takes non-blocking snapshots using two simple data structures: a doubly linked list and a hash map. In this post, we’ll look at how that works, and then find it in the ClickHouse source code.</p>
<p>Prefer video? Here’s the same walkthrough:</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/7J93WKEFinA" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<hr>
<section id="why-the-simple-approaches-dont-work" class="level3">
<h3 class="anchored" data-anchor-id="why-the-simple-approaches-dont-work">Why the simple approaches don’t work</h3>
<p>There are two brute-force ways to take a snapshot, and neither really works for us:</p>
<ol type="1">
<li><strong>Hold a global lock while writing to disk.</strong> Every client write waits until the snapshot is done, which could take seconds.</li>
<li><strong>Deep-copy the whole map in memory</strong>, and let a background thread write the copy to disk. Now we’re using double the memory. And usually only a small fraction of the keys change while the snapshot is being written, so most of that copying is wasted.</li>
</ol>
<p>So what ClickHouse Keeper needs is a data structure with three properties:</p>
<ol type="1">
<li>Constant-time lookups, for the clients reading the data.</li>
<li>No locking, or if that’s not possible, locking only for a very short time.</li>
<li>Copy-on-write only for the keys that change. That way, the extra memory we use is proportional to the number of writes during the snapshot.</li>
</ol>
<hr>
</section>
<section id="why-not-a-plain-hash-map" class="level3">
<h3 class="anchored" data-anchor-id="why-not-a-plain-hash-map">Why not a plain hash map?</h3>
<p>The obvious question is: why can’t we do this with a regular hash map, like <code>std::unordered_map</code> in C++?</p>
<p>The problem is that as writes keep arriving during a snapshot, the hash map will eventually need to grow and <strong>rehash</strong>. Rehashing moves keys into different buckets, so the order of the keys gets scrambled. If the snapshot thread is walking through the buckets while that happens, it can miss some keys and write others twice.</p>
<p>We need a structure that gives us constant-time lookups, but also a stable, predictable order to walk through.</p>
<hr>
</section>
<section id="a-hash-map-and-a-doubly-linked-list" class="level3">
<h3 class="anchored" data-anchor-id="a-hash-map-and-a-doubly-linked-list">A hash map and a doubly linked list</h3>
<p>To get both, we can combine two data structures we already know:</p>
<ol type="1">
<li>A <strong>hash map</strong> for constant-time lookups.</li>
<li>A <strong>doubly linked list</strong> that keeps a stable insertion order.</li>
</ol>
<p>The key-value data actually lives in the nodes of the linked list. The hash map stores the keys, and each key’s value is a pointer to that key’s node in the list.</p>
<p><img src="https://thewalstreetjournal.github.io/posts/clickhouse-keeper-non-blocking-snapshots/hashmap-and-list.png" class="img-fluid" alt="A hash map with keys alice, bob and carol, each pointing to a node in a doubly linked list holding 30, 25 and 35."></p>
<p>Let’s look at how this behaves in two cases: during normal operation, when no snapshot is running, and while a snapshot is being taken.</p>
<section id="normal-operation" class="level4">
<h4 class="anchored" data-anchor-id="normal-operation">Normal operation</h4>
<p>When there’s no snapshot running, everything is straightforward, and every operation is O(1):</p>
<ul>
<li><strong>Insert <code>dave</code>:</strong> look up <code>dave</code> in the hash map. It isn’t there, so append a new node at the tail of the list, and store a pointer to it in the hash map.</li>
<li><strong>Update <code>dave</code> to 45:</strong> look up <code>dave</code>, follow the pointer to its node, and change the value in place.</li>
<li><strong>Delete <code>bob</code>:</strong> look up <code>bob</code>, unlink its node from the list, and remove the key from the hash map.</li>
</ul>
</section>
<section id="during-a-snapshot" class="level4">
<h4 class="anchored" data-anchor-id="during-a-snapshot">During a snapshot</h4>
<p>Here’s the idea. The snapshot thread starts at the head of the list and walks it node by node, writing each node to disk, without holding any lock. At the moment the snapshot starts, the list has some fixed number of nodes. Let’s call it <strong>N</strong>. We can think of this as a <strong>cut-off line</strong>.</p>
<p>If we can guarantee that every write that arrives after the snapshot starts only ever touches the list <em>after</em> this cut-off line, the snapshot thread can safely walk the first N nodes while writes carry on.</p>
<p>To start a snapshot, we record N and turn on a “snapshot mode” flag. Both must happen together, so we take a lock for that tiny moment, record the two values, and release it. Reads and writes are blocked only for those few instructions.</p>
<p><img src="https://thewalstreetjournal.github.io/posts/clickhouse-keeper-non-blocking-snapshots/snapshot-cutoff.png" class="img-fluid" alt="Snapshot mode is on. A cut-off line is drawn after the third node, N = 3. New writes go after the line."></p>
<p>Now the operations behave like this:</p>
<ul>
<li><strong>Insert:</strong> nothing changes. New nodes are always appended at the tail, which is after the cut-off line anyway.</li>
<li><strong>Update:</strong> if the node is <em>before</em> the cut-off line, we can’t change it, because the snapshot thread may not have written it yet. So we mark the old node as <strong>inactive</strong>, append a copy with the new value at the tail, and point the hash map at the copy. That’s the copy-on-write, and readers see the new value straight away. If the same key is updated again, its node is now after the cut-off line, so it’s simply changed in place.</li>
</ul>
<p><img src="https://thewalstreetjournal.github.io/posts/clickhouse-keeper-non-blocking-snapshots/snapshot-update-cow.png" class="img-fluid" alt="update('bob', 27) during a snapshot: bob's old node 25 is marked inactive and stays before the cut-off line, and bob now points to a new node 27 after the line."></p>
<ul>
<li><strong>Delete:</strong> we remove the key from the hash map, but we leave its node in the list, marked as inactive. If it’s before the cut-off line, the snapshot thread still has to write it.</li>
</ul>
<p><img src="https://thewalstreetjournal.github.io/posts/clickhouse-keeper-non-blocking-snapshots/snapshot-delete.png" class="img-fluid" alt="delete('carol') during a snapshot: carol is removed from the hash map, but her node 35 stays in the list marked inactive."></p>
<p>When the snapshot has written all N nodes, there’s a quick cleanup. We take a brief lock, go through only the nodes marked inactive, remove them from the list, and turn snapshot mode off.</p>
<p><img src="https://thewalstreetjournal.github.io/posts/clickhouse-keeper-non-blocking-snapshots/after-snapshot.png" class="img-fluid" alt="After the snapshot: the inactive nodes are removed, leaving 30, 40 and 27 in the list."></p>
<p>That’s the whole design. Writes after the cut-off line are invisible to the snapshot, and only the keys that change get copied. Lookups stay O(1), the lock is held only for a moment at the start and the end, and the extra memory is proportional to the writes during the snapshot. It hits every item on the wishlist.</p>
<p>I find it pretty cool how two simple data structures, put together, solve what looks like a hard problem.</p>
<hr>
</section>
</section>
<section id="in-the-source-code" class="level3">
<h3 class="anchored" data-anchor-id="in-the-source-code">In the source code</h3>
<p><a href="https://github.com/ClickHouse/ClickHouse">ClickHouse</a> is an open-source OLAP database. It has its own implementation of ZooKeeper, called ClickHouse Keeper, and that’s what we’ve been looking at. The code below is from the <a href="https://github.com/ClickHouse/ClickHouse/tree/v26.2.1.1139-stable/src/Coordination">v26.2.1.1139-stable</a> release.</p>
<section id="the-two-structures" class="level4">
<h4 class="anchored" data-anchor-id="the-two-structures">The two structures</h4>
<p>Everything lives in <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/SnapshotableHashTable.h#L86-L95"><code>SnapshotableHashTable.h</code></a>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">using</span> List <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>list<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>ListElem<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;;</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">using</span> Mapped <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">typename</span> List<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>iterator<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">using</span> IndexMap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> HashMap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>string_view<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> Mapped<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;;</span></span>
<span id="cb1-4"></span>
<span id="cb1-5">List list<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-6">IndexMap map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-7"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span> snapshot_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb1-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// Allows to avoid additional copies in updateValue function</span></span>
<span id="cb1-9"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">size_t</span> current_version<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb1-10"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">size_t</span> snapshot_up_to_version<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span></code></pre></div></div>
<p>There are the two structures: a <code>std::list</code>, which is a doubly linked list, and a hash map whose values are iterators into that list. Then there’s the snapshot mode flag, and two version numbers.</p>
<p>Each node in the list is a <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/SnapshotableHashTable.h#L15-L25"><code>ListNode</code></a>. It holds the key, the value, and some metadata packed into a single 64-bit field:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span></span>
<span id="cb2-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">uint64_t</span> active_in_map <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">uint64_t</span> free_key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">uint64_t</span> version <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">62</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> node_metadata<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span></code></pre></div></div>
<p><code>active_in_map</code> is the active/inactive flag from earlier. <code>version</code> is how ClickHouse knows which side of the cut-off line a node is on. Every node records the table’s <code>current_version</code> at the time it was written. When a snapshot starts, the current version becomes <code>snapshot_up_to_version</code>, and <code>current_version</code> is bumped by one ( <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/SnapshotableHashTable.h#L388-L393"><code>enableSnapshotMode</code></a> ). So any node with <code>version &lt;= snapshot_up_to_version</code> is before the cut-off line, and anything written after the snapshot started is after it.</p>
</section>
<section id="starting-a-snapshot-the-short-lock" class="level4">
<h4 class="anchored" data-anchor-id="starting-a-snapshot-the-short-lock">Starting a snapshot: the short lock</h4>
<p>The snapshot starts in <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/KeeperStateMachine.cpp#L823-L826"><code>KeeperStateMachine.cpp</code></a>, the Raft state machine. The comment says exactly what we described:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb3-1"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// lock storage for a short period time to turn on "snapshot mode". After that we can read consistent storage state without locking.</span></span>
<span id="cb3-2">    LockGuardWithStats<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> lock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>storage_mutex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb3-3">    snapshot_task<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>snapshot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>make_shared<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>KeeperStorageSnapshot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>Storage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;&gt;(</span>storage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span> snapshot_meta_copy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> getClusterConfig<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">());</span></span>
<span id="cb3-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>The lock is scoped, so it’s released as soon as the block ends. Inside, the snapshot object is created. Its constructor, in <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/KeeperSnapshotManager.cpp#L615-L618"><code>KeeperSnapshotManager.cpp</code></a>, records N and turns on snapshot mode:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ver<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> storage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>container<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>snapshotSizeWithVersion<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">();</span></span>
<span id="cb4-2">snapshot_container_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// this is N</span></span>
<span id="cb4-3">storage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>enableSnapshotMode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>ver<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span>        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// the flag, and the cut-off version</span></span>
<span id="cb4-4">begin <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> storage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>getSnapshotIteratorBegin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">();</span></span></code></pre></div></div>
</section>
<section id="writes-during-the-snapshot" class="level4">
<h4 class="anchored" data-anchor-id="writes-during-the-snapshot">Writes during the snapshot</h4>
<p>Back in <code>SnapshotableHashTable.h</code>, <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/SnapshotableHashTable.h#L210"><code>insert</code></a> doesn’t care about snapshot mode at all. It just appends to the list and adds the key to the map.</p>
<p><a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/SnapshotableHashTable.h#L297-L343"><code>updateValue</code></a> is where the copy-on-write happens:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb5-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>snapshot_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// We in snapshot mode but updating some node which is already more</span></span>
<span id="cb5-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// fresh than snapshot distance. So it will not participate in</span></span>
<span id="cb5-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// snapshot and we don't need to copy it.</span></span>
<span id="cb5-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>list_itr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>getVersion<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> snapshot_up_to_version<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-7">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-8">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> elem_copy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> list_itr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>copyFromSnapshotNode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">();</span></span>
<span id="cb5-9">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb5-10">        list_itr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>setInactiveInMap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">();</span></span>
<span id="cb5-11">        snapshot_invalid_iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>push_back<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>list_itr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-12">        updater<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>elem_copy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-13"></span>
<span id="cb5-14">        elem_copy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>setVersion<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>current_version<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-15">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> itr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> list<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>insert<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>list<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>end<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">std::</span>move<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>elem_copy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">));</span></span>
<span id="cb5-16">        it<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>getMapped<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> itr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-17">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb5-18">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span></span>
<span id="cb5-20">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-21">        updater<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>list_itr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb5-22">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb5-23">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-24"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>If the node is before the cut-off line, it’s marked inactive, a copy with the new value is appended at the end, and the map is pointed at the copy. Otherwise, it’s updated in place.</p>
<p><a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/SnapshotableHashTable.h#L266-L290"><code>erase</code></a> follows the same pattern. In snapshot mode, it marks the node inactive and removes the key from the map, but it does not remove the node from the list. Outside snapshot mode, it removes both.</p>
<p>Notice <code>snapshot_invalid_iters</code>. Every node that becomes inactive is added to this list, so that the cleanup doesn’t have to walk the whole linked list to find them.</p>
</section>
<section id="writing-the-snapshot-and-cleaning-up" class="level4">
<h4 class="anchored" data-anchor-id="writing-the-snapshot-and-cleaning-up">Writing the snapshot and cleaning up</h4>
<p>The snapshot itself is written by a background task, created right after the short lock in <code>KeeperStateMachine.cpp</code>. Depending on where the snapshot is going, it calls <code>serializeSnapshotToDisk</code> or <code>serializeSnapshotBufferToDisk</code>. Both end up in <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/KeeperSnapshotManager.cpp#L260-L264"><code>serialize</code></a>, which walks exactly N nodes from the start of the list:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb6-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">auto</span> it <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> snapshot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>begin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> counter <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> snapshot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>snapshot_container_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">++</span>counter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<p>When it’s done, the cleanup takes the storage lock again, very briefly ( <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/KeeperStateMachine.cpp#L888-L894">L888–L894</a> ):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode cpp code-with-copy"><code class="sourceCode cpp"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// Destroy snapshot with lock</span></span>
<span id="cb7-2">LockGuardWithStats<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> lock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>storage_mutex<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb7-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb7-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// Turn off "snapshot mode" and clear outdate part of storage state</span></span>
<span id="cb7-5">storage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>clearGarbageAfterSnapshot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">();</span></span>
<span id="cb7-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb7-7">snapshot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>reset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">();</span></span></code></pre></div></div>
<p><code>clearGarbageAfterSnapshot</code> calls <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/SnapshotableHashTable.h#L364-L376"><code>clearOutdatedNodes</code></a>, which goes through <code>snapshot_invalid_iters</code> and erases those nodes from the list. Resetting the snapshot runs its <a href="https://github.com/ClickHouse/ClickHouse/blob/v26.2.1.1139-stable/src/Coordination/KeeperSnapshotManager.cpp#L624-L628">destructor</a>, which turns snapshot mode off.</p>
<hr>
</section>
</section>
<section id="summary" class="level3">
<h3 class="anchored" data-anchor-id="summary">Summary</h3>
<p>We wanted to snapshot a large in-memory hash map to disk without blocking writes. ClickHouse Keeper does it with two simple data structures: a hash map for constant-time lookups, and a doubly linked list where the data actually lives.</p>
<p>When a snapshot starts, it marks a cut-off line in the list. After that, new and updated values go after the line, and deleted or replaced nodes stay in the list, marked inactive. The snapshot thread writes the first N nodes without blocking anyone, and a quick cleanup at the end removes the inactive nodes.</p>
<hr>
</section>
<section id="credits-and-resources" class="level3">
<h3 class="anchored" data-anchor-id="credits-and-resources">Credits and Resources</h3>
<p>For this post, I have heavily used Claude Code. Along with that, I have relied on the following resources:</p>
<ol type="1">
<li><a href="https://clickhouse.com/docs/guides/sre/keeper/clickhouse-keeper">ClickHouse Keeper</a>, ClickHouse docs</li>
<li><a href="https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/">Redis persistence</a>, for how Redis uses <code>fork()</code> for snapshots</li>
<li><a href="https://github.com/ClickHouse/ClickHouse/tree/v26.2.1.1139-stable/src/Coordination">ClickHouse source code, v26.2.1.1139-stable</a></li>
</ol>
<hr>


</section>

 ]]></description>
  <category>clickhouse</category>
  <category>internals</category>
  <guid>https://thewalstreetjournal.github.io/posts/clickhouse-keeper-non-blocking-snapshots/</guid>
  <pubDate>Tue, 06 Oct 2026 18:30:00 GMT</pubDate>
  <media:content url="https://thewalstreetjournal.github.io/posts/clickhouse-keeper-non-blocking-snapshots/snapshot-update-cow.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>What’s Inside the Postgres Write-Ahead Log</title>
  <link>https://thewalstreetjournal.github.io/posts/inside-the-postgres-wal/</link>
  <description><![CDATA[ 






<p>We all know that Postgres has a write-ahead log ( WAL ) for recovery and replication. We probably also know how to configure it, or how to set up replication with it. But we rarely look at what the WAL is actually made of, or how a record gets written into it.</p>
<p>So in this post, we’ll do that. We’ll insert a single row, find its WAL record on disk, and then go through the Postgres source to see who wrote that record and how it gets replayed. It’ll be easier to follow if you know how Postgres organizes its storage. I covered that in an <a href="../../posts/how-postgres-stores-data/index.html">earlier post</a>.</p>
<p>Prefer video? Here’s the same walkthrough:</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/YOyq-kvbyU8" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<hr>
<section id="how-the-wal-is-laid-out" class="level3">
<h3 class="anchored" data-anchor-id="how-the-wal-is-laid-out">How the WAL is laid out</h3>
<p>A write-ahead log is an infinite, append-only stream of records. For the tables we normally use, every change is recorded in the WAL: an insert, an update, a change to an index. Each of them produces a WAL record. And once a record is logged, it is immutable.</p>
<p>But we can’t write an infinite stream to disk as a single file. So Postgres breaks the stream into chunks, and each chunk is a file on disk. These chunks are called <strong>segments</strong>, and each segment is 16MB by default.</p>
<p>Each segment is again split into <strong>pages</strong> of 8KB each. Every page starts with some headers ( things like the timeline ID and some flags ), followed by the actual WAL records.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://thewalstreetjournal.github.io/posts/inside-the-postgres-wal/wal-segments-pages.png" class="img-fluid figure-img" alt="Top: the WAL as an infinite stream of records such as insert, btree, commit and update. Middle: the same records split into 16MB segment files named 000000010...A1 to A4, the last one still filling. Bottom: one segment file split into 8KB pages, each with headers followed by records."></p>
<figcaption>wal-stream</figcaption>
</figure>
</div>
<p>Two small details here. The first page of a segment has a few more headers than the rest: the system identifier, the segment size, and the WAL page size. And a WAL record can cross a page boundary. So every page header has a flag that tells Postgres whether the start of this page is a continuation of a record from the previous page.</p>
<p>Each record also gets a <strong>Log Sequence Number</strong> ( LSN ). It’s a 64-bit number that keeps increasing and is unique for every record. We don’t need to worry about how Postgres generates it. What matters for us is that the LSN tells us where a record lives. If we know the LSN of an INSERT record, we can find that record and read its details. Without the LSN, finding it is possible, but quite hard.</p>
<p>So let’s find one.</p>
<hr>
</section>
<section id="prerequisites" class="level3">
<h3 class="anchored" data-anchor-id="prerequisites">Prerequisites</h3>
<p>I’m using the same postgres 17.6 container as the earlier posts. The Dockerfile is on <a href="https://github.com/thewalstreetjournal/postgres-playlist/tree/main/004-decoding-wal-in-postgres">GitHub</a>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-t</span> postgres-17.6:v1 .</span>
<span id="cb1-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> network create pgnet</span>
<span id="cb1-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--name</span> postgres <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--network</span> pgnet <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span> POSTGRES_PASSWORD=secret postgres-17.6:v1</span></code></pre></div></div>
<p>I keep two terminals open: one running <code>psql</code>, and one with a shell inside the container for looking at the files.</p>
<hr>
</section>
<section id="finding-our-insert-in-the-wal" class="level3">
<h3 class="anchored" data-anchor-id="finding-our-insert-in-the-wal">Finding our INSERT in the WAL</h3>
<p>This is a freshly created container, so nothing has happened in it yet. But Postgres has already set up its WAL and has a current LSN. We can get it with <code>pg_current_wal_lsn()</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> pg_current_wal_lsn() <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> lsn_before_any_activity \gset</span>
<span id="cb2-2">\echo LSN <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">before</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">any</span> activity: <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:lsn_before_any_activity</span></span></code></pre></div></div>
<pre><code>LSN before any activity: 0/195A318</code></pre>
<p><code>\gset</code> stores the result in a psql variable so we can reuse it later. It also hides the output, which is why there’s an <code>\echo</code> after it.</p>
<p>Now let’s create a table and save the LSN again. Then insert one row and save it once more:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">CREATE</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">TABLE</span> users (<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">id</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, name text, email text);</span>
<span id="cb4-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> pg_current_wal_lsn() <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> lsn_after_creating_table \gset</span>
<span id="cb4-3">\echo LSN <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">after</span> creating <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">table</span>: <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:lsn_after_creating_table</span></span>
<span id="cb4-4"></span>
<span id="cb4-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">INSERT</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">INTO</span> users <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">VALUES</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Alice'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'alice@example.com'</span>);</span>
<span id="cb4-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> pg_current_wal_lsn() <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> lsn_after_inserting_a_tuple \gset</span>
<span id="cb4-7">\echo LSN <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">after</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">INSERT</span>: <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:lsn_after_inserting_a_tuple</span></span></code></pre></div></div>
<pre><code>LSN after creating table: 0/19773B0
LSN after INSERT: 0/1977468</code></pre>
<p>So we now have three LSNs: one before the <code>CREATE TABLE</code>, one between the <code>CREATE TABLE</code> and the <code>INSERT</code>, and one after the <code>INSERT</code>. The records we’re interested in are somewhere between them.</p>
<p>Everything Postgres stores lives in its data directory:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb6-1">SHOW data_directory;</span></code></pre></div></div>
<pre><code>      data_directory
--------------------------
 /var/lib/postgresql/data</code></pre>
<p>In the other terminal, the WAL is in the <code>pg_wal</code> directory inside it:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb8-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> exec <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-it</span> postgres bash</span>
<span id="cb8-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> /var/lib/postgresql/data/pg_wal</span>
<span id="cb8-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span></span></code></pre></div></div>
<pre><code>000000010000000000000001  archive_status  summaries</code></pre>
<p>There’s only one segment file, since this is a fresh instance. A busy system will have many more. To find out which segment an LSN belongs to, and where inside that file it is, Postgres has <code>pg_walfile_name_offset</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> pg_walfile_name_offset(:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'lsn_before_any_activity'</span>);</span></code></pre></div></div>
<pre><code>       pg_walfile_name_offset
------------------------------------
 (000000010000000000000001,9806616)</code></pre>
<p>It gives us the segment file’s name and the byte offset inside it. Doing the same for the other two:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> pg_walfile_name_offset(:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'lsn_after_creating_table'</span>);</span>
<span id="cb12-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> pg_walfile_name_offset(:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'lsn_after_inserting_a_tuple'</span>);</span></code></pre></div></div>
<pre><code> (000000010000000000000001,9925552)
 (000000010000000000000001,9925736)</code></pre>
<p>How does it compute this? The LSN itself carries this information. With 16MB segments, the upper bits of the LSN give the segment number, and the lower 24 bits are the byte offset inside that segment. For example, <code>0/19773B0</code> is segment <code>1</code>, offset <code>0x9773B0</code>, which is <code>9925552</code>.</p>
<p>The two offsets around the <code>CREATE TABLE</code> are far apart, so that range has a lot of data. The two around our <code>INSERT</code> are only 184 bytes apart. Let’s dump those bytes with <code>xxd</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">xxd</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> 9925552 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$((</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9925736</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9925552</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">))</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> ./000000010000000000000001</span></code></pre></div></div>
<pre><code>009773b0: 3200 0000 0000 0000 486f 9701 0000 0000  2.......Ho......
009773c0: 1008 0000 9780 5a82 ff18 0000 0000 0000  ......Z.........
009773d0: 0000 00b9 3901 f102 0000 f102 0000 f002  ....9...........
009773e0: 0000 0000 0000 0000 5300 0000 f102 0000  ........S.......
009773f0: b073 9701 0000 0000 800a 0000 6f26 a7d3  .s..........o&amp;..
00977400: 0060 2200 7f06 0000 0500 0000 0640 0000  .`"..........@..
00977410: 0000 0000 ff03 0300 0208 1800 0100 0000  ................
00977420: 0d41 6c69 6365 2561 6c69 6365 4065 7861  .Alice%alice@exa
00977430: 6d70 6c65 2e63 6f6d 0100 0000 0000 0000  mple.com........
00977440: 2200 0000 f102 0000 e873 9701 0000 0000  "........s......
00977450: 0001 0000 6352 4a9d ff08 2f2b f9c2 59fe  ....cRJ.../+..Y.
00977460: 0200 0000 0000 0000                      ........</code></pre>
<p>We can already see <code>Alice</code> and <code>alice@example.com</code> in there. We could try to read the rest byte by byte, like we did for the table pages in the earlier post. But Postgres ships a tool that does it for us, <code>pg_waldump</code>. It takes LSNs, not byte offsets:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb16-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pg_waldump</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> 0/19773B0 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span> 0/1977468</span></code></pre></div></div>
<pre><code>rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 0/019773B0, prev 0/01976F48, desc: RUNNING_XACTS nextXid 753 latestCompletedXid 752 oldestRunningXid 753
rmgr: Heap        len (rec/tot):     83/    83, tx:        753, lsn: 0/019773E8, prev 0/019773B0, desc: INSERT+INIT off: 1, flags: 0x00, blkref #0: rel 1663/5/16390 blk 0
rmgr: Transaction len (rec/tot):     34/    34, tx:        753, lsn: 0/01977440, prev 0/019773E8, desc: COMMIT 2026-09-13 10:43:50.075183 UTC</code></pre>
<p>One SQL statement produced three WAL records. A more complicated statement could produce many more.</p>
<ul>
<li>The first one belongs to the <strong>Standby</strong> resource manager. It describes the transactions running at that moment. We don’t need it here.</li>
<li>The second one is our insert. It comes from the <strong>Heap</strong> resource manager. <code>INSERT+INIT</code> means Postgres inserted the tuple and also initialized a new heap page in the same record, since this is the first row in the table.</li>
<li>The third one is the <strong>Transaction</strong> resource manager’s <code>COMMIT</code>, because the insert ran inside a transaction.</li>
</ul>
<p>If we did the same for the <code>CREATE TABLE</code>, we’d see many more records, mostly for the system catalogs it updates.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The Heap record, byte by byte
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>The Heap record starts at LSN <code>0/019773E8</code>, offset <code>0x9773E8</code> in the dump. Its 83 bytes are:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Bytes</th>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>5300 0000</code></td>
<td><code>xl_tot_len</code></td>
<td>83 bytes</td>
</tr>
<tr class="even">
<td><code>f102 0000</code></td>
<td><code>xl_xid</code></td>
<td>transaction 753</td>
</tr>
<tr class="odd">
<td><code>b073 9701 0000 0000</code></td>
<td><code>xl_prev</code></td>
<td><code>0/019773B0</code>, the Standby record before it</td>
</tr>
<tr class="even">
<td><code>80</code></td>
<td><code>xl_info</code></td>
<td><code>XLOG_HEAP_INSERT</code> with <code>XLOG_HEAP_INIT_PAGE</code></td>
</tr>
<tr class="odd">
<td><code>0a</code></td>
<td><code>xl_rmid</code></td>
<td>10, the Heap resource manager</td>
</tr>
<tr class="even">
<td><code>0000</code></td>
<td></td>
<td>padding</td>
</tr>
<tr class="odd">
<td><code>6f26 a7d3</code></td>
<td><code>xl_crc</code></td>
<td>checksum</td>
</tr>
<tr class="even">
<td><code>00 60 2200</code></td>
<td>block header</td>
<td>block 0, will init the page, has 34 bytes of data</td>
</tr>
<tr class="odd">
<td><code>7f06 0000 0500 0000 0640 0000</code></td>
<td>relation</td>
<td>1663 / 5 / 16390</td>
</tr>
<tr class="even">
<td><code>0000 0000</code></td>
<td>block number</td>
<td>0</td>
</tr>
<tr class="odd">
<td><code>ff 03</code></td>
<td>main data header</td>
<td>3 bytes of main data</td>
</tr>
<tr class="even">
<td><code>0300 0208 18</code></td>
<td><code>xl_heap_header</code></td>
<td>3 columns, infomask <code>0x0802</code>, header size 24</td>
</tr>
<tr class="odd">
<td><code>00</code></td>
<td></td>
<td>padding up to <code>t_hoff</code></td>
</tr>
<tr class="even">
<td><code>0100 0000</code></td>
<td><code>id</code></td>
<td>1</td>
</tr>
<tr class="odd">
<td><code>0d 416c696365</code></td>
<td><code>name</code></td>
<td><code>'Alice'</code></td>
</tr>
<tr class="even">
<td><code>25 616c69636540...</code></td>
<td><code>email</code></td>
<td><code>'alice@example.com'</code></td>
</tr>
<tr class="odd">
<td><code>0100 00</code></td>
<td><code>xl_heap_insert</code></td>
<td>offset 1, flags <code>0x00</code></td>
</tr>
</tbody>
</table>
<p>These are the same pieces we’ll see being registered in <code>heap_insert</code> below.</p>
</div>
</div>
</div>
<p>So these are the WAL records for the operations we ran. But what is a “resource manager”, and how do we interpret these records?</p>
<hr>
</section>
<section id="resource-managers" class="level3">
<h3 class="anchored" data-anchor-id="resource-managers">Resource managers</h3>
<p>Let’s pretend for a moment that we’re writing the WAL code for Postgres. Different operations need to log different things. An INSERT record needs enough information to rebuild the tuple and the heap page it went into. A CREATE TABLE record, on the other hand, needs to know about the physical file that was created for the table.</p>
<p>So every record has the same fixed header, with things like the transaction ID and the LSN of the previous record. What comes after the header, let’s call it the payload, is different for every operation.</p>
<p>How would we design this? We’d keep a registry, say an array. In it, we’d register a “resource manager” for each kind of operation. Each resource manager has a name and implements a set of functions: how to replay its records during recovery, how to describe them, and so on. In Rust we’d do this with trait objects. In Java, with interfaces. In C, with function pointers.</p>
<p>And that’s what Postgres does. The registry is an array called <code>RmgrTable</code>, and each resource manager in it is a struct called <code>RmgrData</code>.</p>
<hr>
</section>
<section id="in-the-source-code" class="level3">
<h3 class="anchored" data-anchor-id="in-the-source-code">In the source code</h3>
<p>All links below are to the <a href="https://github.com/postgres/postgres/tree/REL_17_6">REL_17_6</a> tag.</p>
<p>The fixed header is the <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/access/xlogrecord.h#L41-L53"><code>XLogRecord</code></a> struct in <code>xlogrecord.h</code>. This is the 24-byte header every WAL record starts with:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb18-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">typedef</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> XLogRecord</span>
<span id="cb18-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb18-3">    uint32      xl_tot_len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* total len of entire record */</span></span>
<span id="cb18-4">    TransactionId xl_xid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* xact id */</span></span>
<span id="cb18-5">    XLogRecPtr  xl_prev<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* ptr to previous record in log */</span></span>
<span id="cb18-6">    uint8       xl_info<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* flag bits, see below */</span></span>
<span id="cb18-7">    RmgrId      xl_rmid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* resource manager for this record */</span></span>
<span id="cb18-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* 2 bytes of padding here, initialize to zero */</span></span>
<span id="cb18-9">    pg_crc32c   xl_crc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* CRC for this record */</span></span>
<span id="cb18-10"></span>
<span id="cb18-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* XLogRecordBlockHeaders and XLogRecordDataHeader follow, no padding */</span></span>
<span id="cb18-12"></span>
<span id="cb18-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> XLogRecord<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div></div>
<p>The comment <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/access/xlogrecord.h#L21-L30">above it</a> describes the full layout of a record: the fixed header, then a header for each block ( page ) the record touches, then the data for those blocks, and finally the “main data” that belongs to the resource manager.</p>
<p>The registry, <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/transam/rmgr.c#L47-L52"><code>RmgrTable</code></a>, is in <code>rmgr.c</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb19-1"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">#define PG_RMGR</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">symname</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">name</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">redo</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">desc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">identify</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">startup</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">cleanup</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">mask</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">decode</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> </span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb19-2"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">    </span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> name</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> redo</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> desc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> identify</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> startup</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> cleanup</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> mask</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;"> decode </span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb19-3"></span>
<span id="cb19-4">RmgrData    RmgrTable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>RM_MAX_ID <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb19-5"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">#include </span><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">"access/rmgrlist.h"</span></span>
<span id="cb19-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span></code></pre></div></div>
<p>Its entries come from <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/access/rmgrlist.h#L27-L49"><code>rmgrlist.h</code></a>. That’s where all the resource managers are listed, including the Heap, Standby and Transaction ones we saw in <code>pg_waldump</code>. The <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/access/rmgrlist.h#L38">Heap entry</a> looks like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb20-1">PG_RMGR<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>RM_HEAP_ID<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Heap"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> heap_redo<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> heap_desc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> heap_identify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> NULL<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> NULL<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> heap_mask<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> heap_decode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<p>Each entry fills in an <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/access/xlog_internal.h#L349-L360"><code>RmgrData</code></a>, which is mostly function pointers:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb21-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">typedef</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> RmgrData</span>
<span id="cb21-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb21-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">const</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>rm_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb21-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span>        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>rm_redo<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>XLogReaderState <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>record<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb21-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span>        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>rm_desc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>StringInfo buf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> XLogReaderState <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>record<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb21-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">const</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*(*</span>rm_identify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>uint8 info<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb21-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span>        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>rm_startup<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb21-8">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span>        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>rm_cleanup<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb21-9">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span>        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>rm_mask<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>pagedata<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> BlockNumber blkno<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb21-10">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span>        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>rm_decode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> LogicalDecodingContext <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>ctx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-11">                              <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> XLogRecordBuffer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>buf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb21-12"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> RmgrData<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div></div>
<p>Every resource manager implements its own versions of these functions, and the WAL machinery calls them through these pointers. That’s roughly the design we had in mind.</p>
<section id="writing-the-record-heap_insert" class="level4">
<h4 class="anchored" data-anchor-id="writing-the-record-heap_insert">Writing the record: <code>heap_insert</code></h4>
<p>Now let’s follow our INSERT. Every row inserted into a regular table goes through <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/heap/heapam.c#L2038"><code>heap_insert</code></a> in <code>heapam.c</code>. While reading it, keep in mind that all of this is for recovery. Whatever gets logged here is what the redo function will use to recreate the change.</p>
<p>Before any WAL is written, <code>heap_insert</code> does a few other things. It prepares the tuple ( fills in the header fields, and TOASTs it if a value is too long ), finds a page with enough room for it, and handles some conflict checks and the visibility map.</p>
<p>Then the <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/heap/heapam.c#L2116">WAL part</a> starts. First, there’s a <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/heap/heapam.c#L2132-L2142">small check</a>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb22-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>ItemPointerGetOffsetNumber<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(&amp;(</span>heaptup<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>t_self<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> FirstOffsetNumber <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span></span>
<span id="cb22-2">    PageGetMaxOffsetNumber<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>page<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> FirstOffsetNumber<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb22-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb22-4">    info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|=</span> XLOG_HEAP_INIT_PAGE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb22-5">    bufflags <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|=</span> REGBUF_WILL_INIT<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb22-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>If this tuple is the first and only one on the page, the record gets the <code>INIT</code> flag. That’s the <code>+INIT</code> we saw in <code>pg_waldump</code>. During recovery, it tells Postgres that this is a fresh page, so it can just create a new page and put the tuple in it. Otherwise, Postgres would have to read the existing page first and then insert into it.</p>
<p>After that, logging the record takes four steps ( <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/heap/heapam.c#L2167-L2189">L2167–L2189</a> ):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb23-1">XLogBeginInsert<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">();</span></span>
<span id="cb23-2">XLogRegisterData<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">((</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>xlrec<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> SizeOfHeapInsert<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb23-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb23-4">XLogRegisterBuffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> REGBUF_STANDARD <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> bufflags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb23-5">XLogRegisterBufData<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>xlhdr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> SizeOfHeapHeader<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb23-6">XLogRegisterBufData<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-7">                    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*)</span> heaptup<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>t_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> SizeofHeapTupleHeader<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-8">                    heaptup<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>t_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> SizeofHeapTupleHeader<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb23-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb23-10">recptr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> XLogInsert<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>RM_HEAP_ID<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> info<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span></code></pre></div></div>
<ol type="1">
<li><strong><code>XLogBeginInsert()</code></strong> checks whether we’re allowed to write WAL at all. For example, a replica that is replaying WAL can’t write WAL records of its own.</li>
<li><strong><code>XLogRegisterData()</code></strong> registers the “main” data of the record. For the Heap insert, that’s a tiny struct, <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/access/heapam_xlog.h#L159-L165"><code>xl_heap_insert</code></a>: the offset of the slot on the page the tuple went into, and some flags. These are the <code>off: 1, flags: 0x00</code> in the <code>pg_waldump</code> output.</li>
<li><strong><code>XLogRegisterBuffer()</code></strong> registers the in-memory page we modified. From it, Postgres works out the tablespace, database, relation file and block number. That’s the <code>rel 1663/5/16390 blk 0</code> in <code>pg_waldump</code>. During replay, this tells Postgres which page on disk to change.</li>
<li><strong><code>XLogRegisterBufData()</code></strong>, called twice, attaches data to that page. The first call registers the tuple’s header, and the second registers the tuple data itself. This is where our row ends up in the WAL.</li>
</ol>
<p>Finally, <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/transam/xloginsert.c#L474"><code>XLogInsert</code></a> assembles everything that was registered into one record, in the right order. It computes the checksum, stamps the resource manager’s ID on the record, and copies it into the WAL buffers, from where it’s written to <code>pg_wal</code>. It returns the record’s LSN.</p>
</section>
<section id="replaying-the-record-heap_redo" class="level4">
<h4 class="anchored" data-anchor-id="replaying-the-record-heap_redo">Replaying the record: <code>heap_redo</code></h4>
<p>All of this was so the change can be replayed during recovery. For the Heap resource manager, that’s <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/heap/heapam.c#L10338"><code>heap_redo</code></a>. It’s a simple switch on the record type:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb24-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">switch</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> XLOG_HEAP_OPMASK<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb24-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb24-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">case</span> XLOG_HEAP_INSERT<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb24-4">        heap_xlog_insert<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>record<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb24-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb24-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">case</span> XLOG_HEAP_DELETE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb24-7">        heap_xlog_delete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>record<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb24-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb24-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">case</span> XLOG_HEAP_UPDATE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb24-10">        heap_xlog_update<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>record<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb24-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb24-12">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span>
<span id="cb24-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p><a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/heap/heapam.c#L9592"><code>heap_xlog_insert</code></a> does the reverse of what we saw in <code>heap_insert</code>. It takes the WAL record and redoes the insert on the same page, in the same slot. And here’s the <code>INIT</code> flag being used ( <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/heap/heapam.c#L9638-L9646">L9638–L9646</a> ):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb25-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>XLogRecGetInfo<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>record<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> XLOG_HEAP_INIT_PAGE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb25-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb25-3">    buffer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> XLogInitBufferForRedo<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>record<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb25-4">    page <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> BufferGetPage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb25-5">    PageInit<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>page<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> BufferGetPageSize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb25-6">    action <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> BLK_NEEDS_REDO<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb25-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb25-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span></span>
<span id="cb25-9">    action <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> XLogReadBufferForRedo<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>record<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>buffer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span></code></pre></div></div>
<p>So for one INSERT, the Heap resource manager handles everything: <code>heap_insert</code> writes the record, and <code>heap_redo</code> replays it during recovery.</p>
<hr>
</section>
</section>
<section id="summary" class="level3">
<h3 class="anchored" data-anchor-id="summary">Summary</h3>
<ul>
<li>The WAL is an infinite stream of records, split into 16MB segment files, which are further split into 8KB pages.</li>
<li>Every record can be located by its LSN. Each record has a fixed header, followed by a payload specific to that operation.</li>
<li>Resource managers define what goes in the payload, how to replay it, and how to decode it. They are registered in <code>RmgrTable</code>.</li>
<li>Tools like <code>pg_waldump</code> let us read the WAL in a human-readable form.</li>
</ul>
<hr>
</section>
<section id="what-i-left-out" class="level3">
<h3 class="anchored" data-anchor-id="what-i-left-out">What I left out</h3>
<ul>
<li><strong>Full-page images.</strong> After every checkpoint, the first change to a page writes the entire 8KB page into the WAL, to protect against torn pages.</li>
<li><strong>Redo is safe to repeat.</strong> Replaying a record on a page that already has the change does nothing. The page’s LSN tells Postgres whether the change is already there.</li>
<li><strong><code>wal_level</code> changes what gets logged.</strong> Under <code>wal_level = logical</code>, the INSERT record gets an extra flag ( <code>0x08</code> ) and always keeps the tuple data, so logical decoding can read it.</li>
<li><strong>Indexes write their own records.</strong> An insert into a table with an index also produces records from the index’s resource manager ( for example, Btree ), separate from the Heap record.</li>
</ul>
<hr>
</section>
<section id="credits-and-resources" class="level3">
<h3 class="anchored" data-anchor-id="credits-and-resources">Credits and Resources</h3>
<ol type="1">
<li><a href="https://www.postgresql.org/docs/17/wal-internals.html">WAL Internals</a>, PostgreSQL docs</li>
<li><a href="https://www.postgresql.org/docs/17/pgwaldump.html">pg_waldump</a>, PostgreSQL docs</li>
<li><a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/access/transam/README">The transam README</a> in the postgres source</li>
<li><a href="https://github.com/postgres/postgres/tree/REL_17_6">Postgres Source Code</a></li>
</ol>
<hr>


</section>

 ]]></description>
  <category>postgres</category>
  <category>internals</category>
  <guid>https://thewalstreetjournal.github.io/posts/inside-the-postgres-wal/</guid>
  <pubDate>Thu, 01 Oct 2026 18:30:00 GMT</pubDate>
  <media:content url="https://thewalstreetjournal.github.io/posts/inside-the-postgres-wal/wal-segments-pages.png" medium="image" type="image/png" height="141" width="144"/>
</item>
<item>
  <title>How the Postgres Free Space Map Works</title>
  <link>https://thewalstreetjournal.github.io/posts/postgres-free-space-map/</link>
  <description><![CDATA[ 






<p>Postgres stores data on disk in 8KB pages. Every time we insert a row, postgres has to find a page with enough free space to fit that row. That’s easy when the table is small. But a 100GB table has around 13 million pages, and the free space in each of them keeps changing as rows are inserted and deleted. So how does postgres find a page for the new row so quickly?</p>
<p>The answer is a data structure called the <strong>Free Space Map</strong> ( FSM ). In this post, we’ll build it up step by step and see how Postgres implements it.</p>
<p>Prefer video? Here’s the same walkthrough:</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/ZKMLQ5BFrCw" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<hr>
<section id="the-wishlist" class="level3">
<h3 class="anchored" data-anchor-id="the-wishlist">The wishlist</h3>
<p>Before looking at what postgres does, let’s write down what we want from this data structure.</p>
<p><strong>1. Search and update should be fast.</strong> Before inserting a row, we search for a page with enough free space. After the insert, we update that page’s free space. Both of these happen on every insert, so both should be something like O(log n).</p>
<p><strong>2. Finding the maximum free space should be instant.</strong> If even the page with the maximum free space can’t accommodate our row, there’s no point searching through the other pages. We should immediately give up and add a new, empty page to the table. So checking the maximum free space size has to be O(1).</p>
<p><strong>3. Inserts should be spread across pages.</strong> This one is a bit nuanced. Say we did the simplest thing: go through the pages one by one and pick the first one with enough room. If page 3 has a lot of free space, the first insert goes to page 3. So does the second, and the third.</p>
<p><img src="https://thewalstreetjournal.github.io/posts/postgres-free-space-map/fsm-wishlist-inserts.png" class="img-fluid" alt="Five pages. Pages 1 and 2 are full, pages 3, 4 and 5 have free space. Inserts 1, 2 and 3 all go to page 3."></p>
<p>Every insert adds onto the same page. That’s bad for performance, because all those inserts end up fighting over the same page. If pages 3, 4 and 5 all have room, we’d rather send one insert to each of them.</p>
<p>So the wishlist is:</p>
<ol type="1">
<li>Search and update in O(log n).</li>
<li>Find the maximum free space in O(1).</li>
<li>Spread the inserts evenly.</li>
</ol>
<hr>
</section>
<section id="a-max-heap" class="level3">
<h3 class="anchored" data-anchor-id="a-max-heap">A Max-Heap</h3>
<p>There is one data structure that already gives us the first two: a <strong>max-heap</strong>.</p>
<p>The heap nodes store the free space of each page, one heap node per page. Every internal node stores the maximum of its two children. So the root always holds the largest free space across all pages ( that’s our O(1) maximum ), and searching or updating means walking one path from the root to a leaf or back up ( that’s O(log n) ).</p>
<p>So how do we now spread the inserts evenly? For this, postgres implements a <strong>round-robin</strong> search:</p>
<ul>
<li>The search starts at a leaf, not at the root.</li>
<li>Postgres remembers where the last search ended, in a variable called <code>next_slot</code>. The next search starts from there, and only moves to the right ( wrapping around at the end ).</li>
</ul>
<p>The search itself has two phases:</p>
<ol type="1">
<li><strong>Climb.</strong> Starting from the <code>next_slot</code> leaf, check if the current node has enough free space. If not, move one node to the right, then climb up to its parent. Repeat until you reach a node with enough space.</li>
<li><strong>Descend.</strong> Treat that node as the root and walk down like a normal heap search, preferring the left child whenever it has enough space, until you reach a leaf.</li>
</ol>
<p>Moving right-then-up means we only ever look to the right of where we started, and the area we cover doubles with each step. So we still find a page in O(log n), but each search starts where the last one stopped, and the inserts spread out across pages.</p>
<p>Let’s do a dry run on this tree. The leaves are the free spaces of 8 pages, numbered 0 to 7:</p>
<p><img src="https://thewalstreetjournal.github.io/posts/postgres-free-space-map/fsm-search-tree.png" class="img-fluid" alt="A max-heap with leaves 5, 10, 7, 20, 5, 35, 11, 40. The level above has 10, 20, 35, 40, then 20 and 40, and the root is 40."></p>
<p><strong>Search(8)</strong>, with <code>next_slot = 0</code>:</p>
<ul>
<li>Start at leaf 0, which has 5. Not enough.</li>
<li>Move right to leaf 1 and climb to its parent, which has 10. That’s enough, so the climb stops.</li>
<li>Descend from 10: the left child has 5 ( not enough ), the right child has 10. That’s leaf 1.</li>
<li>Return page 1, and set <code>next_slot = 2</code>.</li>
</ul>
<p><strong>Search(25)</strong>, with <code>next_slot = 2</code>:</p>
<ul>
<li>Start at leaf 2, which has 7. Not enough.</li>
<li>Move right to leaf 3 and climb to its parent, which has 20. Still not enough.</li>
<li>Move right to the node with 35 and climb to its parent, which has 40. That’s enough.</li>
<li>Descend from 40: the left child has 35, which is enough, so go left. From 35: the left child has 5, the right child has 35. That’s leaf 5.</li>
<li>Return page 5, and set <code>next_slot = 6</code>.</li>
</ul>
<p>Notice that the second search never looked at pages 0 or 1. It started where the first one stopped.</p>
<hr>
</section>
<section id="from-one-page-to-millions" class="level3">
<h3 class="anchored" data-anchor-id="from-one-page-to-millions">From one page to millions</h3>
<p>There is one more problem. This max-heap has to live on disk, and everything postgres stores on disk lives in 8KB pages. One 8KB page can hold the max-heap for about 4,000 data pages. That’s nowhere near the millions of pages we want to handle.</p>
<p>So postgres splits the heap across many pages and arranges those pages as a tree too. Each FSM page holds its own small max-heap. The leaves of a top-level FSM page don’t describe data pages. Each leaf holds the root value of a child FSM page one level below. Only the leaves of the bottom-level FSM pages hold the free space of the actual data pages.</p>
<p><img src="https://thewalstreetjournal.github.io/posts/postgres-free-space-map/fsm-three-levels.png" class="img-fluid" alt="Three levels of FSM pages. Each FSM page contains a small max-heap. The leaves of the root page match the roots of the level-1 pages, and the leaves of level-1 pages match the roots of the bottom-level pages."></p>
<p>So we have a tree of FSM pages, and inside each FSM page there is a max-heap. Put together, it’s still one big max-heap.</p>
<p>The search changes a little:</p>
<ol type="1">
<li>Start at the root FSM page and go down level by level.</li>
<li>Inside each FSM page, run the same climb-and-descend search. It tells us which child page to read next. At the bottom level, it tells us which data page to use.</li>
</ol>
<p>For every search, postgres reads exactly three FSM pages: the root, one page from the middle level, and one from the bottom level.</p>
<p>How far does this scale? With 8KB pages, each FSM page has room for 4,069 leaves. Three levels give us 4,069 × 4,069 × 4,069, about 67 billion pages. That’s more than the roughly 4 billion pages a single postgres table can have anyway. So reading 3 FSM pages is enough to find room for a row, even in the biggest table postgres supports.</p>
<p>To summarise: when we insert a row, postgres checks the root of the FSM. If even the biggest free space isn’t enough, it adds a new page to the table. Otherwise, it walks down the three levels of the FSM, doing a round-robin search inside each FSM page, and ends up at a data page with enough room.</p>
<hr>
</section>
<section id="prerequisites" class="level3">
<h3 class="anchored" data-anchor-id="prerequisites">Prerequisites</h3>
<p>I’m using the same postgres container as the earlier posts. The Dockerfile is on <a href="https://github.com/thewalstreetjournal/postgres-playlist/tree/main/003-fsm-data-structure">GitHub</a>. The <code>pg_freespacemap</code> extension we’ll use is included in the official postgres image.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-t</span> postgres-17.6:v1 .</span>
<span id="cb1-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> network create pgnet</span>
<span id="cb1-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--name</span> postgres <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--network</span> pgnet <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span> POSTGRES_PASSWORD=secret postgres-17.6:v1</span>
<span id="cb1-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> exec <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-it</span> postgres bash</span></code></pre></div></div>
<p>I keep two terminals open inside the container: one for <code>psql -U postgres</code> and one for looking at the files.</p>
<hr>
</section>
<section id="the-fsm-in-the-terminal" class="level3">
<h3 class="anchored" data-anchor-id="the-fsm-in-the-terminal">The FSM in the terminal</h3>
<p>Let’s switch to a test database and create a table:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">CREATE</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">DATABASE</span> dummy;</span>
<span id="cb2-2">\c dummy</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">CREATE</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">TABLE</span> items (<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">id</span> serial, <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">data</span> text);</span>
<span id="cb2-5"></span>
<span id="cb2-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> pg_relation_filepath(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'items'</span>);</span></code></pre></div></div>
<pre><code> pg_relation_filepath
----------------------
 base/16386/16388</code></pre>
<p>The FSM is stored per table, right next to the table’s data file. In the other terminal:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$PGDATA</span></span>
<span id="cb4-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> base/16386 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">grep</span> 16388</span></code></pre></div></div>
<pre><code>-rw------- 1 postgres postgres     0 ... 16388</code></pre>
<p>There’s just the data file, and it’s empty: zero bytes, because we haven’t inserted anything yet. There is no FSM file. Postgres is lazy about the FSM. It won’t create it until it needs it.</p>
<p>Let’s insert enough rows to fill a few pages:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">INSERT</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">INTO</span> items (<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">data</span>)</span>
<span id="cb6-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> repeat(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> generate_series(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>);</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> base/16386 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">grep</span> 16388</span></code></pre></div></div>
<pre><code>-rw------- 1 postgres postgres 49152 ... 16388
-rw------- 1 postgres postgres 24576 ... 16388_fsm</code></pre>
<p>The table now has 6 pages ( 49152 / 8192 ), and a new file, <code>16388_fsm</code>, has appeared. It’s 24KB, which is 3 pages. That’s what we’d expect: the root FSM page, one page at the middle level, and one page at the bottom level. These pages are stored in the file depth-first. So if the table grows enough to need a second bottom-level page, it goes right after the first one.</p>
<p>We won’t decode the raw bytes this time. Instead, postgres has an extension that reads the FSM for us:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">CREATE</span> EXTENSION <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">IF</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">NOT</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">EXISTS</span> pg_freespacemap;</span>
<span id="cb9-2"></span>
<span id="cb9-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> pg_freespace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'items'</span>);</span></code></pre></div></div>
<pre><code> blkno | avail
-------+-------
     0 |   128
     1 |   128
     2 |   128
     3 |   128
     4 |   128
     5 |     0</code></pre>
<p>Each row is 200 x’s plus some headers, so 34 of them fit in a page. The first five pages are nearly full, with only 128 bytes left. The last page says 0, but it isn’t actually full: it only has 30 rows. Postgres records a page’s free space in the FSM when it moves on from that page, and we never moved on from the last one.</p>
<p>Now let’s delete half the rows:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">DELETE</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> items <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">WHERE</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">id</span> % <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>;</span>
<span id="cb11-2"></span>
<span id="cb11-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> pg_freespace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'items'</span>);</span></code></pre></div></div>
<pre><code> blkno | avail
-------+-------
     0 |   128
     1 |   128
     2 |   128
     3 |   128
     4 |   128
     5 |     0</code></pre>
<p>Nothing changed. Postgres is lazy here too. A <code>DELETE</code> only marks the rows as deleted. The space is reclaimed later by <code>VACUUM</code>, which also updates the FSM. We can run it ourselves:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb13-1">VACUUM items;</span>
<span id="cb13-2"></span>
<span id="cb13-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> pg_freespace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'items'</span>);</span></code></pre></div></div>
<pre><code> blkno | avail
-------+-------
     0 |  4064
     1 |  4064
     2 |  4064
     3 |  4064
     4 |  4064
     5 |  4544</code></pre>
<p>Now every page is about half empty, which matches the half of the rows we deleted. If you list the files again, you’ll also see a <code>16388_vm</code> file. That’s the visibility map, which <code>VACUUM</code> creates. It isn’t in the scope of this post.</p>
<hr>
</section>
<section id="in-the-source-code" class="level3">
<h3 class="anchored" data-anchor-id="in-the-source-code">In the source code</h3>
<p>The FSM code lives in <a href="https://github.com/postgres/postgres/tree/REL_17_6/src/backend/storage/freespace"><code>src/backend/storage/freespace</code></a>. The <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/README">README</a> there is a very readable description of the whole design.</p>
<p>The search inside one FSM page is <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/fsmpage.c#L158"><code>fsm_search_avail</code> in <code>fsmpage.c</code></a>. The climb is just a few lines ( <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/fsmpage.c#L227-L238">L227–L238</a> ):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb15-1">nodeno <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> target<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb15-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>nodeno <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb15-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb15-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fsmpage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>fp_nodes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>nodeno<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> minvalue<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb15-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb15-6"></span>
<span id="cb15-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/*</span></span>
<span id="cb15-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">     * Move to the right, wrapping around on same level if necessary, then</span></span>
<span id="cb15-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">     * climb up.</span></span>
<span id="cb15-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">     */</span></span>
<span id="cb15-11">    nodeno <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> parentof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rightneighbor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>nodeno<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">));</span></span>
<span id="cb15-12"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>After the descend, it updates the start point for the next search ( <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/fsmpage.c#L303">L303</a> ):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb16-1">fsmpage<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>fp_next_slot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> slot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>advancenext <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span></code></pre></div></div>
<p>Going down the three levels happens in <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/freespace.c#L678"><code>fsm_search</code> in <code>freespace.c</code></a>. It reads one FSM page per level and calls <code>fsm_search_avail</code> on it. One small detail there: <code>advancenext</code> is only true at the bottom level ( <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/freespace.c#L697">L697</a> ). So it’s the bottom-level pages, the ones pointing at real data pages, that move their <code>next_slot</code> forward and spread the inserts.</p>
<hr>
</section>
<section id="what-i-left-out" class="level3">
<h3 class="anchored" data-anchor-id="what-i-left-out">What I left out</h3>
<ul>
<li><strong>Quantization.</strong> Postgres doesn’t store the exact free space of a page. It stores one byte per page: 256 categories of 32 bytes each ( <code>FSM_CATEGORIES</code> and <code>FSM_CAT_STEP</code> in <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/freespace.c#L64-L65"><code>freespace.c</code></a> ). The exact free space would need 13 bits. Postgres rounds the available space down and the requested space up, so it never sends a row to a page that can’t fit it. That’s why we saw neat numbers like 128 and 4064 above.</li>
<li><strong>The FSM is not WAL-logged.</strong> It’s treated as a hint. If it’s wrong after a crash, that’s fine: searches detect and fix broken FSM pages, and <code>VACUUM</code> rewrites the correct values.</li>
<li><strong>The FSM can be stale.</strong> It’s mostly updated by <code>VACUUM</code>, and by inserts that find a page full and move on. So a page can have more ( or less ) room than the FSM says. If an insert lands on a page that’s actually full, it records the real value and searches again.</li>
<li><strong>The tree depth.</strong> The three levels are a compile-time constant ( <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/freespace.c#L75"><code>FSM_TREE_DEPTH</code></a> ). With very small page sizes, postgres would use four levels instead.</li>
</ul>
<hr>
</section>
<section id="credits-and-resources" class="level3">
<h3 class="anchored" data-anchor-id="credits-and-resources">Credits and Resources</h3>
<p>For this post, I have heavily used Claude Code. Along with that, I have relied on the following resources:</p>
<ol type="1">
<li><a href="https://www.postgresql.org/docs/17/storage-fsm.html">Free Space Map</a>, PostgreSQL docs</li>
<li><a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/storage/freespace/README">The FSM README</a> in the postgres source</li>
<li><a href="https://github.com/postgres/postgres/tree/REL_17_6">Postgres Source Code</a></li>
</ol>
<hr>


</section>

 ]]></description>
  <category>postgres</category>
  <category>internals</category>
  <guid>https://thewalstreetjournal.github.io/posts/postgres-free-space-map/</guid>
  <pubDate>Sun, 07 Jun 2026 18:30:00 GMT</pubDate>
  <media:content url="https://thewalstreetjournal.github.io/posts/postgres-free-space-map/fsm-three-levels.png" medium="image" type="image/png" height="91" width="144"/>
</item>
<item>
  <title>How Does Postgres Store Data on Disk?</title>
  <link>https://thewalstreetjournal.github.io/posts/how-postgres-stores-data/</link>
  <description><![CDATA[ 






<p>If you insert a row into a postgres table, could you go and find it on disk? The actual file, the actual bytes? That’s what we’re going to do in this post. We’ll insert one row, find the file it lives in, and then decode it byte by byte using the structs from the postgres source code.</p>
<p>Prefer video? Here’s the same walkthrough:</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/1tNMRcgUtb8" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<section id="how-postgres-organizes-storage" class="level3">
<h3 class="anchored" data-anchor-id="how-postgres-organizes-storage">How postgres organizes storage</h3>
<p>Before we go inserting and searching for data, let’s first build a picture of how postgres organizes data. That way, when we trace the bytes for our data, we’ll know what we’re looking at.</p>
<p>In Postgres, every table is stored in one or more files. We call these files <strong>segment files</strong>. A segment file can grow up to 1GB. If a table grows beyond 1GB, postgres creates another segment file for the next 1GB, then another one, and so on. For a table whose file is named <code>16388</code>, the segments would be named <code>16388</code>, <code>16388.1</code>, <code>16388.2</code>, and so on. ( The 1GB limit can be changed at the time of setup )</p>
<p>Each segment file is made of <strong>pages</strong> ( also called <em>blocks</em> ). A page is 8KB by default. This too can only be changed at compile time. The page is the smallest unit of IO in postgres: when postgres reads from or writes to a table file, it reads or writes a whole 8KB page. Any change to a row, however small, happens by changing the page that contains it.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://thewalstreetjournal.github.io/posts/how-postgres-stores-data/segments-and-pages.png" class="img-fluid figure-img" alt="A table stored as segment files 16388, 16388.1 and 16388.2 of up to 1 GB each. The first segment is a sequence of 8 KB pages, page 0 to page 131071."></p>
<figcaption>segments-and-pages</figcaption>
</figure>
</div>
<p>Now let’s zoom into a page. Every page has the same layout:</p>
<ul>
<li>A <strong>page header</strong> of 24 bytes at the start.</li>
<li>An array of <strong>line pointers</strong> right after the header. Each line pointer is 4 bytes and points to one row in the page. This array grows from the top of the page downwards.</li>
<li>The <strong>tuples</strong> ( rows ) themselves, which are stored from the bottom of the page upwards.</li>
<li>The <strong>free space</strong> in between. New rows go here. The line pointers grow into it from the top, the tuples grow into it from the bottom.</li>
<li>There’s also some <strong>special space</strong> at the very end. Tables don’t use it, but some indexes use it. This is beyond our scope for this post.</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://thewalstreetjournal.github.io/posts/how-postgres-stores-data/page-layout.png" class="img-fluid figure-img" alt="Inside an 8 KB page: a 24-byte page header, then line pointers lp 1 to lp 3 pointing to the rows Bob, Alice and Carol stored from the bottom of the page, with free space in between. pd_lower is 36 and pd_upper is 8072."></p>
<figcaption>page-layout</figcaption>
</figure>
</div>
<p>Each tuple also has a small header of its own, with things like the transaction id that created it, followed by the actual column values.</p>
<section id="why-do-we-need-line-pointers" class="level4">
<h4 class="anchored" data-anchor-id="why-do-we-need-line-pointers">Why do we need line pointers?</h4>
<p>Why not just store the tuples one after another from the start of the page?</p>
<p>For a moment, assume that we don’t have line pointers and we placed the tuples one after the other from the start. Then row 3 in a page got deleted. Now there’s a hole in the middle of the page. To reuse that space, we would want to move the other rows to close the gap. But we can’t just move a row, because indexes may point to it. In order to move rows around, we may also need to move around indexes, which is not efficient.</p>
<p>With line pointers, we introduce indirection. Postgres can shuffle the tuples around inside the page to close the gaps, and it only has to update the offsets in the line pointers. The line pointer numbers themselves stay the same, so every index entry that points to row 3 still finds it. This is why these are also called <strong>slotted pages</strong>: each row has a fixed slot, and the slot knows where the row currently lives.</p>
<iframe src="why-line-pointers.html" title="Why line pointers" style="width:100%;aspect-ratio:800/720;border:0" loading="lazy">
</iframe>
<p>So, to summarise: a table is stored in segment files, a segment file is made of 8KB pages, and every page has a header, line pointers growing from the top, tuples growing from the bottom, and free space in the middle.</p>
<p>Now let’s go insert some data and find it on disk. The numbers in the outputs below ( OIDs, file names, the LSN and the transaction id ) will be slightly different on your machine, but the layout will be exactly the same.</p>
</section>
</section>
<section id="prerequisites" class="level2">
<h2 class="anchored" data-anchor-id="prerequisites">Prerequisites</h2>
<p>For this post, I’m assuming you know what docker containers are and how to build and run them.</p>
<p>I’m using the same postgres container as the <a href="../how-postgres-handles-connections/">previous post</a>, with a few extra tools installed for looking at binary files ( like <code>xxd</code> ). The Dockerfile is on <a href="https://github.com/thewalstreetjournal/postgres-playlist/tree/main/002-how-postgres-stores-data">GitHub</a>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-t</span> postgres-17.6:v1 .</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># To let the python app talk to postgres, we create a docker network</span></span>
<span id="cb1-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> network create pgnet</span>
<span id="cb1-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--name</span> postgres <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--network</span> pgnet <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span> POSTGRES_PASSWORD=secret postgres-17.6:v1</span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Open a shell inside the container</span></span>
<span id="cb1-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> exec <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-it</span> postgres bash</span></code></pre></div></div>
<p>I’ll keep two terminals open inside the container: one for <code>psql</code> and one for looking at the files.</p>
<section id="finding-the-data-directory" class="level3">
<h3 class="anchored" data-anchor-id="finding-the-data-directory">Finding the data directory</h3>
<p>Since postgres is running in this container, it must be storing its data in some directory. This location is usually in the <code>PGDATA</code> environment variable:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">echo</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$PGDATA</span></span>
<span id="cb2-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># /var/lib/postgresql/data</span></span></code></pre></div></div>
<p>If we list this directory, we see a bunch of files and subdirectories:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$PGDATA</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span></span></code></pre></div></div>
<pre><code>total 128
drwx------ 6 postgres postgres  4096 Mar 14 13:05 base
drwx------ 2 postgres postgres  4096 Mar 14 13:05 global
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_commit_ts
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_dynshmem
-rw------- 1 postgres postgres  5743 Mar 14 13:05 pg_hba.conf
-rw------- 1 postgres postgres  2640 Mar 14 13:05 pg_ident.conf
drwx------ 4 postgres postgres  4096 Mar 14 13:05 pg_logical
drwx------ 4 postgres postgres  4096 Mar 14 13:05 pg_multixact
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_notify
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_replslot
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_serial
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_snapshots
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_stat
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_stat_tmp
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_subtrans
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_tblspc
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_twophase
-rw------- 1 postgres postgres     3 Mar 14 13:05 PG_VERSION
drwx------ 4 postgres postgres  4096 Mar 14 13:05 pg_wal
drwx------ 2 postgres postgres  4096 Mar 14 13:05 pg_xact
-rw------- 1 postgres postgres    88 Mar 14 13:05 postgresql.auto.conf
-rw------- 1 postgres postgres 30777 Mar 14 13:05 postgresql.conf
-rw------- 1 postgres postgres    36 Mar 14 13:05 postmaster.opts
-rw------- 1 postgres postgres    94 Mar 14 13:05 postmaster.pid</code></pre>
<p>Some of these handle replication, some handle the write ahead log, some are config files. For now, we only care about <code>base</code>. This is where postgres stores the data for every database.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span> base/</span>
<span id="cb5-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1  16385  4  5</span></span></code></pre></div></div>
<p>These numbers are <strong>object identifiers</strong>, or OIDs. Postgres gives an OID to almost everything it creates: databases, tables, indexes, roles and so on. Inside <code>base/</code>, there is one subdirectory per database, named after that database’s OID.</p>
<p>Let’s create a new database and watch a new directory show up. In the <code>psql</code> terminal ( <code>psql -U postgres</code> ):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">CREATE</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">DATABASE</span> dummy;</span>
<span id="cb6-2"></span>
<span id="cb6-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">oid</span>, datname <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> pg_database;</span></code></pre></div></div>
<p>We get this output:</p>
<pre><code>  oid  |  datname
-------+------------
     5 | postgres
 16385 | mydatabase
     1 | template1
     4 | template0
 16386 | dummy</code></pre>
<p><code>1</code>, <code>4</code> and <code>5</code> are the default databases. <code>16385</code> is <code>mydatabase</code>, which our Dockerfile creates. And <code>16386</code> is the <code>dummy</code> database we just created. Your OIDs might be different.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span> base/</span>
<span id="cb8-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1  16385  16386  4  5</span></span></code></pre></div></div>
<p>We can see the directory for our databae. If we look inside the new directory, we already see about 300 files, even though we haven’t created a single table yet:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span> base/16386 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wc</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span></span>
<span id="cb9-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 298</span></span></code></pre></div></div>
<p>These are the system catalogs, which are postgres’s own internal tables. Every new database is a copy of the <code>template1</code> database, so it starts with its own copy of all of the system catalogs.</p>
<hr>
</section>
<section id="finding-the-tables-file" class="level3">
<h3 class="anchored" data-anchor-id="finding-the-tables-file">Finding the table’s file</h3>
<p>Now let’s create a table and insert a row. Switch to the <code>dummy</code> database with <code>\c dummy</code> and run:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">CREATE</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">TABLE</span> users (</span>
<span id="cb10-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">id</span>   serial <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">PRIMARY</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">KEY</span>,</span>
<span id="cb10-3">    name text,</span>
<span id="cb10-4">    age  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb10-5">);</span>
<span id="cb10-6"></span>
<span id="cb10-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">INSERT</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">INTO</span> users (name, age) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">VALUES</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Bob'</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>);</span></code></pre></div></div>
<p>To find the file for this table, postgres has a function:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> pg_relation_filepath(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'users'</span>);</span></code></pre></div></div>
<pre><code> pg_relation_filepath
----------------------
 base/16386/16388</code></pre>
<p>So our row is somewhere inside <code>base/16386/16388</code>. This number is the table’s <strong>relfilenode</strong>. It often matches the table’s OID, but not always ( some commands like <code>TRUNCATE</code> can give the table a new file ), so it’s better to query postgres than to assume.</p>
<p>Let’s look at the file:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb13-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ls</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> base/16386/16388</span>
<span id="cb13-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -rw------- 1 postgres postgres 8192 ... base/16386/16388</span></span></code></pre></div></div>
<p>It’s exactly 8192 bytes, which is one page. Now let’s dump the file bytes using <code>xxd</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">xxd</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-a</span> base/16386/16388</span></code></pre></div></div>
<pre><code>00000000: 0000 0000 a8ac d401 0000 0000 1c00 d81f  ................
00000010: 0020 0420 0000 0000 d89f 4800 0000 0000  . . ......H.....
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
00001fd0: 0000 0000 0000 0000 de02 0000 0000 0000  ................
00001fe0: 0000 0000 0000 0000 0100 0300 0208 1800  ................
00001ff0: 0100 0000 0942 6f62 1900 0000 0000 0000  .....Bob........</code></pre>
<p>This already matches the picture we had in mind. There are a few non-zero bytes at the start ( the page header and the line pointer ), a lot of empty space (zero bytes) in the middle ( the <code>*</code> means “the same line repeated” ), and a few non-zero bytes at the very end ( our tuple ). We can even see <code>Bob</code> sitting in the last line.</p>
<p>Now let’s decode these bytes properly.</p>
<hr>
</section>
<section id="decoding-the-page-header" class="level3">
<h3 class="anchored" data-anchor-id="decoding-the-page-header">Decoding the page header</h3>
<p>The page header, line pointers and tuples are all just C structs. So if we read the structs in the postgres source code, we can interpret the bytes.</p>
<p>The page header is <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/storage/bufpage.h#L155-L168"><code>PageHeaderData</code> in <code>bufpage.h</code></a>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb16-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">typedef</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> PageHeaderData</span>
<span id="cb16-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb16-3">    PageXLogRecPtr pd_lsn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-4">    uint16      pd_checksum<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-5">    uint16      pd_flags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-6">    LocationIndex pd_lower<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* offset to start of free space */</span></span>
<span id="cb16-7">    LocationIndex pd_upper<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* offset to end of free space */</span></span>
<span id="cb16-8">    LocationIndex pd_special<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* offset to start of special space */</span></span>
<span id="cb16-9">    uint16      pd_pagesize_version<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-10">    TransactionId pd_prune_xid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-11">    ItemIdData  pd_linp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>FLEXIBLE_ARRAY_MEMBER<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">];</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* line pointer array */</span></span>
<span id="cb16-12"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> PageHeaderData<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div></div>
<p>Everything except the line pointer array adds up to the first 24 bytes of the page. Let’s print just those:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb17-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">xxd</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> 24 base/16386/16388</span></code></pre></div></div>
<pre><code>00000000: 0000 0000 a8ac d401 0000 0000 1c00 d81f  ................
00000010: 0020 0420 0000 0000                      . . ....</code></pre>
<p>One thing to know before reading these: the numbers are stored in <strong>little endian</strong> order. In a multi-byte number, the least significant byte comes first. So the two bytes <code>1c 00</code> are the 16 bit number <code>0x001c</code>, and <code>d8 1f</code> is <code>0x1fd8</code>. The bits inside each byte are not reversed, only the order of the bytes.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Bytes</th>
<th>Field</th>
<th>Raw</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>0–7</td>
<td><code>pd_lsn</code></td>
<td><code>00000000 a8acd401</code></td>
<td><code>0/1D4ACA8</code>, the WAL position of the last change to this page</td>
</tr>
<tr class="even">
<td>8–9</td>
<td><code>pd_checksum</code></td>
<td><code>00 00</code></td>
<td>0, data checksums are off by default</td>
</tr>
<tr class="odd">
<td>10–11</td>
<td><code>pd_flags</code></td>
<td><code>00 00</code></td>
<td>0</td>
</tr>
<tr class="even">
<td>12–13</td>
<td><code>pd_lower</code></td>
<td><code>1c 00</code></td>
<td><strong>28</strong>, start of the free space</td>
</tr>
<tr class="odd">
<td>14–15</td>
<td><code>pd_upper</code></td>
<td><code>d8 1f</code></td>
<td><strong>8152</strong>, end of the free space</td>
</tr>
<tr class="even">
<td>16–17</td>
<td><code>pd_special</code></td>
<td><code>00 20</code></td>
<td>8192, the special space is empty</td>
</tr>
<tr class="odd">
<td>18–19</td>
<td><code>pd_pagesize_version</code></td>
<td><code>04 20</code></td>
<td><code>0x2004</code>: page size 8192 + layout version 4</td>
</tr>
<tr class="even">
<td>20–23</td>
<td><code>pd_prune_xid</code></td>
<td><code>00 00 00 00</code></td>
<td>0, nothing to prune yet</td>
</tr>
</tbody>
</table>
<p><code>pd_lower</code> and <code>pd_upper</code> tell the whole story of this page. The free space starts at byte 28, which is the 24 byte header plus one 4 byte line pointer. It ends at byte 8152, which is where our tuple starts. Everything in between, 8124 bytes, is free.</p>
<p>The LSN will be different on your machine. We’ll talk about LSNs when we look at the write ahead log.</p>
<hr>
</section>
<section id="decoding-the-line-pointer" class="level3">
<h3 class="anchored" data-anchor-id="decoding-the-line-pointer">Decoding the line pointer</h3>
<p>Right after the header, at byte 24, comes the line pointer array. A line pointer is the <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/storage/itemid.h#L25-L30"><code>ItemIdData</code> struct in <code>itemid.h</code></a>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb19-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">typedef</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> ItemIdData</span>
<span id="cb19-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb19-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">unsigned</span>    lp_off<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* offset to tuple (from start of page) */</span></span>
<span id="cb19-4">                lp_flags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* state of line pointer, see below */</span></span>
<span id="cb19-5">                lp_len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* byte length of tuple */</span></span>
<span id="cb19-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> ItemIdData<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div></div>
<p>It’s a single 32 bit number split into three bit fields: 15 bits for the offset of the tuple, 2 bits for flags, and 15 bits for the length of the tuple. We have one row, so we have one line pointer. Let’s read bytes 24 to 27 in binary:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb20-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">xxd</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> 24 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> 4 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-b</span> base/16386/16388</span></code></pre></div></div>
<pre><code>00000018: 11011000 10011111 01001000 00000000                    ..H.</code></pre>
<p>This is where it gets slightly confusing. Because of little endian, we first have to put the four bytes back in the right order, which means reversing them:</p>
<pre><code>00000000 01001000 10011111 11011000</code></pre>
<p>Now we can read the fields from the right, since <code>lp_off</code> is the lowest field:</p>
<pre><code> lp_len (15 bits)   lp_flags (2)   lp_off (15 bits)
 000000000100100       01        001111111011000</code></pre>
<ul>
<li><code>lp_off</code> = <code>001111111011000</code> = <strong>8152</strong>. The tuple starts at byte 8152.</li>
<li><code>lp_flags</code> = <code>01</code> = <strong>1</strong>, which is <code>LP_NORMAL</code>. This is a normal, live row.</li>
<li><code>lp_len</code> = <code>000000000100100</code> = <strong>36</strong>. The tuple is 36 bytes long.</li>
</ul>
<p>So our row starts at byte 8152 and is 36 bytes long. That means it ends at byte 8188, and the last 4 bytes of the page are just padding.</p>
<hr>
</section>
<section id="decoding-the-tuple" class="level3">
<h3 class="anchored" data-anchor-id="decoding-the-tuple">Decoding the tuple</h3>
<p>Now let’s read those 36 bytes:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb24-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">xxd</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> 8152 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> 36 base/16386/16388</span></code></pre></div></div>
<pre><code>00001fd8: de02 0000 0000 0000 0000 0000 0000 0000  ................
00001fe8: 0100 0300 0208 1800 0100 0000 0942 6f62  .............Bob
00001ff8: 1900 0000                                ....</code></pre>
<p>Every tuple starts with a header, defined as <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/access/htup_details.h#L153-L179"><code>HeapTupleHeaderData</code> in <code>htup_details.h</code></a>. The fields add up to 23 bytes. Postgres then rounds that up to 24, so that the column data starts at an aligned offset.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Bytes</th>
<th>Field</th>
<th>Raw</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>0–3</td>
<td><code>t_xmin</code></td>
<td><code>de 02 00 00</code></td>
<td>734, the transaction that inserted this row</td>
</tr>
<tr class="even">
<td>4–7</td>
<td><code>t_xmax</code></td>
<td><code>00 00 00 00</code></td>
<td>0, no transaction has deleted it</td>
</tr>
<tr class="odd">
<td>8–11</td>
<td><code>t_cid</code></td>
<td><code>00 00 00 00</code></td>
<td>0, the first command in its transaction</td>
</tr>
<tr class="even">
<td>12–17</td>
<td><code>t_ctid</code></td>
<td><code>0000 0000 0100</code></td>
<td>(0, 1): page 0, line pointer 1, the row points at itself</td>
</tr>
<tr class="odd">
<td>18–19</td>
<td><code>t_infomask2</code></td>
<td><code>03 00</code></td>
<td>3, the number of columns</td>
</tr>
<tr class="even">
<td>20–21</td>
<td><code>t_infomask</code></td>
<td><code>02 08</code></td>
<td><code>0x0802</code>: <code>HEAP_HASVARWIDTH</code> ( has a text column ) and <code>HEAP_XMAX_INVALID</code></td>
</tr>
<tr class="odd">
<td>22</td>
<td><code>t_hoff</code></td>
<td><code>18</code></td>
<td>24, the column data starts 24 bytes into the tuple</td>
</tr>
<tr class="even">
<td>23</td>
<td></td>
<td><code>00</code></td>
<td>padding</td>
</tr>
</tbody>
</table>
<p>You can check <code>t_xmin</code> from psql: <code>SELECT xmin, * FROM users;</code> returns <code>734</code>. Your number will be different.</p>
<p>The <code>t_ctid</code> field is the same ( page, line pointer ) pair that indexes use. Here we can see it in the bytes: <code>0000 0000</code> is block 0 and <code>0100</code> is line pointer 1.</p>
<p>After the header come the 12 bytes of actual data:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb26-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">xxd</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> 8176 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> 12 base/16386/16388</span></code></pre></div></div>
<pre><code>00001ff0: 0100 0000 0942 6f62 1900 0000            .....Bob....</code></pre>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Bytes</th>
<th>Column</th>
<th>Raw</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>0–3</td>
<td><code>id</code> ( int )</td>
<td><code>01 00 00 00</code></td>
<td>1</td>
</tr>
<tr class="even">
<td>4</td>
<td><code>name</code> length</td>
<td><code>09</code></td>
<td>a 1 byte length header</td>
</tr>
<tr class="odd">
<td>5–7</td>
<td><code>name</code> ( text )</td>
<td><code>42 6f 62</code></td>
<td>“Bob”</td>
</tr>
<tr class="even">
<td>8–11</td>
<td><code>age</code> ( int )</td>
<td><code>19 00 00 00</code></td>
<td>25</td>
</tr>
</tbody>
</table>
<p>The integers are easy: 4 bytes each, little endian. The text column is more interesting. Text has a variable length, so postgres needs to store the length too. For short values, it uses a single length byte: the total length shifted left by one, with the lowest bit set to 1 to mark it as a short header ( see <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/include/varatt.h#L236-L237"><code>SET_VARSIZE_1B</code> in <code>varatt.h</code></a> ). “Bob” is 3 bytes, plus 1 for the header, is 4. <code>(4 &lt;&lt; 1) | 1 = 9</code>, which is the <code>09</code> we see.</p>
<p>And that’s our row: <code>(1, 'Bob', 25)</code>, found on disk, byte by byte.</p>
<hr>
</section>
<section id="the-easy-way-pageinspect" class="level3">
<h3 class="anchored" data-anchor-id="the-easy-way-pageinspect">The easy way: pageinspect</h3>
<p>Decoding bytes by hand is tedious, and nobody really needs to do it. But I think it’s awesome to do it once. But postgres comes with an extension called <code>pageinspect</code> that does all of this for us:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb28-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">CREATE</span> EXTENSION pageinspect;</span>
<span id="cb28-2"></span>
<span id="cb28-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lower</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">upper</span>, special, pagesize, version</span>
<span id="cb28-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> page_header(get_raw_page(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'users'</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>));</span></code></pre></div></div>
<pre><code> lower | upper | special | pagesize | version
-------+-------+---------+----------+---------
    28 |  8152 |    8192 |     8192 |       4</code></pre>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb30" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb30-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> lp, lp_off, lp_flags, lp_len, t_xmin, t_ctid, t_hoff, t_data</span>
<span id="cb30-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> heap_page_items(get_raw_page(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'users'</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>));</span></code></pre></div></div>
<pre><code> lp | lp_off | lp_flags | lp_len | t_xmin | t_ctid | t_hoff |           t_data
----+--------+----------+--------+--------+--------+--------+----------------------------
  1 |   8152 |        1 |     36 |    734 | (0,1)  |     24 | \x0100000009426f6219000000</code></pre>
<p>Every number matches what we decoded by hand. There is also <a href="https://github.com/df7cb/pg_filedump"><code>pg_filedump</code></a>, a command line tool that can read the files directly, without a running server.</p>
<hr>
</section>
<section id="summary" class="level3">
<h3 class="anchored" data-anchor-id="summary">Summary</h3>
<p>Postgres stores the data of a table in segment files of up to 1GB. Each segment file is made of 8KB pages, and the page is the unit of IO in postgres. Each page has a 24 byte header, an array of line pointers growing from the top, the tuples growing from the bottom, and free space in between.</p>
<p>When we insert a row, postgres looks for a page with enough free space. If there isn’t one, it adds a new page to the end of the table’s file. Once the file reaches 1GB, a new segment file is started and the process repeats.</p>
<hr>
</section>
<section id="what-i-left-out" class="level3">
<h3 class="anchored" data-anchor-id="what-i-left-out">What I left out</h3>
<p>A few things that didn’t fit in this post:</p>
<ul>
<li><strong>Forks.</strong> Right now our table has a single file. Over time, postgres also creates <code>16388_fsm</code> ( the free space map, which helps postgres find a page with enough room for a new row ) and <code>16388_vm</code> ( the visibility map, which helps <code>VACUUM</code> skip pages and makes index-only scans possible ). These usually show up after the first <code>VACUUM</code>. The free space map gets its own post next.</li>
<li><strong>TOAST.</strong> Large values, like a long text column, don’t fit in an 8KB page. Postgres compresses them and/or moves them to a separate TOAST table, and the row only keeps a pointer.</li>
<li><strong>NULLs.</strong> If a row has NULL values, the tuple header gets an extra bitmap marking which columns are NULL, and <code>t_hoff</code> grows to make room for it.</li>
<li><strong>Alignment.</strong> Columns are aligned based on their type. An <code>int</code> must start at a multiple of 4 bytes, for example. In our row that worked out with no padding, but column order can waste space in other tables.</li>
<li><strong>Updates.</strong> An <code>UPDATE</code> in postgres doesn’t change the row in place. It writes a new version of the row and sets <code>t_xmax</code> and <code>t_ctid</code> on the old one. That’s a topic for another post.</li>
</ul>
<hr>
</section>
<section id="credits-and-resources" class="level3">
<h3 class="anchored" data-anchor-id="credits-and-resources">Credits and Resources</h3>
<p>For this post, I have heavily used Claude Code. Along with that, I have relied on the following resources:</p>
<ol type="1">
<li><a href="https://www.postgresql.org/docs/17/storage-file-layout.html">Database File Layout</a> and <a href="https://www.postgresql.org/docs/17/storage-page-layout.html">Database Page Layout</a>, PostgreSQL docs</li>
<li><a href="https://15445.courses.cs.cmu.edu/fall2025/">Intro to Database Systems by CMU</a></li>
<li><a href="https://github.com/postgres/postgres/tree/REL_17_6">Postgres Source Code</a></li>
</ol>
<hr>


</section>
</section>

 ]]></description>
  <category>postgres</category>
  <category>internals</category>
  <guid>https://thewalstreetjournal.github.io/posts/how-postgres-stores-data/</guid>
  <pubDate>Sat, 28 Mar 2026 18:30:00 GMT</pubDate>
  <media:content url="https://thewalstreetjournal.github.io/posts/how-postgres-stores-data/page-layout.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>How Does Postgres Handle Connections?</title>
  <link>https://thewalstreetjournal.github.io/posts/how-postgres-handles-connections/</link>
  <description><![CDATA[ 






<p>Postgres works on a client-server model. A server process runs in a loop, waiting for clients to connect. Every time a client connects, Postgres starts a new operating-system process just for that client, and that process handles all of the client’s queries until it disconnects.</p>
<p>In this post, we’ll watch this happen inside a Docker container, look at what it means for scaling Postgres, and find the place in the source code where the new process gets created.</p>
<p>Prefer video? Here’s the same walkthrough:</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/iJHdUqGNBlk" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<hr>
<section id="prerequisites" class="level3">
<h3 class="anchored" data-anchor-id="prerequisites">Prerequisites</h3>
<p>For this post, I’m assuming you:</p>
<ul>
<li>Know what Docker containers are and how to build and run them.</li>
<li>Are familiar with processes and threads.</li>
</ul>
<p>We’ll use two containers: one running Postgres, and one with a small Python script that connects to it. Create a folder with these three files.</p>
<div class="callout callout-style-default callout-note callout-titled" title="Dockerfile (Postgres)">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Dockerfile (Postgres)
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode dockerfile code-with-copy"><code class="sourceCode dockerfile"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> postgres:17.6</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">USER</span> root</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># procps gives us `ps` inside the container</span></span>
<span id="cb1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">RUN</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">apt-get</span> update <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;&amp;</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">apt-get</span> install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-y</span> procps <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rm</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-rf</span> /var/lib/apt/lists/<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">*</span></span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># A user and database for the Python client to connect to</span></span>
<span id="cb1-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">RUN</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">echo</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CREATE USER myuser WITH PASSWORD 'mypassword'; CREATE DATABASE mydatabase OWNER myuser;"</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-11">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> /docker-entrypoint-initdb.d/init.sql</span>
<span id="cb1-12"></span>
<span id="cb1-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">EXPOSE</span> 5432</span>
<span id="cb1-14"></span>
<span id="cb1-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">USER</span> postgres</span></code></pre></div></div>
</div>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled" title="Dockerfile.client (Python client)">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Dockerfile.client (Python client)
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode dockerfile code-with-copy"><code class="sourceCode dockerfile"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> python:3.12-slim</span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">RUN</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install psycopg2-binary</span>
<span id="cb2-4"></span>
<span id="cb2-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">COPY</span> connect.py /app/connect.py</span>
<span id="cb2-6"></span>
<span id="cb2-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">ENTRYPOINT</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"python"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/app/connect.py"</span>]</span></code></pre></div></div>
</div>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled" title="connect.py">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-3-contents" aria-controls="callout-3" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>connect.py
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-3" class="callout-3-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span>
<span id="cb3-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> psycopg2</span>
<span id="cb3-5"></span>
<span id="cb3-6">conn_params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb3-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"host"</span>:     os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PG_HOST"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"postgres"</span>),</span>
<span id="cb3-8">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"port"</span>:     os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PG_PORT"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"5432"</span>),</span>
<span id="cb3-9">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dbname"</span>:   os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PG_DB"</span>,   <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mydatabase"</span>),</span>
<span id="cb3-10">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>:     os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PG_USER"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"myuser"</span>),</span>
<span id="cb3-11">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"password"</span>: os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PG_PASSWORD"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mypassword"</span>),</span>
<span id="cb3-12">}</span>
<span id="cb3-13"></span>
<span id="cb3-14">cmd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sys.argv[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(sys.argv) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span></span>
<span id="cb3-15"></span>
<span id="cb3-16"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> cmd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"connect"</span>:</span>
<span id="cb3-17">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Open a connection, hold it for 30 seconds, then close it</span></span>
<span id="cb3-18">    conn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> psycopg2.<span class="ex" style="color: null;
background-color: null;
font-style: inherit;">connect</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>conn_params)</span>
<span id="cb3-19">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Connected to postgres successfully."</span>)</span>
<span id="cb3-20">    time.sleep(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>)</span>
<span id="cb3-21">    conn.close()</span>
<span id="cb3-22"></span>
<span id="cb3-23"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> cmd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"select"</span>:</span>
<span id="cb3-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Run a query that takes 30 seconds</span></span>
<span id="cb3-25">    conn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> psycopg2.<span class="ex" style="color: null;
background-color: null;
font-style: inherit;">connect</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>conn_params)</span>
<span id="cb3-26">    cur <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> conn.cursor()</span>
<span id="cb3-27">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Running SELECT pg_sleep(30) ..."</span>)</span>
<span id="cb3-28">    cur.execute(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SELECT pg_sleep(30)"</span>)</span>
<span id="cb3-29">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Done."</span>)</span>
<span id="cb3-30">    cur.close()</span>
<span id="cb3-31">    conn.close()</span>
<span id="cb3-32"></span>
<span id="cb3-33"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb3-34">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Usage: python connect.py [connect|select]"</span>)</span>
<span id="cb3-35">    sys.exit(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
</div>
</div>
</div>
<p>All the code for this post is also on <a href="https://github.com/thewalstreetjournal/postgres-playlist/tree/main/001-how-postgres-handles-connections">GitHub</a>.</p>
<p>Then build the images and start Postgres:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build the Postgres and Python client images</span></span>
<span id="cb4-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-t</span> postgres-17.6:v1 .</span>
<span id="cb4-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-f</span> Dockerfile.client <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-t</span> pg-client:v1 .</span>
<span id="cb4-4"></span>
<span id="cb4-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create a network so the client container can reach Postgres by name</span></span>
<span id="cb4-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> network create pgnet</span>
<span id="cb4-7"></span>
<span id="cb4-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Start Postgres</span></span>
<span id="cb4-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--name</span> postgres <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--network</span> pgnet <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span> POSTGRES_PASSWORD=secret postgres-17.6:v1</span></code></pre></div></div>
<hr>
</section>
<section id="walkthrough" class="level3">
<h3 class="anchored" data-anchor-id="walkthrough">Walkthrough</h3>
<p>Now that we have the docker containers built, let’s begin. Let’s exec into the postgres container:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> exec <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-it</span> postgres bash</span></code></pre></div></div>
<p>Once inside, we can list all the processes running in our container by doing <code>ps aux</code>. We get the following output:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres@0f41997a8b1b:/$</span> ps aux</span>
<span id="cb6-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">USER</span>         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND</span>
<span id="cb6-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>       1  0.0  0.1 217536 28556 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres</span>
<span id="cb6-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      49  0.0  0.0 217668 10088 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: checkpointer </span>
<span id="cb6-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      50  0.0  0.0 217692  7436 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: background writer </span>
<span id="cb6-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      52  0.0  0.0 217536 10264 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: walwriter </span>
<span id="cb6-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      53  0.0  0.0 219112  9136 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: autovacuum launcher </span>
<span id="cb6-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      54  0.0  0.0 219120  8276 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: logical replication launcher </span>
<span id="cb6-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>     399  1.3  0.0   7352  4096 pts/0    Ss   06:51   0:00 bash</span>
<span id="cb6-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>     406  0.0  0.0   9392  4116 pts/0    R+   06:51   0:00 ps aux</span></code></pre></div></div>
<p>Already a lot seems to be going on, even though we have just freshly started this container. There are several processes already running. We’ll get to the other processes later in the series, but for now the most important process for us is the one with PID 1. This is the <strong>postmaster</strong>. When we start postgres, the postmaster process is started. It then spawns the other postgres processes that we see listed above.</p>
<p>The postmaster process has a key responsibility: it runs an infinite loop, waiting for new connections, and “handles” the incoming connections. Of course, there are other things as well that this postmaster process does, but for the purposes of this blog, we’ll focus on connection handling.</p>
<p>Now, let’s try to connect to postgres using the python application. In a second terminal (outside the container), run:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--rm</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--network</span> pgnet pg-client:v1 connect</span></code></pre></div></div>
<p>This is going to run the python application that will try to connect to the postgres, hold the connection open for 30 seconds and then close it. From the postgres server terminal, if we again run <code>ps aux</code>, we get a new entry now, the process with PID 432:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb8-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres@0f41997a8b1b:/$</span> ps aux</span>
<span id="cb8-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">USER</span>         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND</span>
<span id="cb8-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>       1  0.0  0.1 217536 28556 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres</span>
<span id="cb8-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      49  0.0  0.0 217668 10088 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: checkpointer </span>
<span id="cb8-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      50  0.0  0.0 217692  7436 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: background writer </span>
<span id="cb8-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      52  0.0  0.0 217536 10264 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: walwriter </span>
<span id="cb8-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      53  0.0  0.0 219112  9136 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: autovacuum launcher </span>
<span id="cb8-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>      54  0.0  0.0 219120  8276 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   Mar12   0:00 postgres: logical replication launcher </span>
<span id="cb8-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>     426  0.6  0.0   7352  4060 pts/0    Ss   06:57   0:00 bash</span>
<span id="cb8-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>     432  0.0  0.0 219648 12960 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">?</span>        Ss   06:57   0:00 postgres: myuser mydatabase 172.18.0.3<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">48278</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">idle</span></span>
<span id="cb8-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">postgres</span>     433  0.0  0.0   9392  4136 pts/0    R+   06:57   0:00 ps aux</span></code></pre></div></div>
<p>When our python application tried to connect to postgres server, the postmaster process jumped to action and created a new process to handle that connection request. From the name of the process, we can interpret a few things:</p>
<ul>
<li><code>postgres:</code> - this is the prefix attached to all the processes spawned by postmaster</li>
<li><code>myuser</code> - the name of the user that connected to postgres</li>
<li><code>mydatabase</code> - the name of the database to which the user connected</li>
<li><code>172.18.0.3(48278)</code> - the host and port of the python application i.e.&nbsp;the client</li>
<li><code>idle</code> - the state of the connection. Currently, the connection is sitting idle, without any work to do.</li>
</ul>
<p>We can see the same thing from inside Postgres. There is a view in Postgres called <code>pg_stat_activity</code>, which has one row per backend process:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">psql</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-U</span> myuser <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span> mydatabase <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SELECT pid, usename, datname, client_addr, state FROM pg_stat_activity WHERE backend_type = 'client backend';"</span></span></code></pre></div></div>
<p>The <code>pid</code> column matches the process ID from <code>ps aux</code>. You’ll also see a second row for the <code>psql</code> session we just opened, because it’s a connection too, with its own backend process.</p>
<p>If we try to simulate a select query that runs for 30 seconds using:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--rm</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--network</span> pgnet pg-client:v1 select</span></code></pre></div></div>
<p>we see the state of the connection change from <code>idle</code> to <code>SELECT</code>. This is postgres indicating to us that the handler process is currently executing a SELECT request sent by the client.</p>
<p>If we open another terminal and again try to connect to postgres server, we will see another process is being spawned. This is a key point: for every connection, the postgres server is going to have a process handling that connection. This process has its own stack, memory space, and the context switching overhead.</p>
<p>At the same time, this process is independent of the other connections. If it exits or runs into an error, the postgres server continues to function as before. ( Note that a real crash, like a segfault or the Linux OOM killer killing the process, is different: postgres then stops all the processes and restarts. )</p>
<p>Postgres runs on a process-per-connection model. For every new connection, postgres is going to spawn a new process to handle it. When the connection is terminated, the process exits. Now, many modern databases do not follow multi-process model, but rather the multi-threaded model. For each new connection, they’re gonna spawn (reuse from the threadpool) some threads. This is lighter than the process based approach but if not carefully designed, can cause the database to crash.</p>
<p>The multi-process model of postgres has important implications on how we scale postgres.</p>
<p>If we have an application with thousands of users who want to connect to the database, the postgres server is going to end up spawning thousands of processes. This is going to hamper the performance of our database. We simply cannot have a case where we are spawning thousands of processes just to handle connections.</p>
<p>To get around this, we can do several things. The <a href="https://openai.com/index/scaling-postgresql/">OpenAI blog post</a> lists a bunch of things they did to scale postgres to serve 800 million users. We’ll focus on a couple of them:</p>
<ol type="1">
<li><p>Setting <code>max_connections</code> parameter: at the most basic level, we can restrict the number of connections postgres will accept. By default, this is set to 100. After this limit is reached, the clients trying to connect will get an error. Out of these 100, a few connections (3 by default, set by <code>superuser_reserved_connections</code>) are kept aside for superusers, so that an admin can still log in and fix things even when all the other connections are used up. This safeguards the postgres server but doesn’t serve the clients well. So, we look at the next option.</p></li>
<li><p>Using a connection pooler like PgBouncer. In such a setup, the clients try to connect to the pooler and the pooler maintains a small pool of connections with postgres. As a result, from our application’s point of view, there could be thousands of connections but from the point of view of the database, there are only a handful of connections.</p></li>
</ol>
<hr>
</section>
<section id="source-code-walkthrough" class="level3">
<h3 class="anchored" data-anchor-id="source-code-walkthrough">Source Code Walkthrough</h3>
<p>Let’s see where this happens in the postgres source code. I’m using the <code>REL_17_6</code> branch here, so the line numbers in the links match postgres 17.6.</p>
<p>The postmaster runs an infinite loop called <code>ServerLoop</code> in <code>postmaster.c</code>. When a new client connects, the loop accepts the connection and calls <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/postmaster/postmaster.c#L3545"><code>BackendStartup</code></a>. This function is called once for every new connection. It does some bookkeeping for the new backend, like generating a cancel key, and then calls <a href="https://github.com/postgres/postgres/blob/REL_17_6/src/backend/postmaster/launch_backend.c#L231"><code>postmaster_child_launch</code></a>.</p>
<p><code>postmaster_child_launch</code> lives in <code>launch_backend.c</code>, and this is where the fork actually happens:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb11-1">pid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fork_process<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">();</span></span>
<span id="cb11-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>pid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span>               <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* child */</span></span>
<span id="cb11-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb11-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* Close the postmaster's sockets */</span></span>
<span id="cb11-5">    ClosePostmasterPorts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>child_type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> B_LOGGER<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span></span>
<span id="cb11-6">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span></span></code></pre></div></div>
<p><code>fork_process()</code> is a small wrapper around the <code>fork()</code> system call. After the fork, there are two processes running the same code. In the child (<code>pid == 0</code>), the first thing it does is close the postmaster’s listening sockets, since it doesn’t need them. But it keeps the socket for its own client. This is the “copy of the socket” I mentioned earlier: <code>fork()</code> copies all the open file descriptors of the parent, so the child gets the client’s connection for free.</p>
<p>That’s it- one <code>fork()</code> per connection. Every backend process we saw in <code>ps aux</code> was created here.</p>
<hr>
</section>
<section id="credits-and-resources" class="level3">
<h3 class="anchored" data-anchor-id="credits-and-resources">Credits and Resources</h3>
<p>For this post, I have heavily used Claude Code. Along with that, I have relied on the following resources:</p>
<ol type="1">
<li><a href="https://15445.courses.cs.cmu.edu/fall2025/">Intro to Database Systems by CMU</a></li>
<li><a href="https://github.com/postgres/postgres/tree/REL_17_6">Postgres Source Code</a></li>
</ol>
<hr>


</section>

 ]]></description>
  <category>postgres</category>
  <category>internals</category>
  <guid>https://thewalstreetjournal.github.io/posts/how-postgres-handles-connections/</guid>
  <pubDate>Sat, 14 Mar 2026 18:30:00 GMT</pubDate>
  <media:content url="https://thewalstreetjournal.github.io/posts/how-postgres-handles-connections/process-per-connection.png" medium="image" type="image/png" height="81" width="144"/>
</item>
</channel>
</rss>
