<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://masahikosawada.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://masahikosawada.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-08-18T16:03:21+09:00</updated><id>https://masahikosawada.github.io/feed.xml</id><title type="html">MasahikoSawada</title><subtitle>PostgreSQLメジャーコントリビュータ／コミッタ Masahiko Sawada のブログ。 VACUUM、MVCC、論理レプリケーション、トランザクション管理、インデックスなど PostgreSQL の内部構造とソースコードを日本語で解説しています。</subtitle><author><name>Masahiko Sawada</name></author><entry xml:lang="en"><title type="html">An interactive TUI for reading PostgreSQL WAL (pg_walview)</title><link href="https://masahikosawada.github.io/2026/08/17/pg_walview/" rel="alternate" type="text/html" title="An interactive TUI for reading PostgreSQL WAL (pg_walview)" /><published>2026-08-17T00:00:00+09:00</published><updated>2026-08-17T00:00:00+09:00</updated><id>https://masahikosawada.github.io/2026/08/17/pg_walview</id><content type="html" xml:base="https://masahikosawada.github.io/2026/08/17/pg_walview/"><![CDATA[<p>A while ago I wrote <a href="https://github.com/MasahikoSawada/pg_walview">pg_walview</a>, a TUI for reading PostgreSQL WAL (Write-Ahead Log) files. It does not print everything at once the way <code class="language-plaintext highlighter-rouge">pg_waldump</code> does, and instead lets you move a cursor through the records one at a time in your terminal, which is the whole point of it. It is written in Rust.</p>

<video src="/images/2026-08-18/pg_walview-demo.mp4" poster="/images/2026-08-18/pg_walview-demo-poster.jpg" width="1200" height="600" autoplay="" muted="" loop="" playsinline="" preload="none" style="max-width:100%;height:auto" aria-label="pg_walview opening a WAL file, moving through records and showing the record details and the hex dump">
</video>

<p><code class="language-plaintext highlighter-rouge">pg_waldump</code> can filter in most of the ways you would want. <code class="language-plaintext highlighter-rouge">-x</code> for a transaction ID, <code class="language-plaintext highlighter-rouge">-r</code> for a resource manager, <code class="language-plaintext highlighter-rouge">-R</code> and <code class="language-plaintext highlighter-rouge">-B</code> for a relation and a block. But once you filter, you lose sight of what was going on around the records you kept. I kept running it with <code class="language-plaintext highlighter-rouge">-x</code> to find the transaction I cared about, then running it again without <code class="language-plaintext highlighter-rouge">-x</code> because I wanted to see what else was going on around those records, and after doing that a few too many times I thought it would be nicer to load the whole segment up front and change the view in place instead.</p>

<h2 id="the-panes">The Panes</h2>

<p>The screen is split into three panes. <code class="language-plaintext highlighter-rouge">Tab</code> moves the focus between them.</p>

<h3 id="wal-records-left">WAL records (left)</h3>

<p>A list of the records. LSN, XID, record length, whether the record carries a full-page image, resource manager, a description.</p>

<p>The graph line down the left edge is the part I wanted most. The records sharing the transaction ID of the record under the cursor are connected by it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    0/100 13840   171  Heap      UPDATE
 ┏━ 0/100 13841  8135  Heap      LOCK        &lt;- first record of this XID
 ┃  0/100 13839  8135  Heap      LOCK
 ┣━ 0/100 13841   171  Heap      UPDATE      &lt;- cursor
 ┃  0/100 13840  8135  Heap      LOCK
 ┃  0/100 13839   171  Heap      UPDATE
 ┣━ 0/100 13841    64  Btree     INSERT_LEAF
 ┗━ 0/100 13841    34  Transact  COMMIT      &lt;- last record of this XID
</code></pre></div></div>

<p>The records of other transactions stay where they are, so you can follow one transaction and still see everything that happened in between. <code class="language-plaintext highlighter-rouge">s</code> and <code class="language-plaintext highlighter-rouge">r</code> jump to the next and previous record with the same XID<sup id="fnref:xid" role="doc-noteref"><a href="#fn:xid" class="footnote" rel="footnote">1</a></sup>.</p>

<p>The colour of the line comes from the last record of that XID. That is, from how the transaction ended.</p>

<table>
  <thead>
    <tr>
      <th>State</th>
      <th>Condition</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Committed</td>
      <td>ends with <code class="language-plaintext highlighter-rouge">COMMIT</code> or <code class="language-plaintext highlighter-rouge">COMMIT_PREPARED</code></td>
    </tr>
    <tr>
      <td>Aborted</td>
      <td>ends with <code class="language-plaintext highlighter-rouge">ABORT</code> or <code class="language-plaintext highlighter-rouge">ABORT_PREPARED</code></td>
    </tr>
    <tr>
      <td>Pending</td>
      <td>ends with a Transaction record that is neither a commit nor an abort, such as <code class="language-plaintext highlighter-rouge">PREPARE</code></td>
    </tr>
    <tr>
      <td>Unfinished</td>
      <td>does not end with a Transaction record at all</td>
    </tr>
  </tbody>
</table>

<p>That last one turns out to be quietly useful. It means the transaction does not finish inside this WAL file, the rest of it is in the next segment, and so you know whether you need to open another file before you go looking for a commit that is not there.</p>

<p>Only the 16 ANSI colours are used, by the way. What they look like is up to your terminal theme. <code class="language-plaintext highlighter-rouge">NO_COLOR</code> turns them off.</p>

<h3 id="details-top-right">DETAILS (top right)</h3>

<p>The details of the selected record. The <code class="language-plaintext highlighter-rouge">XLogRecord</code> header, the block references (<code class="language-plaintext highlighter-rouge">RelFileLocator</code>, fork, block number, flags), and the fields decoded per resource manager.</p>

<p>It is an accordion, and <code class="language-plaintext highlighter-rouge">Enter</code> expands or collapses a block. For a Heap <code class="language-plaintext highlighter-rouge">UPDATE</code> it expands <code class="language-plaintext highlighter-rouge">t_infomask</code> and <code class="language-plaintext highlighter-rouge">t_infomask2</code> of <code class="language-plaintext highlighter-rouge">xl_heap_header</code> into flag names, so checking whether something like <code class="language-plaintext highlighter-rouge">HEAP_XMAX_INVALID</code> is set no longer means leaving the tool and looking the bits up. Handy.</p>

<h3 id="hex-dump-bottom-right">HEX DUMP (bottom right)</h3>

<p>The bytes of the whole WAL segment. Each line is addressed twice, by its LSN and by its offset in the file, which means you can read a record and see which of the 8kB pages it happens to be sitting on without doing the arithmetic yourself.</p>

<p>Only the bytes of the selected record get colour. And they are coloured by what they are: the <code class="language-plaintext highlighter-rouge">XLogRecord</code> header, the descriptors, a full-page image, block data, main data. A record crossing a page boundary is coloured as the two or more pieces it really occupies. The page header that splits it is not part of the record, so it stays uncoloured.</p>

<p>Whatever the DETAILS cursor sits on is picked out in the dump too, so the accordion doubles as a navigator over the bytes. Having the decoded fields and the raw bytes next to each other also helped while writing the <code class="language-plaintext highlighter-rouge">*desc.c</code> equivalents<sup id="fnref:desc" role="doc-noteref"><a href="#fn:desc" class="footnote" rel="footnote">2</a></sup>.</p>

<h2 id="usage">Usage</h2>

<p>If <code class="language-plaintext highlighter-rouge">pg_config</code> is in your <code class="language-plaintext highlighter-rouge">PATH</code>, the build needs nothing else. The definitions of <code class="language-plaintext highlighter-rouge">XLogRecord</code> and friends come from the server headers at build time, so those headers have to be there<sup id="fnref:version" role="doc-noteref"><a href="#fn:version" class="footnote" rel="footnote">3</a></sup>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/MasahikoSawada/pg_walview.git
<span class="nb">cd </span>pg_walview
cargo build <span class="nt">--release</span>

<span class="c"># If PostgreSQL is installed somewhere of your own</span>
<span class="nv">PG_INCLUDE_DIR</span><span class="o">=</span>/path/to/pgsql/include/server cargo build <span class="nt">--release</span>
</code></pre></div></div>

<p>Then pass the path to a WAL file. It does not connect to a server, so the instance does not have to be running, and a file you copied off a machine somewhere is enough.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pg_walview /path/to/pg_wal/000000010000000000000001
</code></pre></div></div>

<p>The main keybindings:</p>

<table>
  <thead>
    <tr>
      <th>Key</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">j</code> / <code class="language-plaintext highlighter-rouge">k</code></td>
      <td>Next / previous record</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">g</code> / <code class="language-plaintext highlighter-rouge">G</code></td>
      <td>First / last record</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">s</code> / <code class="language-plaintext highlighter-rouge">r</code></td>
      <td>Next / previous record with the same XID</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Space</code> / <code class="language-plaintext highlighter-rouge">-</code></td>
      <td>Page down / page up</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Tab</code></td>
      <td>Switch pane</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">q</code></td>
      <td>Quit</td>
    </tr>
  </tbody>
</table>

<h2 id="limitations">Limitations</h2>

<ul>
  <li>Only one segment file can be open at a time. A transaction spanning segments cannot be followed all the way. The “unfinished” state of the graph line is the other side of this limitation.</li>
  <li>Every record in the file is read into memory. Segments are 16MB by default, so this has not bothered me yet, but it may start to matter with a larger <code class="language-plaintext highlighter-rouge">--wal-segsize</code>. The segment size itself is read from the long page header, so a non-default one is read correctly.</li>
  <li>The per-resource-manager decoders are incomplete. Heap, Heap2, Btree, Transaction and XLOG are done. GiST, GIN, SP-GiST and others still just report the size of the main data.</li>
  <li>Compressed full-page images (FPI) are not decompressed. The compression method is shown, but not the contents.</li>
</ul>

<h2 id="wrapping-up">Wrapping Up</h2>

<p><code class="language-plaintext highlighter-rouge">pg_waldump</code> and <a href="https://www.postgresql.org/docs/18/pgwalinspect.html">pg_walinspect</a> are both there for looking inside WAL, and they have more features. pg_walview does one thing. It lets you move around.</p>

<p>In my case I spend a fair amount of time on logical replication debugging. Where did this transaction commit? What came between these two records? For that kind of question it has been worth writing.</p>

<p>The source is on <a href="https://github.com/MasahikoSawada/pg_walview">GitHub</a>, under the MIT License. Contributions are welcome!</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:xid" role="doc-endnote">
      <p>The jump is disabled when the XID is 0, 1 or 2. Those are <code class="language-plaintext highlighter-rouge">InvalidTransactionId</code>, <code class="language-plaintext highlighter-rouge">BootstrapTransactionId</code> and <code class="language-plaintext highlighter-rouge">FrozenTransactionId</code>, and since a lot of records carry no XID at all, connecting them would not mean anything. <a href="#fnref:xid" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:desc" role="doc-endnote">
      <p>The per-resource-manager decoding does the same job as the <code class="language-plaintext highlighter-rouge">*desc.c</code> files under <code class="language-plaintext highlighter-rouge">src/backend/access/rmgrdesc/</code> in PostgreSQL itself. I kept the structure close to that, on the assumption that it should be easier to follow upstream changes later. <a href="#fnref:desc" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:version" role="doc-endnote">
      <p>Which also means a binary only reads WAL from the version it was built against. Opening a segment from another version reports a page magic mismatch rather than misreading it. <code class="language-plaintext highlighter-rouge">--version</code> prints which PostgreSQL a given binary was built for. <a href="#fnref:version" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Masahiko Sawada</name></author><category term="PostgreSQL" /><category term="WAL" /><category term="Rust" /><summary type="html"><![CDATA[pg_walview is a TUI for reading PostgreSQL WAL files interactively. It draws a graph line through the records that share a transaction ID, so a single transaction can be followed without filtering the surrounding records away.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://masahikosawada.github.io/assets/images/og-default.png" /><media:content medium="image" url="https://masahikosawada.github.io/assets/images/og-default.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">PostgreSQL 18 supports UUIDv7</title><link href="https://masahikosawada.github.io/2025/09/04/UUIDv7-in-PostgreSQL/" rel="alternate" type="text/html" title="PostgreSQL 18 supports UUIDv7" /><published>2025-09-04T00:00:00+09:00</published><updated>2025-09-04T00:00:00+09:00</updated><id>https://masahikosawada.github.io/2025/09/04/UUIDv7-in-PostgreSQL</id><content type="html" xml:base="https://masahikosawada.github.io/2025/09/04/UUIDv7-in-PostgreSQL/"><![CDATA[<p>UUIDv7 was defined in <a href="https://www.rfc-editor.org/rfc/rfc9562.html">RFC 9562</a>. There are eight versions of UUID, and while they all have a size of 128 bits, the data stored in each version is different. Until I learned about UUIDv7, when I thought of UUIDs, I imagined random data - but that’s version 4 UUID. The version 7 UUID (UUIDv7) has a significant feature: sortability thanks to a timestamp stored at the beginning of the data.</p>

<p>When you compare them, the difference is obvious:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">=#</span> <span class="k">select</span> <span class="n">uuidv4</span><span class="p">(),</span> <span class="n">uuidv7</span><span class="p">()</span> <span class="k">from</span> <span class="n">generate_series</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
                <span class="n">uuidv4</span>                <span class="o">|</span>                <span class="n">uuidv7</span>
<span class="c1">--------------------------------------+--------------------------------------</span>
 <span class="mi">216</span><span class="n">aae4c</span><span class="o">-</span><span class="mi">6</span><span class="n">b02</span><span class="o">-</span><span class="mi">4</span><span class="n">bea</span><span class="o">-</span><span class="n">bd84</span><span class="o">-</span><span class="mi">7</span><span class="n">fea9617e0cc</span> <span class="o">|</span> <span class="mi">01991632</span><span class="o">-</span><span class="n">e3a0</span><span class="o">-</span><span class="mi">7468</span><span class="o">-</span><span class="n">ac42</span><span class="o">-</span><span class="mi">26</span><span class="n">afbc51df65</span>
 <span class="mi">55372</span><span class="n">a64</span><span class="o">-</span><span class="mi">0351</span><span class="o">-</span><span class="mi">40</span><span class="n">a1</span><span class="o">-</span><span class="n">a32c</span><span class="o">-</span><span class="mi">318</span><span class="n">e581f4561</span> <span class="o">|</span> <span class="mi">01991632</span><span class="o">-</span><span class="n">e3a0</span><span class="o">-</span><span class="mi">748</span><span class="n">a</span><span class="o">-</span><span class="n">bb4c</span><span class="o">-</span><span class="mi">9882</span><span class="n">ddaf0721</span>
 <span class="n">e154c7a7</span><span class="o">-</span><span class="mi">4</span><span class="n">a6b</span><span class="o">-</span><span class="mi">4446</span><span class="o">-</span><span class="mi">96</span><span class="n">be</span><span class="o">-</span><span class="mi">6</span><span class="n">a1cb84b773e</span> <span class="o">|</span> <span class="mi">01991632</span><span class="o">-</span><span class="n">e3a0</span><span class="o">-</span><span class="mi">749</span><span class="n">f</span><span class="o">-</span><span class="n">b429</span><span class="o">-</span><span class="n">d2ef56b9683e</span>
 <span class="mi">59</span><span class="n">adba82</span><span class="o">-</span><span class="mi">8</span><span class="n">a88</span><span class="o">-</span><span class="mi">4</span><span class="n">f4f</span><span class="o">-</span><span class="n">b859</span><span class="o">-</span><span class="mi">036430</span><span class="n">d09045</span> <span class="o">|</span> <span class="mi">01991632</span><span class="o">-</span><span class="n">e3a0</span><span class="o">-</span><span class="mi">74</span><span class="n">b5</span><span class="o">-</span><span class="mi">9</span><span class="n">a86</span><span class="o">-</span><span class="mi">8107</span><span class="n">f1851200</span>
 <span class="n">be096cb4</span><span class="o">-</span><span class="n">ee48</span><span class="o">-</span><span class="mi">4</span><span class="n">bf8</span><span class="o">-</span><span class="n">ae30</span><span class="o">-</span><span class="mi">3</span><span class="n">ca6d09af786</span> <span class="o">|</span> <span class="mi">01991632</span><span class="o">-</span><span class="n">e3a0</span><span class="o">-</span><span class="mi">74</span><span class="n">c9</span><span class="o">-</span><span class="n">a08a</span><span class="o">-</span><span class="mi">1</span><span class="n">ec588e5a60d</span>
<span class="p">(</span><span class="mi">5</span> <span class="k">rows</span><span class="p">)</span>
</code></pre></div></div>

<p>UUIDv7 offers many advantages when used as a primary key in databases. PostgreSQL uses Btree indexes for primary keys. Therefore, even when loading large amounts of data, the UUID values being inserted are always in ascending order, which provides high locality for index updates and leads to better performance. Additionally, in PostgreSQL, it helps reduce Full Page Writes (FPW).</p>

<p>Here are the results of inserting 5 million rows using SERIAL type (sequence) and UUID type (UUIDv4 and UUIDv7) as primary keys (verified on PostgreSQL 18 Beta):</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>SERIAL</th>
      <th>UUIDv4</th>
      <th>UUIDv7</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Druation</td>
      <td>8.452 s</td>
      <td>42.24 s</td>
      <td>16.922 s</td>
    </tr>
  </tbody>
</table>

<h2 id="how-to-use-uuid-in-postgresql">How to use UUID in PostgreSQL</h2>

<p>PostgreSQL has a native <a href="https://www.postgresql.jp/document/17/html/datatype-uuid.html">uuid data type</a>, and (as of PostgreSQL 17) there are two main ways to generate UUIDs:</p>

<p>The first method is to use the built-in <a href="https://www.postgresql.jp/document/17/html/functions-uuid.html">gen_random_uuid() SQL function</a>. This generates a version 4 UUID (more details about UUID versions will be discussed later). It’s PostgreSQL’s own implementation.</p>

<p>The second method is to use the <a href="https://www.postgresql.jp/document/17/html/uuid-ossp.html">uuid-ossp contrib module</a>. This performs UUID generation using an external library, which varies depending on the platform. On Linux and macOS, it uses libuuid. This method can generate versions 1 through 5.</p>

<p>As mentioned above, PostgreSQL 17 currently doesn’t support UUIDv7 generation, so if you want to use UUIDv7, you need to either use a published extension or implement it yourself. A search on GitHub reveals the following extensions:</p>

<ul>
  <li><a href="https://github.com/fboulnois/pg_uuidv7">pg_uuidv7</a>
    <ul>
      <li>Implemented in C</li>
    </ul>
  </li>
  <li><a href="https://github.com/dverite/postgres-uuidv7-sql">postgres-uuidv7-sql</a>
    <ul>
      <li>Implemented in SQL, so it can be used with just CREATE FUNCTION without needing to register it as an extension~</li>
    </ul>
  </li>
</ul>

<p>There’s also a blog post about creating a UUIDv7 generation function using pg_tle + PL/Rust:</p>

<p><a href="https://aws.amazon.com/blogs/database/implement-uuidv7-in-amazon-rds-for-postgresql-using-trusted-language-extensions/">https://aws.amazon.com/blogs/database/implement-uuidv7-in-amazon-rds-for-postgresql-using-trusted-language-extensions/</a></p>

<p>Since PL/Rust has limitations on crates can be used, the Rust uuid crate is not available. If you want to create a UUIDv7 generation function using a custom extension, using pgrx<sup id="fnref:pgrx" role="doc-noteref"><a href="#fn:pgrx" class="footnote" rel="footnote">1</a></sup> is probably the easiest way. For just generating UUIDv7, you only need the following code:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">pgrx</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">uuid</span><span class="p">::</span><span class="n">Uuid</span><span class="p">;</span>

<span class="p">::</span><span class="nn">pgrx</span><span class="p">::</span><span class="nd">pg_module_magic!</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">version</span><span class="p">);</span>

<span class="nd">#[pg_extern]</span>
<span class="k">fn</span> <span class="nf">pgrx_uuidv7</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">pgrx</span><span class="p">::</span><span class="n">Uuid</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">uuid</span> <span class="o">=</span> <span class="nn">Uuid</span><span class="p">::</span><span class="nf">now_v7</span><span class="p">();</span>

    <span class="nn">pgrx</span><span class="p">::</span><span class="nn">Uuid</span><span class="p">::</span><span class="nf">from_bytes</span><span class="p">(</span><span class="n">uuid</span><span class="nf">.into_bytes</span><span class="p">())</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Great news is that upcomoing PostgreSQL 18 will introduce the <a href="https://www.postgresql.org/docs/devel/functions-uuid.html">uuidv7() SQL function</a>, making UUIDv7 available to all PostgreSQL users (<a href="https://github.com/postgres/postgres/commit/78c5e141e9c139fc2ff36a220334e4aa25e1b0eb">commit log</a>)!</p>

<p>For backward compatibility, <code class="language-plaintext highlighter-rouge">gen_random_uuid()</code> will continue to exist as a function that generates UUIDv4. Along with <code class="language-plaintext highlighter-rouge">uuidv7()</code>, <code class="language-plaintext highlighter-rouge">uuidv4()</code> has also been added, but it’s just an alias for <code class="language-plaintext highlighter-rouge">gen_random_uuid()</code>.</p>

<h2 id="data-format-of-uuidv7">Data Format of UUIDv7</h2>

<p>As per RFC, the data format of UUIDv7 is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           unix_ts_ms                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          unix_ts_ms           |  ver  |       rand_a          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|                        rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
</code></pre></div></div>

<p>Every UUID data contains the number representing the version of UUID:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>           Version
              |
              v
01937a3a-3d34-74d0-a1b7-e1f1b53064d8
</code></pre></div></div>

<p>In UUIDv7, the rough format is: millisecond-precision timestamp followed by the version number, and then random data (plus variant, to be precise) after the version.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  timestamp      random data(+var)
|-----------|  |-------------------|
01937a3a-3d34-74d0-a1b7-e1f1b53064d8

</code></pre></div></div>

<h2 id="monotonicity-in-uuidv7">Monotonicity in UUIDv7</h2>

<p>For use cases where millisecond precision isn’t sufficient, the RFC allows implementations to use the <code class="language-plaintext highlighter-rouge">rand_a</code> (12 bits) portion (and optionally <code class="language-plaintext highlighter-rouge">rand_b</code>) as additional data to maintain monotonicity of generated values. The RFC <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-monotonicity-and-counters">introduces several methods</a> for how this can be done.</p>

<p>Different UUIDv7 generation function implementations use different approaches, but how <code class="language-plaintext highlighter-rouge">rand_a</code> and <code class="language-plaintext highlighter-rouge">rand_b</code> are used is crucial for maintaining data monotonicity in environments with high-frequency UUID generation. For example, in the simplest format of “millisecond timestamp + all random data,” if more than 1000 UUIDs are generated per second, all leading timestamps will have the same value, so monotonicity of the generated UUIDv7 data isn’t guaranteed. This means that in systems that might generate more than 1000 UUIDs per second, using such a UUIDv7 generation function won’t fully leverage UUIDv7’s advantages. It’s important to choose the right UUIDv7 generation function based on your use case<sup id="fnref:pg_uuidv7_analysis" role="doc-noteref"><a href="#fn:pg_uuidv7_analysis" class="footnote" rel="footnote">2</a></sup>.</p>

<h2 id="postgresqls-uuidv7-implementation">PostgreSQL’s UUIDv7 Implementation</h2>

<p>PostgreSQL’s UUIDv7 implementation adopts <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-monotonicity-and-counters">Method 3 (Replace Leftmost Random Bits with Increased Clock Precision)</a> from the RFC. Specifically, it uses the <code class="language-plaintext highlighter-rouge">rand_a</code> portion for sub-millisecond timestamps, using 60 bits (=48+12) total for the timestamp. This allows it to handle about 4 million UUID generations per second. Furthermore, within the same process, it’s guaranteed that the <code class="language-plaintext highlighter-rouge">rand_a</code> portion is increased by a certain step for each UUID generation, so UUIDv7 data generated from a single process is guaranteed to be monotonically increasing even at higher frequencies.</p>

<p>Additionally, it’s possible to specify an interval value as an argument, allowing you to offset the timestamp stored in the UUID data by a specified period.</p>

<p>The source code can be found <a href="https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/uuid.c#L601">here</a>.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:pgrx" role="doc-endnote">
      <p>A framework for creating PostgreSQL extensions in Rust <a href="#fnref:pgrx" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:pg_uuidv7_analysis" role="doc-endnote">
      <p>For example, looking at <a href="https://github.com/fboulnois/pg_uuidv7/blob/main/pg_uuidv7.c#L35">pg_uuidv7’s implementation</a>, you can see it uses “millisecond timestamp + random data” <a href="#fnref:pg_uuidv7_analysis" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Masahiko Sawada</name></author><category term="PostgreSQL" /><category term="UUID" /><summary type="html"><![CDATA[PostgreSQL 18 supports UUIDv7 as defined in RFC 9562. Explains what makes UUIDv7 sortable, how the new uuidv7() function behaves, and how it compares with UUIDv4 in practice.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://masahikosawada.github.io/assets/images/og-default.png" /><media:content medium="image" url="https://masahikosawada.github.io/assets/images/og-default.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">Building PostgreSQL with musl libc</title><link href="https://masahikosawada.github.io/2024/10/14/musl-libc-postgresql/" rel="alternate" type="text/html" title="Building PostgreSQL with musl libc" /><published>2024-10-14T00:00:00+09:00</published><updated>2024-10-14T00:00:00+09:00</updated><id>https://masahikosawada.github.io/2024/10/14/musl-libc-postgresql</id><content type="html" xml:base="https://masahikosawada.github.io/2024/10/14/musl-libc-postgresql/"><![CDATA[<p>The well-known implementation of the standard C library is glibc. <a href="https://www.musl-libc.org">musl libc</a> is another implementation of the standard C library. It uses the MIT License, and it is known for its simple implementation and small binary size. It is also used in Alpine Linux.</p>

<p>A comparison with glibc can be found <a href="https://www.musl-libc.org">here</a>.</p>

<p>This post is a note on how to build PostgreSQL using musl libc.</p>

<h2 id="preparing-musl-libc">Preparing musl libc</h2>

<p>The source code of musl libc can be downloaded <a href="https://musl.libc.org/releases.html">here</a>. It seems that there is also a place called <a href="https://musl.cc/">musl.cc</a> where you can download pre-built binaries, but this time I built it from source code:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>wget https://musl.libc.org/releases/musl-1.2.5.tar.gz
<span class="nv">$ </span><span class="nb">tar </span>zxf musl-1.2.5.tar.gz
<span class="nv">$ </span><span class="nb">cd </span>musl-1.2.5
<span class="nv">$ </span>./configure <span class="nt">--prefix</span><span class="o">=</span>/home/masahiko/musl <span class="nt">--syslibdir</span><span class="o">=</span>/home/masahiko/musl-lib/
<span class="nv">$ </span>make
<span class="nv">$ </span>make <span class="nb">install</span>
</code></pre></div></div>

<p>By specifying <code class="language-plaintext highlighter-rouge">--prefix</code> and <code class="language-plaintext highlighter-rouge">--syslibdir</code> options, you can specify the directories where musl libc and the dynamic linker will be installed, respectively. The build completed in about 20 seconds in my environment.</p>

<p>You can find a program called <code class="language-plaintext highlighter-rouge">musl-gcc</code> in the <code class="language-plaintext highlighter-rouge">bin</code> directory of the installation destination:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">ls</span> /home/masahiko/musl/bin
musl-gcc
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">musl-gcc</code> is a wrapper program (it’s actually a shell script), and it seems that when you compile programs using this, they will be linked against musl libc.</p>

<h2 id="building-postgresql-from-source-code">Building PostgreSQL from source code</h2>

<h3 id="preparation">Preparation</h3>

<p>As preparation, include the <code class="language-plaintext highlighter-rouge">musl-gcc</code> installed above in the <code class="language-plaintext highlighter-rouge">PATH</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span>/home/masahiko/musl/bin:<span class="nv">$PATH</span>
</code></pre></div></div>

<p>Then, copy the directories <code class="language-plaintext highlighter-rouge">/usr/include/linux</code>, <code class="language-plaintext highlighter-rouge">/usr/include/asm</code>, and <code class="language-plaintext highlighter-rouge">/usr/include/asm-generic</code> to the location where musl libc was installed:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cd</span> /home/masahiko/musl/include
<span class="nv">$ </span><span class="nb">cp</span> <span class="nt">-rs</span> /usr/include/linux linux/
<span class="nv">$ </span><span class="nb">cp</span> <span class="nt">-rs</span> /usr/include/asm asm/
<span class="nv">$ </span><span class="nb">cp</span> <span class="nt">-rs</span> /usr/include/asm-generic asm-generic/
</code></pre></div></div>

<p>The reason for doing this will be explained later.</p>

<h3 id="build">Build</h3>

<p>Download the PostgreSQL source code and built it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git clone git://git.postgresql.org/git/postgresql.git
<span class="nv">$ </span><span class="nb">cd </span>postgresql
<span class="nv">$ </span>./configure <span class="nt">--prefix</span><span class="o">=</span>/home/masahiko/pgsql <span class="nv">CC</span><span class="o">=</span>musl-cc <span class="nt">--without-readline</span> <span class="nt">--without-icu</span> <span class="nt">--witout-zlib</span>
<span class="nv">$ </span>make
<span class="nv">$ </span>make <span class="nb">install</span>
</code></pre></div></div>

<h3 id="about-ccmusl-cc">About <code class="language-plaintext highlighter-rouge">CC=musl-cc</code></h3>

<p>You can specify the compiler to use with <code class="language-plaintext highlighter-rouge">CC</code>.</p>

<h3 id="about-specifying---without-xxx">About specifying <code class="language-plaintext highlighter-rouge">--without-XXX</code></h3>

<p>By default, PostgreSQL builds with readline, zlib, and icu enabled (in the case of using <code class="language-plaintext highlighter-rouge">configure</code> script). While the readline header files are in <code class="language-plaintext highlighter-rouge">usr/include/readline</code>, <code class="language-plaintext highlighter-rouge">/usr/include</code> also contains the header files of glibc. Therefore, if it’s configured to search <code class="language-plaintext highlighter-rouge">/usr/include</code> directory when compiling programs, the build using <code class="language-plaintext highlighter-rouge">musl-gcc</code> wouldn’t work. So I disabled these libraries for the build.</p>

<p>The reason I copied <code class="language-plaintext highlighter-rouge">/usr/include/linux</code> etc. directories in the preparation step was to deal with this problem. <a href="https://www.openwall.com/lists/musl/2017/11/23/1">This seems to be the recommended way</a>, but I went with it for now. I think readline etc. could also be built in a similar way. Not sure.</p>

<p>While PostgreSQL can be built without readline etc. <code class="language-plaintext highlighter-rouge">/usr/include/linux</code> etc. are essential for building PostgreSQL as PostgreSQL’s built-in programs are using it. For instance, <code class="language-plaintext highlighter-rouge">pg_combinebackup</code> requires <code class="language-plaintext highlighter-rouge">/usr/include/linux</code>. Without these steps, I got the following error in my environment:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>musl-gcc <span class="nt">-Wall</span> <span class="nt">-Wmissing-prototypes</span> <span class="nt">-Wpointer-arith</span> <span class="nt">-Wdeclaration-after-statement</span> <span class="nt">-Werror</span><span class="o">=</span>vla <span class="nt">-Wendif-labels</span> <span class="nt">-Wmissing-format-attribute</span> <span class="nt">-Wimplicit-fallthrough</span><span class="o">=</span>3 <span class="nt">-Wcast-function-type</span> <span class="nt">-Wshadow</span><span class="o">=</span>compatible-
<span class="nb">local</span> <span class="nt">-Wformat-security</span> <span class="nt">-fno-strict-aliasing</span> <span class="nt">-fwrapv</span> <span class="nt">-fexcess-precision</span><span class="o">=</span>standard <span class="nt">-Wno-format-truncation</span> <span class="nt">-Wno-stringop-truncation</span> <span class="nt">-O2</span> <span class="nt">-I</span>../../../src/interfaces/libpq <span class="nt">-I</span>../../../src/include  <span class="nt">-D_GNU_SOURCE</span>
   <span class="nt">-c</span> <span class="nt">-o</span> pg_combinebackup.o pg_combinebackup.c
pg_combinebackup.c:24:10: fatal error: linux/fs.h: No such file or directory
   24 | <span class="c">#include &lt;linux/fs.h&gt;</span>
      |          ^~~~~~~~~~~~
</code></pre></div></div>

<h3 id="warnings-during-build">WARNINGs during build</h3>

<p>In my environment, I got the following WARNING during the build, but the build itself succeeded, so no problem:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pg_get_line.c: In <span class="k">function </span>_pg_get_line_append_:
pg_get_line.c:129:27: warning: _<span class="o">({</span>anonymous<span class="o">})</span>_ may be used uninitialized <span class="o">[</span><span class="nt">-Wmaybe-uninitialized</span><span class="o">]</span>
  129 |         <span class="k">if</span> <span class="o">(</span>prompt_ctx <span class="o">&amp;&amp;</span> sigsetjmp<span class="o">(</span><span class="k">*</span><span class="o">((</span>sigjmp_buf <span class="k">*</span><span class="o">)</span> prompt_ctx-&gt;jmpbuf<span class="o">)</span>, 1<span class="o">)</span> <span class="o">!=</span> 0<span class="o">)</span>
      |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
</code></pre></div></div>

<h2 id="veficiation">Veficiation</h2>

<p>After building PostgreSQL from source code, verify that it is indeed linked against musl libc:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ldd bin/postgres
        linux-vdso.so.1 <span class="o">(</span>0x00007ffd0bdf6000<span class="o">)</span>
        libc.so <span class="o">=&gt;</span> /home/masahiko/musl/lib/libc.so <span class="o">(</span>0x00007f21be639000<span class="o">)</span>
</code></pre></div></div>

<p>musl libc is not completely compatible with glibc, and there are <a href="https://wiki.musl-libc.org/functional-differences-from-glibc.html">some behavioral differences</a>, <code class="language-plaintext highlighter-rouge">make check-world</code> also passed.</p>

<p>While musl libc is smaller as a library than glibc, glibc is faster in terms of performance, so I’d also like to compare the performance as the next step.</p>]]></content><author><name>Masahiko Sawada</name></author><category term="PostgreSQL" /><summary type="html"><![CDATA[How to build PostgreSQL against musl libc instead of glibc: preparing the musl toolchain, the configure invocation, and the problems encountered along the way.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://masahikosawada.github.io/assets/images/og-default.png" /><media:content medium="image" url="https://masahikosawada.github.io/assets/images/og-default.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">Implementing a new PostgreSQL Table AM for archiving tables</title><link href="https://masahikosawada.github.io/2024/03/07/Implementing_Table_AM_for_Archving_Tables/" rel="alternate" type="text/html" title="Implementing a new PostgreSQL Table AM for archiving tables" /><published>2024-03-07T00:00:00+09:00</published><updated>2024-03-07T00:00:00+09:00</updated><id>https://masahikosawada.github.io/2024/03/07/Implementing_Table_AM_for_Archving_Tables</id><content type="html" xml:base="https://masahikosawada.github.io/2024/03/07/Implementing_Table_AM_for_Archving_Tables/"><![CDATA[<p>I recently published my hobby project <a href="https://github.com/MasahikoSawada/pgroad">pgroad</a>, a new PostgreSQL Table Access Method (Table AM). pgroad is a PostgreSQL extension that adds a new table format called <code class="language-plaintext highlighter-rouge">road</code> to PostgreSQL. ROAD stands for “Read Only Archived Data” and is used to convert existing tables that are accessed infrequently but cannot be dropped into compact, read-only tables.</p>

<p><strong>CAUTION: since <code class="language-plaintext highlighter-rouge">pgroad</code> is still in development and a hobby project, it’s not production ready</strong></p>

<h2 id="how-it-works">How It Works</h2>

<p>You can register <code class="language-plaintext highlighter-rouge">pgroad</code> in the database using <code class="language-plaintext highlighter-rouge">CREATE EXTENSION</code> command (you also need to add <code class="language-plaintext highlighter-rouge">pgroad</code> to <code class="language-plaintext highlighter-rouge">shared_preload_libraries</code> first):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=# CREATE EXTENSION pgroad;
CREATE EXTENSION
</code></pre></div></div>

<p>Create sample tables using pgbench:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pgbench -i -s 100
dropping old tables...
creating tables...
generating data (client-side)...
vacuuming...
creating primary keys...
done in 10.43 s (drop tables 0.00 s, create tables 0.01 s, client-side generate 7.41 s, vacuum 0.16 s, primary keys 2.86 s).
$ psql
=# \dt+ pgbench_accounts
                                         List of relations
 Schema |       Name       | Type  |  Owner   | Persistence | Access method |  Size   | Description
--------+------------------+-------+----------+-------------+---------------+---------+-------------
 public | pgbench_accounts | table | masahiko | permanent   | heap          | 1281 MB |
(1 row)
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">pgbench_accounts</code> table is a <code class="language-plaintext highlighter-rouge">heap</code> table, and we convert it into a <code class="language-plaintext highlighter-rouge">road</code> table:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=# ALTER TABLE pgbench_accounts SET ACCESS METHOD road;
ALTER TABLE
=# \dt+ pgbench_accounts
                                         List of relations
 Schema |       Name       | Type  |  Owner   | Persistence | Access method |  Size  | Description
--------+------------------+-------+----------+-------------+---------------+--------+-------------
 public | pgbench_accounts | table | masahiko | permanent   | road          | 118 MB |
(1 row)
</code></pre></div></div>

<p>You can see that the table has shrunk from 1281MB to 118MB. Once converted into a <code class="language-plaintext highlighter-rouge">road</code> table, you cannot modify the table:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=# DELETE FROM pgbench_accounts ;
ERROR:  road_tuple_delete is not supported
=# INSERT INTO pgbench_accounts (aid) VALUES (1);
ERROR:  cannot insert tuple directly into a ROAD table
HINT:  Use ALTER TABLE ... SET ACCESS METHOD or CREATE TABLE ... AS to insert tuples
</code></pre></div></div>

<h2 id="architecture">Architecture</h2>

<p>The implementation is very simple. It scans the existing table, stores tuples into 16kB chunks (in memory), compresses it, and writes them to the <code class="language-plaintext highlighter-rouge">road</code> table.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+-------------------+     +------------+  compression   +---------+
|                   | --&gt; |   chunk    | -------------&gt; |xxxxxxxxx| \
|                   |     |  (16kB)    |                +---------+   \
|                   |     +------------+                                \     +--------------------+
|  original table   |                                                    \    |                    |
| (heap, 8kB pages) |     +------------+  compression   +---------+       `-&gt; |     new table      |
|                   | --&gt; |   chunk    | -------------&gt; |xxxxxxxxx| --------&gt; |  (road, 8kB pages) |
|                   |     |  (16kB)    |                +---------+       .-&gt; |                    |
|                   |     +------------+                                 /    |                    |
|                   |                                                   /     +--------------------+
|                   |     +------------+ compression    +---------+   /
|                   | --&gt; |   chunk    | -------------&gt; |xxxxxxxxx| /
|                   |     |  (16kB)    |                +---------+
|                   |     +------------+
+-------------------+
</code></pre></div></div>

<p>Currently the <code class="language-plaintext highlighter-rouge">road</code> table internally utilizes heap tuples. However, heap tuple headers contain some data like xmin and xmax that <code class="language-plaintext highlighter-rouge">road</code> doesn’t need, so I would like to add support for a custom (more compact) tuple format some day.</p>

<p>Chunk pages are compressed using <code class="language-plaintext highlighter-rouge">pglz</code> by default, but <code class="language-plaintext highlighter-rouge">lz4</code> can also be chosen if enabled in the PostgreSQL.</p>

<h2 id="supported-features">Supported Features</h2>

<p>I’ve implemented basic features for now:</p>

<ul>
  <li>Table creation</li>
  <li>Index creation (excluding BRIN)</li>
  <li>Scanning
    <ul>
      <li>Seq Scan</li>
      <li>Index Scan</li>
    </ul>
  </li>
  <li>TOAST</li>
  <li>WAL (Generic WAL)</li>
</ul>

<h2 id="converting-existing-tables-to-road-tables">Converting existing tables to road tables</h2>

<p>Since <code class="language-plaintext highlighter-rouge">pgroad</code> focuses specifically on archiving existing data, <code class="language-plaintext highlighter-rouge">road</code> tables can only be created in the following two ways:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ALTER TABLE ... SET ACCESS METHOD road</code></li>
  <li><code class="language-plaintext highlighter-rouge">CREATE TABLE ... USING road AS ...</code></li>
</ul>

<p>Also, you cannot create a <code class="language-plaintext highlighter-rouge">road</code> table inside a transaction block.</p>

<h3 id="utilizing-processutility_hook">Utilizing ProcessUtility_hook</h3>

<p>PostgreSQL provides hook points that extensions can tap into by registering their own function. <code class="language-plaintext highlighter-rouge">ProcessUtility_hook</code> is one such hook point that gets called when DDL statements are executed. <code class="language-plaintext highlighter-rouge">pgroad</code> uses <code class="language-plaintext highlighter-rouge">ProcessUtility_hook</code> to detect if the SQL statement was <code class="language-plaintext highlighter-rouge">CREATE TABLE AS</code> or <code class="language-plaintext highlighter-rouge">ALTER TABLE ... SET ACCESS METHOD road</code>:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span>
<span class="nf">road_ProcessUtility</span><span class="p">(</span><span class="n">PlannedStmt</span> <span class="o">*</span><span class="n">pstmt</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">queryString</span><span class="p">,</span>
                    <span class="n">bool</span> <span class="n">readOnlyTree</span><span class="p">,</span>
                    <span class="n">ProcessUtilityContext</span> <span class="n">context</span><span class="p">,</span> <span class="n">ParamListInfo</span> <span class="n">params</span><span class="p">,</span>
                    <span class="n">QueryEnvironment</span> <span class="o">*</span><span class="n">queryEnv</span><span class="p">,</span>
                    <span class="n">DestReceiver</span> <span class="o">*</span><span class="n">dest</span><span class="p">,</span> <span class="n">QueryCompletion</span> <span class="o">*</span><span class="n">qc</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">NodeTag</span>     <span class="n">tag</span> <span class="o">=</span> <span class="n">nodeTag</span><span class="p">(</span><span class="n">pstmt</span><span class="o">-&gt;</span><span class="n">utilityStmt</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">tag</span> <span class="o">==</span> <span class="n">T_CreateTableAsStmt</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">RoadInsertState</span><span class="p">.</span><span class="n">called_in_ctas</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
        <span class="n">Assert</span><span class="p">(</span><span class="o">!</span><span class="n">RoadInsertState</span><span class="p">.</span><span class="n">called_in_atsam</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">tag</span> <span class="o">==</span> <span class="n">T_AlterTableStmt</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">AlterTableStmt</span> <span class="o">*</span><span class="n">atstmt</span> <span class="o">=</span> <span class="p">(</span><span class="n">AlterTableStmt</span> <span class="o">*</span><span class="p">)</span> <span class="n">pstmt</span><span class="o">-&gt;</span><span class="n">utilityStmt</span><span class="p">;</span>
        <span class="n">ListCell</span>   <span class="o">*</span><span class="n">cell</span><span class="p">;</span>

        <span class="n">foreach</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">atstmt</span><span class="o">-&gt;</span><span class="n">cmds</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">AlterTableCmd</span> <span class="o">*</span><span class="n">cmd</span> <span class="o">=</span> <span class="p">(</span><span class="n">AlterTableCmd</span> <span class="o">*</span><span class="p">)</span> <span class="n">lfirst</span><span class="p">(</span><span class="n">cell</span><span class="p">);</span>

            <span class="k">if</span> <span class="p">(</span><span class="n">cmd</span><span class="o">-&gt;</span><span class="n">subtype</span> <span class="o">==</span> <span class="n">AT_SetAccessMethod</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">Relation</span>    <span class="n">rel</span> <span class="o">=</span> <span class="n">relation_openrv</span><span class="p">(</span><span class="n">atstmt</span><span class="o">-&gt;</span><span class="n">relation</span><span class="p">,</span> <span class="n">ShareLock</span><span class="p">);</span>

                <span class="cm">/*
                 * Are we about to change the access method of the relation to
                 * ROAD table AM?
                 */</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">cmd</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">,</span> <span class="s">"road"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="cm">/* Remember the original table's OID */</span>
                    <span class="n">RoadInsertState</span><span class="p">.</span><span class="n">atsam_relid</span> <span class="o">=</span> <span class="n">RelationGetRelid</span><span class="p">(</span><span class="n">rel</span><span class="p">);</span>

                    <span class="n">RoadInsertState</span><span class="p">.</span><span class="n">called_in_atsam</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="n">RelationClose</span><span class="p">(</span><span class="n">rel</span><span class="p">);</span>

                <span class="k">break</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="n">Assert</span><span class="p">(</span><span class="o">!</span><span class="n">RoadInsertState</span><span class="p">.</span><span class="n">called_in_ctas</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">prev_ProcessUtility</span><span class="p">(</span><span class="n">pstmt</span><span class="p">,</span> <span class="n">queryString</span><span class="p">,</span> <span class="nb">false</span><span class="p">,</span> <span class="n">context</span><span class="p">,</span>
                        <span class="n">params</span><span class="p">,</span> <span class="n">queryEnv</span><span class="p">,</span> <span class="n">dest</span><span class="p">,</span> <span class="n">qc</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We remember if either of the two DDL is executed, and raise an error in the insert callback if necessary:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span>
<span class="nf">road_tuple_insert</span><span class="p">(</span><span class="n">Relation</span> <span class="n">relation</span><span class="p">,</span> <span class="n">TupleTableSlot</span> <span class="o">*</span><span class="n">slot</span><span class="p">,</span>
                  <span class="n">CommandId</span> <span class="n">cid</span><span class="p">,</span> <span class="kt">int</span> <span class="n">options</span><span class="p">,</span> <span class="n">BulkInsertState</span> <span class="n">bistate</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">RoadInsertStateData</span> <span class="o">*</span><span class="n">state</span><span class="p">;</span>
    <span class="n">ItemPointerData</span> <span class="n">tid</span><span class="p">;</span>
    <span class="n">RowNumber</span>   <span class="n">rownum</span><span class="p">;</span>
    <span class="n">bool</span>        <span class="n">shouldFree</span><span class="p">;</span>
    <span class="n">HeapTuple</span>   <span class="n">tuple</span> <span class="o">=</span> <span class="n">ExecFetchSlotHeapTuple</span><span class="p">(</span><span class="n">slot</span><span class="p">,</span> <span class="nb">true</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">shouldFree</span><span class="p">);</span>

    <span class="n">state</span> <span class="o">=</span> <span class="n">road_get_insert_state</span><span class="p">(</span><span class="n">relation</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">called_in_atsam</span> <span class="o">||</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">called_in_ctas</span><span class="p">))</span>
        <span class="n">ereport</span><span class="p">(</span><span class="n">ERROR</span><span class="p">,</span>
                <span class="p">(</span><span class="n">errmsg</span><span class="p">(</span><span class="s">"cannot insert tuple directly into a ROAD table"</span><span class="p">),</span>
                 <span class="n">errhint</span><span class="p">(</span><span class="s">"Use %s or %s to insert tuples"</span><span class="p">,</span>
                         <span class="s">"ALTER TABLE ... SET ACCESS METHOD"</span><span class="p">,</span>
                         <span class="s">"CREATE TABLE ... AS"</span><span class="p">)));</span>
</code></pre></div></div>

<h2 id="try-creating-your-own-table-ams">Try Creating Your Own Table AMs</h2>

<p>The Table AM is actually a collection of callbacks. Table AM developer implements callbacks that get invoked for functionality like scans, index creation etc. that the table AM wants to support. Table AMs are nicely abstracted from other PostgreSQL components, so you can implement yours fairly independently. PostgreSQL provides transaction manager, buffer manager etc. so AMs can choose whether or not to leverage those facilities. For example, using the buffer manager provided by PostgreSQL core allows Table AM developers to implement their access method without having to consider the lower levels than the shared buffer.</p>

<p>That said, there is a lot more to consider when implementing a table AM:</p>

<ul>
  <li>How to store data?
    <ul>
      <li>Row, columnar, and page format.</li>
    </ul>
  </li>
  <li>Concurrency control and lock granularity.</li>
  <li>Crash recovery and replication.</li>
  <li>Handling ROLLBACK and failed transactions (including handling garbage data).</li>
  <li>Handling data bigger than page size.</li>
</ul>

<p>While designing an idea, a robust Table AM is great, for hobby projects I recommend constraining the use cases as much as possible. More constraints means less cases to handle an dsimpler implementation. Start with a “minimally viable” table AM - something that works for a narrow use case, even if it’s not fully practical. Getting something working will be motivating you! The learning is in the process more than the end product.</p>

<p><code class="language-plaintext highlighter-rouge">pgroad</code> has the following limitations that made its implementation very straightforward:</p>

<ul>
  <li>Table creation only by converting existing tables.
    <ul>
      <li><code class="language-plaintext highlighter-rouge">road</code> tables can only be made with an exclusive lock on the source table.
        <ul>
          <li>No concurrency</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Cannot create <code class="language-plaintext highlighter-rouge">road</code> tables in transaction blocks
    <ul>
      <li>Entire table is lost on failed creation.</li>
      <li>No need to handle SAVEPOINTs.</li>
      <li>No need to handle CURSOR and to consider CommandId.</li>
    </ul>
  </li>
  <li>Read only (no INSERT/UPDATE/DELETE)
    <ul>
      <li>Fewer callbacks to implement.</li>
      <li>No garbage in tables.</li>
    </ul>
  </li>
</ul>

<p>While <code class="language-plaintext highlighter-rouge">pgorad</code> focuses on archival, some other fun Table AM ideas:</p>

<ul>
  <li>Automatic row IDs.</li>
  <li>Heap table with <a href="https://www.pdl.cmu.edu/PDL-FTP/Database/pax.pdf">PAX page format</a>.</li>
  <li>Write ahead log only tables.</li>
</ul>

<p>I encourage you to try developing your own PostgreSQL Table AM extension!</p>]]></content><author><name>Masahiko Sawada</name></author><category term="PostgreSQL" /><category term="Table AM" /><summary type="html"><![CDATA[Implementing pgroad, a PostgreSQL Table Access Method that converts existing tables into compact read-only archived tables. Covers the Table AM callbacks involved and how the road format works.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://masahikosawada.github.io/assets/images/og-default.png" /><media:content medium="image" url="https://masahikosawada.github.io/assets/images/og-default.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">Burning PostgreSQL transaction IDs.</title><link href="https://masahikosawada.github.io/2023/12/21/Burning-XIDs/" rel="alternate" type="text/html" title="Burning PostgreSQL transaction IDs." /><published>2023-12-21T00:00:00+09:00</published><updated>2023-12-21T00:00:00+09:00</updated><id>https://masahikosawada.github.io/2023/12/21/Burning-XIDs</id><content type="html" xml:base="https://masahikosawada.github.io/2023/12/21/Burning-XIDs/"><![CDATA[<p>PostgreSQL’s transaction ID (hereafter XID) is internally represendted as a “monotoronicaly increasing 32-bit unsigned integer value”, so after reaching 2^32-1 (approximately 4 billion) it wraps around back to 0. In PostgreSQL, each transaction that modifies the database such as INSERT, UPDATE and even DDLs is assigned a unique XID. Since the order of XIDs is used to check the visibility of tuples in tables, if XID wraps around after reaching the upper limit, this logic would break.</p>

<p>To prevent this problem, PostgreSQL has a safety mechanis called “aggressive vacuum”, runs automatically before the wraparound happens (specifically, before consumign approx. 2^31 XIDs). This clears old XIDs so new ones can continue to be consumed. In recent years, I’ve been working on improvements around this safety mechanism, so I often needed to test the mechanism itself, which requires consuming a massive number of XIDs and takes time.</p>

<p>For example, a poor-man’s approach to complete 2 billion wirte transactions. Moreover, when issuing new XIDs, status data accociated eich each XID, like CLOG and Commit Timestamp, also need to expand. When testing those behaviors, actually consuming XIDs is important.</p>

<p>So in this post, I’ll introduce various methods to consume XIDs quickly.</p>

<h2 id="1-consuming-xids-using-plpgsql">1. Consuming XIDs using PL/pgSQL</h2>

<p>Comsuming 1 billion XIDs using this use-defined function that internally generates 1 billion subtransactions.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">PROCEDURE</span> <span class="n">consume_xids</span><span class="p">(</span><span class="n">cnt</span> <span class="nb">int</span><span class="p">)</span>
<span class="k">AS</span> <span class="err">$$</span>
<span class="k">DECLARE</span> <span class="n">i</span> <span class="nb">int</span><span class="p">;</span>
<span class="k">BEGIN</span>
	<span class="k">FOR</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="p">..</span><span class="n">cnt</span> <span class="n">LOOP</span>
		<span class="k">EXECUTE</span> <span class="s1">'SELECT txid_current()'</span><span class="p">;</span>
		<span class="k">COMMIT</span><span class="p">;</span>
	<span class="k">END</span> <span class="n">LOOP</span><span class="p">;</span>
<span class="k">END</span><span class="p">;</span>
<span class="err">$$</span>
<span class="k">LANGUAGE</span> <span class="n">plpgsql</span><span class="p">;</span>
<span class="o">=#</span> <span class="k">select</span> <span class="n">pg_current_xact_id</span><span class="p">();</span>

<span class="o">=#</span> <span class="k">call</span> <span class="n">consume_xid</span><span class="p">(</span><span class="mi">100</span><span class="n">_000_000</span><span class="p">);</span>
<span class="nb">Time</span><span class="p">:</span> <span class="mi">797958</span><span class="p">.</span><span class="mi">576</span> <span class="n">ms</span> <span class="p">(</span><span class="mi">13</span><span class="p">:</span><span class="mi">17</span><span class="p">.</span><span class="mi">959</span><span class="p">)</span>

<span class="o">=#</span> <span class="k">select</span> <span class="n">pg_current_xact_id</span><span class="p">();</span>
</code></pre></div></div>

<p>Took 13 minutes, quite slow. XID generation takes exclusive locks so parallelization doesn’t help much.</p>

<h2 id="2-using-pg_resetwal">2. Using pg_resetwal</h2>

<p>The pg_resetwal resets PostgreSQL’s internal data. We can force the next XID like this. Since it just overwrite the internal data so pg_resetwal should complete instanly.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>psql <span class="nt">-c</span> <span class="s2">"SELECT pg_current_xact_id()"</span>
<span class="nv">$ </span>pg_ctl stop
<span class="nv">$ </span>pg_resetwal <span class="nt">-x</span> 2000027648
<span class="nv">$ </span>pg_ctl start
<span class="nv">$ </span>psql <span class="nt">-c</span> <span class="s2">"SELECT pg_current_xact_id()"</span>
</code></pre></div></div>

<p>Why is the next XID an odd number, 2000027648? This is because we need to make sure the next XID lang on a CLOG page (8kB) boundary. Otherwise on startup, we’d get erros that transaction status (e.g. committed, aborted etc) is inaccessible. This happens because on startup, if the current XID isn’t at CLOG page boundary, statuses for unused XIDs in that page are initialied to 0 (see <code class="language-plaintext highlighter-rouge">TrimCLOG()</code> for details). This applies to all pg_resetwal use case, although it’s normally not an issue as XID skips are small.</p>

<p>This quickly and easily skips XIDs but not a “real” use case. We need to stop and start server, choose the next XID carefully, and it depends on page size.</p>

<h2 id="3-using-c-function-to-skip-xids">3. Using C function to skip XIDs</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PG_FUNCTION_INFO_V1</span><span class="p">(</span><span class="n">set_next_xid</span><span class="p">);</span>
<span class="n">Datum</span>
<span class="nf">set_next_xid</span><span class="p">(</span><span class="n">PG_FUNCTION_ARGS</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">TransactionId</span> <span class="n">next_xid</span> <span class="o">=</span> <span class="n">PG_GETARG_TRANSACTIONID</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">TransactionId</span> <span class="n">xid</span><span class="p">;</span>
    <span class="n">uint32</span> <span class="n">epoch</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">TransactionIdIsNormal</span><span class="p">(</span><span class="n">next_xid</span><span class="p">))</span>
        <span class="n">elog</span><span class="p">(</span><span class="n">ERROR</span><span class="p">,</span> <span class="s">"cannot set invalid transaction id"</span><span class="p">);</span>

    <span class="n">LWLockAcquire</span><span class="p">(</span><span class="n">XidGenLock</span><span class="p">,</span> <span class="n">LW_EXCLUSIVE</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">TransactionIdPrecedes</span><span class="p">(</span><span class="n">next_xid</span><span class="p">,</span>
                              <span class="n">XidFromFullTransactionId</span><span class="p">(</span><span class="n">TransamVariables</span><span class="o">-&gt;</span><span class="n">nextXid</span><span class="p">)))</span>
    <span class="p">{</span>
        <span class="n">LWLockRelease</span><span class="p">(</span><span class="n">XidGenLock</span><span class="p">);</span>
        <span class="n">elog</span><span class="p">(</span><span class="n">ERROR</span><span class="p">,</span> <span class="s">"cannot set transaction id older than the current transaction id"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="cm">/*
     * If the new XID is past xidVacLimit, start trying to force autovacuum
     * cycles.
     */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">TransactionIdFollowsOrEquals</span><span class="p">(</span><span class="n">next_xid</span><span class="p">,</span> <span class="n">TransamVariables</span><span class="o">-&gt;</span><span class="n">xidVacLimit</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="cm">/* For safety, we release XidGenLock while sending signal */</span>
        <span class="n">LWLockRelease</span><span class="p">(</span><span class="n">XidGenLock</span><span class="p">);</span>
        <span class="n">SendPostmasterSignal</span><span class="p">(</span><span class="n">PMSIGNAL_START_AUTOVAC_LAUNCHER</span><span class="p">);</span>
        <span class="n">LWLockAcquire</span><span class="p">(</span><span class="n">XidGenLock</span><span class="p">,</span> <span class="n">LW_EXCLUSIVE</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">ExtendCLOG</span><span class="p">(</span><span class="n">next_xid</span><span class="p">);</span>
    <span class="n">ExtendCommitTs</span><span class="p">(</span><span class="n">next_xid</span><span class="p">);</span>
    <span class="n">ExtendSUBTRANS</span><span class="p">(</span><span class="n">next_xid</span><span class="p">);</span>

    <span class="cm">/* Construct the new XID */</span>
    <span class="n">epoch</span> <span class="o">=</span> <span class="n">EpochFromFullTransactionId</span><span class="p">(</span><span class="n">TransamVariables</span><span class="o">-&gt;</span><span class="n">nextXid</span><span class="p">);</span>
    <span class="n">xid</span> <span class="o">=</span> <span class="n">XidFromFullTransactionId</span><span class="p">(</span><span class="n">TransamVariables</span><span class="o">-&gt;</span><span class="n">nextXid</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">xid</span> <span class="o">&gt;</span> <span class="n">next_xid</span><span class="p">))</span>
        <span class="o">++</span><span class="n">epoch</span><span class="p">;</span>
    <span class="n">TransamVariables</span><span class="o">-&gt;</span><span class="n">nextXid</span> <span class="o">=</span>
        <span class="n">FullTransactionIdFromEpochAndXid</span><span class="p">(</span><span class="n">epoch</span><span class="p">,</span> <span class="n">next_xid</span><span class="p">);</span>

    <span class="n">LWLockRelease</span><span class="p">(</span><span class="n">XidGenLock</span><span class="p">);</span>

    <span class="n">PG_RETURN_VOID</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It can avoid serer restart unlike previous method. Expands CLOG and CommitTS etc near new XID. It can trigger autovacuum launcher for aggressive vacuum if new XID is old enough. Also, it should complete instantly.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">=#</span> <span class="k">select</span> <span class="n">txid_current</span><span class="p">();</span>
 <span class="n">txid_current</span>
<span class="c1">--------------</span>
          <span class="mi">737</span>
<span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="nb">Time</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">850</span> <span class="n">ms</span>
<span class="o">=#</span> <span class="k">select</span> <span class="n">set_next_xid</span><span class="p">(</span><span class="s1">'999981056'</span><span class="p">::</span><span class="n">xid</span><span class="p">);</span>
 <span class="n">set_next_xid</span>
<span class="c1">--------------</span>

<span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="nb">Time</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">483</span> <span class="n">ms</span>
<span class="o">=#</span> <span class="k">select</span> <span class="n">txid_current</span><span class="p">();</span>
 <span class="n">txid_current</span>
<span class="c1">--------------</span>
    <span class="mi">999981056</span>
<span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="nb">Time</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">926</span> <span class="n">ms</span>
</code></pre></div></div>

<p>However, <code class="language-plaintext highlighter-rouge">ExtendCLOG()</code> only create pages if the passed XID lands on a page boundary, so the next XID must be calculated carefully same as before. This jumps XIDs more than incrementing them. Expands CLOG near new XID but does nothing for preceding XIDs.</p>

<h2 id="4-fast-forward-xid-internally">4. “Fast Forward” XID internally</h2>

<p>Finally, the approach used by the <code class="language-plaintext highlighter-rouge">xid_wraparound</code> testing extension I recently <a href="https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=e255b646a16b45823c338dadf787813fc9e191dc">pushed</a> to the PostgreSQL source code.</p>

<p>Tough written for regression tests, the SQL functions in the exntesion can work if you installed it to your system.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">=#</span> <span class="k">CREATE</span> <span class="n">EXTENSION</span> <span class="n">xid_wraparound</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="n">EXTENSION</span>

<span class="o">=#</span> <span class="err">\</span><span class="n">dx</span> <span class="n">xid_wraparound</span>
                 <span class="n">List</span> <span class="k">of</span> <span class="n">installed</span> <span class="n">extensions</span>
      <span class="n">Name</span>      <span class="o">|</span> <span class="k">Version</span> <span class="o">|</span> <span class="k">Schema</span> <span class="o">|</span>       <span class="n">Description</span>
<span class="c1">----------------+---------+--------+--------------------------</span>
 <span class="n">xid_wraparound</span> <span class="o">|</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span>     <span class="o">|</span> <span class="k">public</span> <span class="o">|</span> <span class="n">Tests</span> <span class="k">for</span> <span class="n">XID</span> <span class="n">wraparound</span>
<span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>
<span class="o">=#</span> <span class="err">\</span><span class="n">dx</span><span class="o">+</span> <span class="n">xid_wraparound</span>
<span class="n">Objects</span> <span class="k">in</span> <span class="n">extension</span> <span class="nv">"xid_wraparound"</span>
        <span class="k">Object</span> <span class="n">description</span>
<span class="c1">-----------------------------------</span>
 <span class="k">function</span> <span class="n">consume_xids</span><span class="p">(</span><span class="nb">bigint</span><span class="p">)</span>
 <span class="k">function</span> <span class="n">consume_xids_until</span><span class="p">(</span><span class="n">xid8</span><span class="p">)</span>
<span class="p">(</span><span class="mi">2</span> <span class="k">rows</span><span class="p">)</span>
</code></pre></div></div>

<p>The key point of this method that differs from previous method is to move XIDs forward by “skiping while consuming” XIDs; it consume XIDs normally near “intereting XIDs” (those needing CLOG expansion etc) and skip XIDs internally elsewhere.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">=#</span> <span class="k">select</span> <span class="n">txid_current</span><span class="p">();</span>
 <span class="n">txid_current</span>
<span class="c1">--------------</span>
          <span class="mi">737</span>
<span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="o">=#</span> <span class="k">select</span> <span class="n">consume_xids</span><span class="p">(</span><span class="s1">'1000000000'</span><span class="p">);</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">10000071</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">10000809</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">20000880</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">20001618</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">30001689</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">30002427</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">40002498</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">40003236</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">50003230</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">50003968</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">60003297</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">60004035</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">70003998</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">70004736</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">80004096</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">80004834</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">90004766</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">90005504</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">100004895</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">100005633</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">110005534</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">110006272</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">120005694</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">120006432</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">130006302</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">130007040</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">140006493</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">140007231</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">150007070</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">150007808</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">160007292</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">160008030</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">170007838</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">170008576</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">180008091</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">180008829</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">190008606</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">190009344</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">200008890</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">200009628</span>
<span class="p">:</span>
<span class="p">:</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">960038174</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">960038912</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">970038423</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">970039161</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">980038942</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">980039680</span>
<span class="n">NOTICE</span><span class="p">:</span>  <span class="n">consumed</span> <span class="mi">990039222</span> <span class="o">/</span> <span class="mi">1000000000</span> <span class="n">XIDs</span><span class="p">,</span> <span class="n">latest</span> <span class="mi">0</span><span class="p">:</span><span class="mi">990039960</span>
 <span class="n">consume_xids</span>
<span class="c1">--------------</span>
   <span class="mi">1000000738</span>
<span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="nb">Time</span><span class="p">:</span> <span class="mi">2893</span><span class="p">.</span><span class="mi">244</span> <span class="n">ms</span> <span class="p">(</span><span class="mi">00</span><span class="p">:</span><span class="mi">02</span><span class="p">.</span><span class="mi">893</span><span class="p">)</span>
<span class="o">=#</span> <span class="k">select</span> <span class="n">txid_current</span><span class="p">();</span>
 <span class="n">txid_current</span>
<span class="c1">--------------</span>
   <span class="mi">1000000739</span>
<span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>
</code></pre></div></div>

<p>Good, reasonably fast. It can simulate more “real” XID consumption by doing normal consuming and related processing.</p>

<p>Note that it doesn’t trigger autovacuum launcher for aggressive vacuum so timing depends on the new XID and <code class="language-plaintext highlighter-rouge">autovacuum_naptime</code>. This may change in the future.</p>

<p>Thank you for reading.</p>]]></content><author><name>Masahiko Sawada</name></author><category term="PostgreSQL" /><category term="Vacuum" /><summary type="html"><![CDATA[Several ways to burn PostgreSQL transaction IDs (XIDs) as fast as possible, for testing XID wraparound and aggressive vacuum without waiting for 2 billion real transactions.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://masahikosawada.github.io/assets/images/og-default.png" /><media:content medium="image" url="https://masahikosawada.github.io/assets/images/og-default.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>