<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>石頭的coding之路 — PostgreSQL</title>
  <subtitle>PostgreSQL internals, Rust extensions and performance work by Daniel Shih</subtitle>
  <link href="https://isdaniel.github.io/planet-postgresql/atom.xml" rel="self"/>
  <link href="https://isdaniel.github.io/"/>
  <updated>2026-05-03T16:00:00.000Z</updated>
  <id>https://isdaniel.github.io/planet-postgresql/</id>
  <author>
    <name>Daniel Shih</name>
    <uri>https://isdaniel.github.io/about/</uri>
  </author>
  <entry>
    <title>pg_dbmigrator: Near-Zero-Downtime PostgreSQL Migration with Online Mode</title>
    <link href="https://isdaniel.github.io/pg-migrator-online-postgresql-migration/"/>
    <id>https://isdaniel.github.io/pg-migrator-online-postgresql-migration/</id>
    <published>2026-05-03T16:00:00.000Z</published>
    <updated>2026-05-03T16:00:00.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>A hands-on guide to pg_dbmigrator, a Rust CLI for migrating PostgreSQL databases — covering offline dump/restore and the online logical-replication mode for near-zero-downtime cutovers.</summary>
    <content type="html">&lt;h1 id=&quot;pg-dbmigrator-Near-Zero-Downtime-PostgreSQL-Migration-with-Online-Mode&quot;&gt;&lt;a href=&quot;#pg-dbmigrator-Near-Zero-Downtime-PostgreSQL-Migration-with-Online-Mode&quot; class=&quot;headerlink&quot; title=&quot;pg_dbmigrator: Near-Zero-Downtime PostgreSQL Migration with Online Mode&quot;&gt;&lt;/a&gt;pg_dbmigrator: Near-Zero-Downtime PostgreSQL Migration with Online Mode&lt;/h1&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg_dbmigrator&quot;&gt;pg_dbmigrator&lt;/a&gt; is a Rust-based CLI and library for moving PostgreSQL databases between two endpoints. It supports two strategies under one tool:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Offline mode&lt;/strong&gt; — a one-shot &lt;code&gt;pg_dump&lt;/code&gt; → &lt;code&gt;pg_restore&lt;/code&gt;, suitable for maintenance windows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Online mode&lt;/strong&gt; — a streaming pipeline built on PostgreSQL’s logical replication that copies the initial snapshot, then continuously applies WAL changes until you trigger cutover.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’ve ever had to migrate a busy PostgreSQL database between hosts, regions, or cloud providers (on-prem → RDS, RDS → Aurora, Azure → AWS, major-version upgrades, etc.), the online mode is what makes pg_dbmigrator interesting: the source database stays writable during the entire copy, and downtime is reduced to the few seconds it takes to flip the application’s connection string.&lt;/p&gt;
&lt;p&gt;This post walks through how the tool works, how to operate it end-to-end, and what to watch for in production.&lt;/p&gt;
&lt;h2 id=&quot;Why-Another-Migration-Tool&quot;&gt;&lt;a href=&quot;#Why-Another-Migration-Tool&quot; class=&quot;headerlink&quot; title=&quot;Why Another Migration Tool?&quot;&gt;&lt;/a&gt;Why Another Migration Tool?&lt;/h2&gt;&lt;p&gt;PostgreSQL ships with the building blocks — &lt;code&gt;pg_dump&lt;/code&gt;, &lt;code&gt;pg_restore&lt;/code&gt;, &lt;code&gt;CREATE PUBLICATION&lt;/code&gt;, &lt;code&gt;CREATE SUBSCRIPTION&lt;/code&gt;, replication slots — but stitching them together correctly is surprisingly easy to get wrong. Common mistakes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dumping &lt;em&gt;before&lt;/em&gt; creating the slot, so you have no consistent starting LSN.&lt;/li&gt;
&lt;li&gt;Creating the slot &lt;em&gt;after&lt;/em&gt; the dump, missing changes that occurred during the dump.&lt;/li&gt;
&lt;li&gt;Forgetting to use &lt;code&gt;EXPORT_SNAPSHOT&lt;/code&gt; so the dump and the replication start point don’t align.&lt;/li&gt;
&lt;li&gt;Cutting over while the subscriber is still lagging behind, losing writes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;pg_dbmigrator encodes the correct order as a state machine, surfaces lag metrics, and lets the operator drive the cutover when ready.&lt;/p&gt;
&lt;h2 id=&quot;Migration-Modes-at-a-Glance&quot;&gt;&lt;a href=&quot;#Migration-Modes-at-a-Glance&quot; class=&quot;headerlink&quot; title=&quot;Migration Modes at a Glance&quot;&gt;&lt;/a&gt;Migration Modes at a Glance&lt;/h2&gt;&lt;h3 id=&quot;Offline-mode-—-one-shot-with-downtime&quot;&gt;&lt;a href=&quot;#Offline-mode-—-one-shot-with-downtime&quot; class=&quot;headerlink&quot; title=&quot;Offline mode — one shot, with downtime&quot;&gt;&lt;/a&gt;Offline mode — one shot, with downtime&lt;/h3&gt;&lt;p&gt;The source must be quiesced (writes stopped) before the dump starts. Anything written after the dump begins will be lost. The whole migration is a single linear pipe:&lt;/p&gt;
&lt;figure class=&quot;highlight pgsql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;┌────────┐   pg_dump    ┌──────┐   pg_restore   ┌────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│ Source │ ───────────▶ │ Dump │ ─────────────▶ │ Target │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└────────┘              └──────┘                └────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; (writes stopped &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; the entire duration)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Downtime ≈ time to dump + time to restore. Simple, but every minute is user-visible.&lt;/p&gt;
&lt;h3 id=&quot;Online-mode-—-copy-replay-cutover-on-demand&quot;&gt;&lt;a href=&quot;#Online-mode-—-copy-replay-cutover-on-demand&quot; class=&quot;headerlink&quot; title=&quot;Online mode — copy + replay, cutover on demand&quot;&gt;&lt;/a&gt;Online mode — copy + replay, cutover on demand&lt;/h3&gt;&lt;p&gt;Two things run back-to-back on the same timeline. First, an initial snapshot copy (just like offline). Then, while the target catches up, a logical-replication stream keeps replaying every change that happened on the source after the snapshot — including writes that arrived &lt;strong&gt;during&lt;/strong&gt; the dump. The source stays writable the whole time.&lt;/p&gt;
&lt;figure class=&quot;highlight applescript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;          Phase &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;: initial &lt;span class=&quot;keyword&quot;&gt;copy&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌────────┐  snapshot dump   ┌────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│ Source │ ───────────────▶ │ Target │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└────────┘                  └────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     │  (slot created BEFORE dump,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     │   so we know &lt;span class=&quot;keyword&quot;&gt;the&lt;/span&gt; exact LSN&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     │   &lt;span class=&quot;keyword&quot;&gt;the&lt;/span&gt; dump corresponds &lt;span class=&quot;keyword&quot;&gt;to&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     ▼  Phase &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;: streaming catch-up (runs continuously)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌────────┐  WAL changes via             ┌────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│ Source │  logical replication ─────▶ │ Target │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└────────┘  (&lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;the&lt;/span&gt; slot&amp;#x27;s LSN)       └────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     │  Source stays writable. Lag shrinks &lt;span class=&quot;keyword&quot;&gt;over&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;time&lt;/span&gt;.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     ▼  Phase &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;: cutover (operator-driven)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;number&quot;&gt;1.&lt;/span&gt; Stop app writes &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;the&lt;/span&gt; source&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;number&quot;&gt;2.&lt;/span&gt; Wait &lt;span class=&quot;keyword&quot;&gt;until&lt;/span&gt; lag = &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;number&quot;&gt;3.&lt;/span&gt; Press Ctrl+C  →  pg_dbmigrator stops cleanly&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;number&quot;&gt;4.&lt;/span&gt; Repoint &lt;span class=&quot;keyword&quot;&gt;the&lt;/span&gt; app &lt;span class=&quot;keyword&quot;&gt;at&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;the&lt;/span&gt; target&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The trick that makes this work: the replication slot is created &lt;strong&gt;before&lt;/strong&gt; the dump and exports a snapshot. The dump uses that snapshot, and the stream starts at the slot’s LSN — so the stream picks up &lt;em&gt;exactly&lt;/em&gt; where the dump left off. No gap, no duplicates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb&lt;/strong&gt;: pick offline when you can afford a maintenance window the size of (dump + restore). Pick online when you can’t.&lt;/p&gt;
&lt;h2 id=&quot;Online-Mode-The-State-Machine&quot;&gt;&lt;a href=&quot;#Online-Mode-The-State-Machine&quot; class=&quot;headerlink&quot; title=&quot;Online Mode: The State Machine&quot;&gt;&lt;/a&gt;Online Mode: The State Machine&lt;/h2&gt;&lt;p&gt;Internally pg_dbmigrator runs the online migration as a sequence of phases:&lt;/p&gt;
&lt;figure class=&quot;highlight pgsql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;Validate&lt;/span&gt; → SourceVacuum → PrepareSnapshot → Dump → Restore → &lt;span class=&quot;keyword&quot;&gt;Analyze&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;   → StreamApply → (Lag heartbeat …) → CaughtUp → Cutover&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;   → SourceCleanup → Complete&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Each phase has a specific job:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Validate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Checks connection strings, &lt;code&gt;wal_level&lt;/code&gt;, replication slot&amp;#x2F;sender capacity, target reachability.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SourceVacuum&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs &lt;code&gt;VACUUM ANALYZE&lt;/code&gt; on the source to reclaim dead tuples and refresh planner statistics before the dump. Skip with &lt;code&gt;--skip-source-vacuum&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PrepareSnapshot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creates the logical replication slot with &lt;code&gt;EXPORT_SNAPSHOT&lt;/code&gt;. Streaming is deferred until after the dump completes; the exported snapshot ID is what makes the dump consistent with the slot’s starting LSN.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dump&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs &lt;code&gt;pg_dump --snapshot=&amp;lt;exported_id&amp;gt;&lt;/code&gt; so the dump reflects the database state &lt;em&gt;exactly&lt;/em&gt; at the slot’s starting LSN.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Restore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pg_restore&lt;/code&gt; into the target. Parallelized with &lt;code&gt;--jobs&lt;/code&gt; and split-sections (indexes rebuilt in parallel) by default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Analyze&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs &lt;code&gt;ANALYZE&lt;/code&gt; on the target so the first application queries have fresh statistics. Skip with &lt;code&gt;--skip-analyze&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;StreamApply&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Starts &lt;code&gt;START_REPLICATION&lt;/code&gt; from the slot’s LSN and applies decoded changes onto the target.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lag heartbeat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Polls &lt;code&gt;pg_current_wal_flush_lsn()&lt;/code&gt; on the source on a tunable interval, logs the delta against the receiver&amp;#x2F;applied LSN.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CaughtUp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fires once lag falls below &lt;code&gt;--lag-threshold-bytes&lt;/code&gt;, signalling the operator that cutover is safe. The threshold is &lt;strong&gt;advisory only&lt;/strong&gt; — it does not auto-trigger cutover.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cutover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Triggered by SIGINT (Ctrl+C). Flushes the final LSN feedback, syncs sequences on the target, then disables and drops the subscription.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SourceCleanup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;After cutover, drops auto-created publications and the replication slot on the source. Failures are logged as warnings but don’t abort.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The key invariant: the slot is created &lt;strong&gt;before&lt;/strong&gt; the dump, and the dump uses the slot’s exported snapshot. That way the WAL stream picks up exactly where the dump ended — no gap, no overlap.&lt;/p&gt;
&lt;h2 id=&quot;Installation&quot;&gt;&lt;a href=&quot;#Installation&quot; class=&quot;headerlink&quot; title=&quot;Installation&quot;&gt;&lt;/a&gt;Installation&lt;/h2&gt;&lt;p&gt;pg_dbmigrator is published on crates.io:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;cargo install pg_dbmigrator&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pg_dbmigrator --&lt;span class=&quot;built_in&quot;&gt;help&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Or build from source:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;git &lt;span class=&quot;built_in&quot;&gt;clone&lt;/span&gt; https://github.com/isdaniel/pg_dbmigrator&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;cd&lt;/span&gt; pg_dbmigrator&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cargo install --path crates/pg_dbmigrator-cli&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;You also need &lt;code&gt;pg_dump&lt;/code&gt; and &lt;code&gt;pg_restore&lt;/code&gt; on the &lt;code&gt;PATH&lt;/code&gt; — the tool shells out to them rather than reimplementing the dump format. Make sure their major version is &lt;strong&gt;&amp;gt;&amp;#x3D;&lt;/strong&gt; the source database’s major version, otherwise you’ll hit compatibility errors during dump.&lt;/p&gt;
&lt;h2 id=&quot;Source-Prerequisites&quot;&gt;&lt;a href=&quot;#Source-Prerequisites&quot; class=&quot;headerlink&quot; title=&quot;Source Prerequisites&quot;&gt;&lt;/a&gt;Source Prerequisites&lt;/h2&gt;&lt;p&gt;Online mode requires a few server settings on the &lt;strong&gt;source&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- postgresql.conf (requires restart)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SYSTEM&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SET&lt;/span&gt; wal_level &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;logical&amp;#x27;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SYSTEM&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SET&lt;/span&gt; max_replication_slots &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SYSTEM&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SET&lt;/span&gt; max_wal_senders &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;That’s it for setup — pg_dbmigrator will &lt;strong&gt;auto-create the publication&lt;/strong&gt; (&lt;code&gt;FOR ALL TABLES&lt;/code&gt;) on first run if it doesn’t already exist, and clean it up after cutover. If you’d rather manage the publication yourself (e.g. to scope it to a subset of tables), pre-create it and pass &lt;code&gt;--no-auto-create-publication&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; PUBLICATION pg_dbmigrator_pub &lt;span class=&quot;keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;TABLE&lt;/span&gt; schema.t1, schema.t2;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The connecting role needs &lt;code&gt;REPLICATION&lt;/code&gt; and the ability to read every table in the publication. On managed services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWS RDS&lt;/strong&gt;: set &lt;code&gt;rds.logical_replication = 1&lt;/code&gt; in the parameter group, then reboot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Database for PostgreSQL&lt;/strong&gt;: enable the &lt;code&gt;logical&lt;/code&gt; &lt;code&gt;wal_level&lt;/code&gt; server parameter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud SQL&lt;/strong&gt;: enable &lt;code&gt;cloudsql.logical_decoding&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;target&lt;/strong&gt; doesn’t need &lt;code&gt;wal_level = logical&lt;/code&gt; unless you plan to chain replication out of it.&lt;/p&gt;
&lt;h3 id=&quot;Resource-Lifecycle&quot;&gt;&lt;a href=&quot;#Resource-Lifecycle&quot; class=&quot;headerlink&quot; title=&quot;Resource Lifecycle&quot;&gt;&lt;/a&gt;Resource Lifecycle&lt;/h3&gt;&lt;p&gt;By default, pg_dbmigrator owns the resources it creates and cleans them up when the migration finishes. Override the cleanup if you need the resource to outlive the migration:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Created by&lt;/th&gt;
&lt;th&gt;Default cleanup&lt;/th&gt;
&lt;th&gt;Override&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Publication&lt;/td&gt;
&lt;td&gt;Auto-created if missing&lt;/td&gt;
&lt;td&gt;Dropped if auto-created&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--no-auto-create-publication&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replication slot&lt;/td&gt;
&lt;td&gt;Always by migrator&lt;/td&gt;
&lt;td&gt;Dropped after cutover&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--keep-slot&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subscription&lt;/td&gt;
&lt;td&gt;Always by migrator&lt;/td&gt;
&lt;td&gt;Dropped after cutover&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--keep-subscription&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;Offline-Mode-the-Simple-Case&quot;&gt;&lt;a href=&quot;#Offline-Mode-the-Simple-Case&quot; class=&quot;headerlink&quot; title=&quot;Offline Mode: the Simple Case&quot;&gt;&lt;/a&gt;Offline Mode: the Simple Case&lt;/h2&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;pg_dbmigrator \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --mode offline \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --&lt;span class=&quot;built_in&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;postgres://user:pw@src.example.com:5432/appdb&amp;#x27;&lt;/span&gt; \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --target &lt;span class=&quot;string&quot;&gt;&amp;#x27;postgres://user:pw@dst.example.com:5432/appdb&amp;#x27;&lt;/span&gt; \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --&lt;span class=&quot;built_in&quot;&gt;jobs&lt;/span&gt; 4 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --drop-target-first&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;What this does:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Drops the target database contents first (&lt;code&gt;--drop-target-first&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;pg_dump&lt;/code&gt; against the source.&lt;/li&gt;
&lt;li&gt;Pipes into &lt;code&gt;pg_restore&lt;/code&gt; with 4 parallel workers.&lt;/li&gt;
&lt;li&gt;Exits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use it for dev&amp;#x2F;staging clones, small databases, or when an outage window is acceptable.&lt;/p&gt;
&lt;h2 id=&quot;Online-Mode-the-Full-Workflow&quot;&gt;&lt;a href=&quot;#Online-Mode-the-Full-Workflow&quot; class=&quot;headerlink&quot; title=&quot;Online Mode: the Full Workflow&quot;&gt;&lt;/a&gt;Online Mode: the Full Workflow&lt;/h2&gt;&lt;p&gt;A typical online migration command:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;pg_dbmigrator \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --mode online \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --&lt;span class=&quot;built_in&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;postgres://user:pw@src.example.com:5432/appdb&amp;#x27;&lt;/span&gt; \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --target &lt;span class=&quot;string&quot;&gt;&amp;#x27;postgres://user:pw@dst.example.com:5432/appdb&amp;#x27;&lt;/span&gt; \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --slot-name pg_dbmigrator_slot \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --publication pg_dbmigrator_pub \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --subscription-name pg_dbmigrator_sub \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --&lt;span class=&quot;built_in&quot;&gt;jobs&lt;/span&gt; 4 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --lag-threshold-bytes 8192 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --cutover-poll-secs 5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Walking through what each flag controls:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--mode online&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Selects the streaming pipeline.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--source&lt;/code&gt; &amp;#x2F; &lt;code&gt;--target&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Standard libpq connection strings.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--slot-name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name of the logical replication slot created on the source. Pick something recognizable so you can clean it up if the migration is aborted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--publication&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publication name. Defaults to &lt;code&gt;pg_dbmigrator_pub&lt;/code&gt;; auto-created &lt;code&gt;FOR ALL TABLES&lt;/code&gt; if missing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--subscription-name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name pg_dbmigrator gives the subscription it creates on the target.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--jobs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Parallelism for the initial &lt;code&gt;pg_restore&lt;/code&gt;. Auto-detected from CPU count and clamped to &lt;code&gt;[1, 8]&lt;/code&gt; if omitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--lag-threshold-bytes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the gap between source flush LSN and applied LSN drops below this, the tool emits a “ready to cut over” signal. &lt;strong&gt;Advisory only&lt;/strong&gt; — it does not auto-trigger cutover.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--cutover-poll-secs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How often the lag heartbeat polls &lt;code&gt;pg_current_wal_flush_lsn()&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Useful additional flags:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--drop-target-first&lt;/code&gt; — recreate the target schema cleanly. Necessary on most fresh runs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-auto-create-publication&lt;/code&gt; — skip auto-creation; use a publication you manage yourself (also skips its cleanup).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--keep-slot&lt;/code&gt; &amp;#x2F; &lt;code&gt;--keep-subscription&lt;/code&gt; — leave the slot or subscription in place after cutover instead of dropping them. Handy for keeping a rollback window.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-sequence-sync&lt;/code&gt; — skip the automatic &lt;code&gt;setval()&lt;/code&gt; pass at cutover. Use when the target role lacks the privileges; you’ll need to advance sequences manually.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--exclude-schema&lt;/code&gt; &amp;#x2F; &lt;code&gt;--exclude-table&lt;/code&gt; — repeat to omit specific schemas or tables from the dump.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--skip-source-vacuum&lt;/code&gt; &amp;#x2F; &lt;code&gt;--skip-analyze&lt;/code&gt; — disable the pre-dump &lt;code&gt;VACUUM ANALYZE&lt;/code&gt; on the source or the post-restore &lt;code&gt;ANALYZE&lt;/code&gt; on the target. Useful when the source is already well-vacuumed or when you want to control the maintenance window yourself.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--dump-compress&lt;/code&gt; — archive compression spec. Defaults to &lt;code&gt;lz4:1&lt;/code&gt;; alternatives include &lt;code&gt;zstd:3&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-split-sections&lt;/code&gt; — disable parallel section restore. Split sections are 30–60% faster on index-heavy schemas, so only flip this off if the parallel restore is misbehaving.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--allow-restore-errors&lt;/code&gt; — treat &lt;code&gt;pg_restore&lt;/code&gt; errors as warnings. Useful when the target is a managed service that reserves extension names (Azure-reserved extensions, missing &lt;code&gt;pg_cron&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;What-You’ll-See-in-the-Terminal&quot;&gt;&lt;a href=&quot;#What-You’ll-See-in-the-Terminal&quot; class=&quot;headerlink&quot; title=&quot;What You’ll See in the Terminal&quot;&gt;&lt;/a&gt;What You’ll See in the Terminal&lt;/h3&gt;&lt;p&gt;Once &lt;code&gt;StreamApply&lt;/code&gt; starts, the tool prints periodic heartbeats:&lt;/p&gt;
&lt;figure class=&quot;highlight dns&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;[INFO] StreamApply: replication lag &lt;span class=&quot;number&quot;&gt;1,245,184&lt;/span&gt; bytes&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       (source LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;A2B3C4D, received LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1A1F0000&lt;/span&gt;, applied LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1A1E8000&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;[INFO] StreamApply: replication lag &lt;span class=&quot;number&quot;&gt;524,288&lt;/span&gt; bytes&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       (source LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1A2C1000&lt;/span&gt;, received LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1A2B9000&lt;/span&gt;, applied LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1A2B8000&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;[INFO] StreamApply: replication lag &lt;span class=&quot;number&quot;&gt;4&lt;/span&gt;,&lt;span class=&quot;number&quot;&gt;096&lt;/span&gt; bytes&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       (source LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1A2D5000&lt;/span&gt;, received LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1A2D4000&lt;/span&gt;, applied LSN &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;number&quot;&gt;1A2D4000&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;[INFO] CaughtUp: lag below &lt;span class=&quot;number&quot;&gt;8&lt;/span&gt;,&lt;span class=&quot;number&quot;&gt;192&lt;/span&gt; bytes — safe to cut over&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The lag is the byte distance between the source’s current flush LSN and the target’s applied LSN. Watch it converge before cutting over.&lt;/p&gt;
&lt;h3 id=&quot;Driving-the-Cutover&quot;&gt;&lt;a href=&quot;#Driving-the-Cutover&quot; class=&quot;headerlink&quot; title=&quot;Driving the Cutover&quot;&gt;&lt;/a&gt;Driving the Cutover&lt;/h3&gt;&lt;p&gt;Cutover is &lt;strong&gt;operator-driven&lt;/strong&gt;, not automatic. This is intentional — only you know when the application is in a state where it can be flipped. The flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Wait for &lt;code&gt;CaughtUp&lt;/code&gt; to appear (or for the lag to be acceptable).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stop application writes to the source&lt;/strong&gt; (put the app in maintenance, revoke writes, or fail over the load balancer).&lt;/li&gt;
&lt;li&gt;Wait one more heartbeat to confirm lag is &lt;code&gt;0&lt;/code&gt; bytes.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+C&lt;/strong&gt; (single SIGINT). pg_dbmigrator:&lt;ul&gt;
&lt;li&gt;Stops the apply loop at the current LSN and flushes the final feedback message.&lt;/li&gt;
&lt;li&gt;Syncs sequences on the target via &lt;code&gt;setval()&lt;/code&gt; (skip with &lt;code&gt;--no-sequence-sync&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Disables and drops the subscription on the target (unless &lt;code&gt;--keep-subscription&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Drops the replication slot and any auto-created publication on the source (unless &lt;code&gt;--keep-slot&lt;/code&gt; or &lt;code&gt;--no-auto-create-publication&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Exits cleanly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Repoint the application’s connection string to the target.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A second Ctrl+C is an escape hatch — if shutdown is stuck, hitting it again forces termination. Use only as a last resort, since the slot may be left behind on the source (drop manually with &lt;code&gt;SELECT pg_drop_replication_slot(&amp;#39;pg_dbmigrator_slot&amp;#39;);&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&quot;A-Realistic-Online-Migration-Recipe&quot;&gt;&lt;a href=&quot;#A-Realistic-Online-Migration-Recipe&quot; class=&quot;headerlink&quot; title=&quot;A Realistic Online Migration Recipe&quot;&gt;&lt;/a&gt;A Realistic Online Migration Recipe&lt;/h2&gt;&lt;p&gt;Putting it together, here’s the sequence I’d run for a real migration from on-prem to RDS:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 1. On the source, one-time setup&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;psql &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;span class=&quot;variable&quot;&gt;$SOURCE&lt;/span&gt;&amp;quot;&lt;/span&gt; &amp;lt;&amp;lt;&lt;span class=&quot;string&quot;&gt;&amp;#x27;SQL&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;ALTER SYSTEM SET wal_level = &lt;span class=&quot;string&quot;&gt;&amp;#x27;logical&amp;#x27;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;SQL&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Restart source PostgreSQL.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# (No need to pre-create the publication — pg_dbmigrator will auto-create&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#  pg_dbmigrator_pub FOR ALL TABLES on first run, and clean it up at cutover.)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 2. Kick off the migration in a tmux/screen session you can leave running.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pg_dbmigrator \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --mode online \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --&lt;span class=&quot;built_in&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;span class=&quot;variable&quot;&gt;$SOURCE&lt;/span&gt;&amp;quot;&lt;/span&gt; \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --target &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;span class=&quot;variable&quot;&gt;$TARGET&lt;/span&gt;&amp;quot;&lt;/span&gt; \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --slot-name pg_dbmigrator_slot \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --publication pg_dbmigrator_pub \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --subscription-name pg_dbmigrator_sub \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --&lt;span class=&quot;built_in&quot;&gt;jobs&lt;/span&gt; 8 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --lag-threshold-bytes 16384 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --cutover-poll-secs 5 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --drop-target-first&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 3. (In another terminal) sanity-check row counts as the dump completes,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#    and watch lag heartbeats.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 4. When CaughtUp fires and you&amp;#x27;re ready:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#      a. Put app in maintenance mode (stop writes to source).&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#      b. Wait until lag heartbeat shows 0 bytes.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#      c. Ctrl+C the pg_dbmigrator process — it will sync sequences, drop&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#         the subscription, and clean up the slot/publication on the source.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#      d. Update DNS / connection string to point at TARGET.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#      e. Bring app out of maintenance.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 5. Cleanup on the source — by default pg_dbmigrator drops the slot and&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#    the auto-created publication itself. Only run these if you used&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#    --keep-slot / --no-auto-create-publication, or aborted the run:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;psql &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;span class=&quot;variable&quot;&gt;$SOURCE&lt;/span&gt;&amp;quot;&lt;/span&gt; -c &lt;span class=&quot;string&quot;&gt;&amp;quot;SELECT pg_drop_replication_slot(&amp;#x27;pg_dbmigrator_slot&amp;#x27;);&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;psql &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;span class=&quot;variable&quot;&gt;$SOURCE&lt;/span&gt;&amp;quot;&lt;/span&gt; -c &lt;span class=&quot;string&quot;&gt;&amp;quot;DROP PUBLICATION pg_dbmigrator_pub;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Total user-visible downtime is steps 4a–4e, which is typically tens of seconds.&lt;/p&gt;
&lt;h2 id=&quot;Operational-Gotchas&quot;&gt;&lt;a href=&quot;#Operational-Gotchas&quot; class=&quot;headerlink&quot; title=&quot;Operational Gotchas&quot;&gt;&lt;/a&gt;Operational Gotchas&lt;/h2&gt;&lt;p&gt;A few things that bite people the first time:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replication slots hold WAL&lt;/strong&gt;. If you start an online migration and abandon it, the slot will keep the source’s WAL pinned indefinitely, eventually filling the source’s disk. A clean Ctrl+C drops the slot for you, but if the process is killed or crashes, drop the slot manually (&lt;code&gt;pg_drop_replication_slot&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sequences are synced automatically at cutover&lt;/strong&gt;. pg_dbmigrator runs &lt;code&gt;setval()&lt;/code&gt; on every sequence in the included schemas as part of cutover, so you don’t need the old “advance every sequence by hand” ritual. Per-sequence failures (typically a missing privilege on the target role) are logged as warnings but won’t abort cutover. If your target role can’t &lt;code&gt;setval&lt;/code&gt;, pass &lt;code&gt;--no-sequence-sync&lt;/code&gt; and run the SQL yourself:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; setval(&lt;span class=&quot;string&quot;&gt;&amp;#x27;public.users_id_seq&amp;#x27;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;              (&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;max&lt;/span&gt;(id) &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; public.users));&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;DDL during migration is not replicated.&lt;/strong&gt; If someone runs an &lt;code&gt;ALTER TABLE&lt;/code&gt; on the source mid-stream, the target won’t see it and apply will eventually fail when a row references the new column. Freeze schema changes for the duration of the migration. If a DDL change really must happen, you’ll need to refresh the publication and restart the migration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Restore errors on managed targets.&lt;/strong&gt; Azure reserves extension names; &lt;code&gt;pg_cron&lt;/code&gt; is unavailable on most managed services; some superuser-only objects can’t be restored by an unprivileged role. &lt;code&gt;--allow-restore-errors&lt;/code&gt; lets the migration proceed, but inspect the warnings — anything important needs to be applied manually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Custom column transforms aren’t supported.&lt;/strong&gt; The streaming apply binds replicated values as text and casts them server-side. If you need to transform data during migration (e.g. re-encrypt a column), do it before or after, not inline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Major-version differences.&lt;/strong&gt; If source is PG14 and target is PG16, run &lt;code&gt;pg_dump&lt;/code&gt;&amp;#x2F;&lt;code&gt;pg_restore&lt;/code&gt; from the &lt;strong&gt;higher&lt;/strong&gt; version’s binaries. pg_dbmigrator inherits whatever’s on the &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;When-to-Pick-Offline-vs-Online&quot;&gt;&lt;a href=&quot;#When-to-Pick-Offline-vs-Online&quot; class=&quot;headerlink&quot; title=&quot;When to Pick Offline vs. Online&quot;&gt;&lt;/a&gt;When to Pick Offline vs. Online&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Database is small (&amp;lt; few GB) and you have a maintenance window&lt;/td&gt;
&lt;td&gt;Offline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev &amp;#x2F; staging clones&lt;/td&gt;
&lt;td&gt;Offline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production database, can’t take more than a few seconds of write downtime&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-cloud or cross-region migrations&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Major-version upgrade on a live system&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source can’t be set to &lt;code&gt;wal_level = logical&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offline (no choice)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;Summary&quot;&gt;&lt;a href=&quot;#Summary&quot; class=&quot;headerlink&quot; title=&quot;Summary&quot;&gt;&lt;/a&gt;Summary&lt;/h2&gt;&lt;p&gt;pg_dbmigrator wraps a careful, correct online migration recipe — slot-before-dump, snapshot-consistent copy, streaming apply, operator-driven cutover — into a single Rust CLI. For straightforward jobs the offline mode is enough; for production cutovers where downtime matters, the online mode reduces the outage to the moment you press Ctrl+C.&lt;/p&gt;
&lt;p&gt;The most important habits when operating it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Always confirm &lt;code&gt;wal_level = logical&lt;/code&gt; on the source before starting.&lt;/li&gt;
&lt;li&gt;Watch the lag heartbeats and only cut over from a &lt;code&gt;CaughtUp&lt;/code&gt; state with &lt;code&gt;0&lt;/code&gt; bytes lag and writes stopped — the threshold is advisory, not automatic.&lt;/li&gt;
&lt;li&gt;Let the tool exit cleanly via Ctrl+C so it can drop the slot, the auto-created publication, and sync sequences for you. If the process dies hard, drop the slot manually so abandoned WAL doesn’t fill the source’s disk.&lt;/li&gt;
&lt;li&gt;Freeze schema changes for the duration of the migration — DDL isn’t replicated.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;References&quot;&gt;&lt;a href=&quot;#References&quot; class=&quot;headerlink&quot; title=&quot;References&quot;&gt;&lt;/a&gt;References&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg_dbmigrator&quot;&gt;pg_dbmigrator GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.postgresql.org/docs/current/logical-replication.html&quot;&gt;PostgreSQL Logical Replication Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.postgresql.org/docs/current/logicaldecoding-explanation.html#LOGICALDECODING-REPLICATION-SLOTS&quot;&gt;PostgreSQL Replication Slots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://isdaniel.github.io/pg-walstream-rust-postgresql-wal-streaming/&quot;&gt;pg-walstream: PostgreSQL WAL Streaming in Rust&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://isdaniel.github.io/postgresql-wal-introduce/&quot;&gt;PostgreSQL WAL (Write-Ahead Logging) mechanism&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/pg-migrator-online-postgresql-migration/&quot;&gt;https://isdaniel.github.io/pg-migrator-online-postgresql-migration/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>pg_pidstat: Real-Time Per-Backend CPU, Memory, and I/O Monitoring for PostgreSQL</title>
    <link href="https://isdaniel.github.io/pg-pidstat-real-time-postgresql-backend-monitoring/"/>
    <id>https://isdaniel.github.io/pg-pidstat-real-time-postgresql-backend-monitoring/</id>
    <published>2026-04-27T02:00:00.000Z</published>
    <updated>2026-04-27T02:00:00.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>pg_pidstat is a Rust-based PostgreSQL extension that adds real-time per-backend CPU, memory, I/O, and context switch monitoring via a single SQL view.</summary>
    <content type="html">&lt;h1 id=&quot;pg-pidstat-Real-Time-Per-Backend-CPU-Memory-and-I-O-Monitoring-for-PostgreSQL&quot;&gt;&lt;a href=&quot;#pg-pidstat-Real-Time-Per-Backend-CPU-Memory-and-I-O-Monitoring-for-PostgreSQL&quot; class=&quot;headerlink&quot; title=&quot;pg_pidstat: Real-Time Per-Backend CPU, Memory, and I&amp;#x2F;O Monitoring for PostgreSQL&quot;&gt;&lt;/a&gt;pg_pidstat: Real-Time Per-Backend CPU, Memory, and I&amp;#x2F;O Monitoring for PostgreSQL&lt;/h1&gt;&lt;p&gt;When a PostgreSQL server slows down, the first question is usually: &lt;strong&gt;which backend is consuming resources?&lt;/strong&gt; &lt;code&gt;pg_stat_activity&lt;/code&gt; tells you what each backend is doing, but not how much CPU, memory, or disk I&amp;#x2F;O it uses. You end up jumping between &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;pidstat&lt;/code&gt;, and &lt;code&gt;pg_stat_activity&lt;/code&gt;, manually correlating PIDs.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg_pidstat&quot;&gt;pg_pidstat&lt;/a&gt; solves this by embedding OS-level process metrics directly into PostgreSQL. A single SQL view gives you per-backend CPU percentage, memory consumption, I&amp;#x2F;O throughput, and context switch rates — all joined with the connection context you already know from &lt;code&gt;pg_stat_activity&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;The-Problem-Blind-Spots-in-PostgreSQL-Monitoring&quot;&gt;&lt;a href=&quot;#The-Problem-Blind-Spots-in-PostgreSQL-Monitoring&quot; class=&quot;headerlink&quot; title=&quot;The Problem: Blind Spots in PostgreSQL Monitoring&quot;&gt;&lt;/a&gt;The Problem: Blind Spots in PostgreSQL Monitoring&lt;/h2&gt;&lt;p&gt;PostgreSQL’s built-in statistics views (&lt;code&gt;pg_stat_activity&lt;/code&gt;, &lt;code&gt;pg_stat_user_tables&lt;/code&gt;, &lt;code&gt;pg_stat_io&lt;/code&gt;) provide query-level and table-level insights, but they lack &lt;strong&gt;process-level OS metrics&lt;/strong&gt;. When you need to answer questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which backend is burning 100% CPU?&lt;/li&gt;
&lt;li&gt;Is that long-running query memory-hungry or I&amp;#x2F;O-bound?&lt;/li&gt;
&lt;li&gt;Are context switches causing latency spikes?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You typically resort to external tools — &lt;code&gt;pidstat&lt;/code&gt;, &lt;code&gt;htop&lt;/code&gt;, or custom &lt;code&gt;/proc&lt;/code&gt; scrapers — and then manually join their output with &lt;code&gt;pg_stat_activity&lt;/code&gt; by PID. This workflow is tedious, error-prone, and impossible to automate in pure SQL.&lt;/p&gt;
&lt;p&gt;pg_pidstat eliminates that gap entirely.&lt;/p&gt;
&lt;h2 id=&quot;Key-Features&quot;&gt;&lt;a href=&quot;#Key-Features&quot; class=&quot;headerlink&quot; title=&quot;Key Features&quot;&gt;&lt;/a&gt;Key Features&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-backend CPU usage&lt;/strong&gt; as a percentage of total system capacity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory tracking&lt;/strong&gt; — both percentage and absolute MB (resident set size)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I&amp;#x2F;O throughput&lt;/strong&gt; — read&amp;#x2F;write bytes per second and IOPS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I&amp;#x2F;O wait detection&lt;/strong&gt; — flags backends blocked on disk&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context switch rates&lt;/strong&gt; — voluntary and non-voluntary, per second&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full &lt;code&gt;pg_stat_activity&lt;/code&gt; integration&lt;/strong&gt; — all standard columns (pid, datname, usename, state, query, etc.) included&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background worker sampling&lt;/strong&gt; — metrics updated every 1 second automatically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lock-free reads&lt;/strong&gt; — minimal contention with brief exclusive writes only during sampling&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Architecture&quot;&gt;&lt;a href=&quot;#Architecture&quot; class=&quot;headerlink&quot; title=&quot;Architecture&quot;&gt;&lt;/a&gt;Architecture&lt;/h2&gt;&lt;p&gt;pg_pidstat is built with &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/pgcentralfoundation/pgrx&quot;&gt;pgrx&lt;/a&gt; (Rust) and consists of four modules:&lt;/p&gt;
&lt;figure class=&quot;highlight pgsql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;┌─────────────────────────────────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│                    &lt;span class=&quot;keyword&quot;&gt;SQL&lt;/span&gt; Interface                     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│          pg_pidstat &lt;span class=&quot;keyword&quot;&gt;view&lt;/span&gt; + helper &lt;span class=&quot;keyword&quot;&gt;functions&lt;/span&gt;           │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│                    (activity.rs)                      │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└──────────────────────┬──────────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                       │  per-second deltas&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌──────────────────────▼──────────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│               Background Worker                      │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│        Samples &lt;span class=&quot;keyword&quot;&gt;all&lt;/span&gt; backends every &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt; second           │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│                   (bgworker.rs)                      │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└──────────────────────┬──────────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                       │  raw counters&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌──────────────────────▼──────────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│           &lt;span class=&quot;type&quot;&gt;Double&lt;/span&gt;-Buffered Shared Memory              │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│        &lt;span class=&quot;keyword&quot;&gt;Lock&lt;/span&gt;-free reads, up &lt;span class=&quot;keyword&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;1024&lt;/span&gt; backends          │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│                    (shmem.rs)                         │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└──────────────────────┬──────────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                       │  reads /proc/[pid]&lt;span class=&quot;comment&quot;&gt;/*&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;┌──────────────────────▼──────────────────────────────┐&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;│               /proc Filesystem Parser                │&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;│   /proc/[pid]/stat, statm, io, status, /proc/meminfo│&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;│                 (proc_stats.rs)                      │&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;└─────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A background worker wakes every second and iterates over all PostgreSQL backend PIDs.&lt;/li&gt;
&lt;li&gt;For each PID, it reads &lt;code&gt;/proc/[pid]/stat&lt;/code&gt;, &lt;code&gt;/proc/[pid]/statm&lt;/code&gt;, &lt;code&gt;/proc/[pid]/io&lt;/code&gt;, and &lt;code&gt;/proc/[pid]/status&lt;/code&gt; to collect raw CPU ticks, memory pages, I&amp;#x2F;O byte counters, and context switch counts.&lt;/li&gt;
&lt;li&gt;The raw readings are stored in a double-buffered shared memory region. The double-buffer design lets SQL queries read the previous-second snapshot without blocking the worker’s current write.&lt;/li&gt;
&lt;li&gt;When you query the &lt;code&gt;pg_pidstat&lt;/code&gt; view, the &lt;code&gt;activity&lt;/code&gt; module computes per-second deltas (rates) from the raw counters and joins them with &lt;code&gt;pg_stat_activity&lt;/code&gt; columns.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This design ensures &lt;strong&gt;zero lock contention on reads&lt;/strong&gt; — the only exclusive lock is a brief window when the background worker swaps the active buffer.&lt;/p&gt;
&lt;h2 id=&quot;Metrics-Reference&quot;&gt;&lt;a href=&quot;#Metrics-Reference&quot; class=&quot;headerlink&quot; title=&quot;Metrics Reference&quot;&gt;&lt;/a&gt;Metrics Reference&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;pg_pidstat&lt;/code&gt; view includes all standard &lt;code&gt;pg_stat_activity&lt;/code&gt; columns plus these monitoring columns:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cpu_percent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPU usage as % of total system capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_percent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resident memory as % of total system RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_usage_mb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resident memory in megabytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;io_read_bytes_per_sec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disk read throughput (bytes&amp;#x2F;sec)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;io_write_bytes_per_sec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disk write throughput (bytes&amp;#x2F;sec)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;io_read_ops_per_sec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;io_write_ops_per_sec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;io_wait&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt; if the process is in disk sleep state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;voluntary_ctxt_switches_per_sec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Voluntary context switches per second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nonvoluntary_ctxt_switches_per_sec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Non-voluntary context switches per second&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;Usage-Examples&quot;&gt;&lt;a href=&quot;#Usage-Examples&quot; class=&quot;headerlink&quot; title=&quot;Usage Examples&quot;&gt;&lt;/a&gt;Usage Examples&lt;/h2&gt;&lt;h3 id=&quot;Find-CPU-Heavy-Backends&quot;&gt;&lt;a href=&quot;#Find-CPU-Heavy-Backends&quot; class=&quot;headerlink&quot; title=&quot;Find CPU-Heavy Backends&quot;&gt;&lt;/a&gt;Find CPU-Heavy Backends&lt;/h3&gt;&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; pid, datname, usename, state, query,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       cpu_percent, memory_percent, memory_usage_mb&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; pg_pidstat&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;BY&lt;/span&gt; cpu_percent &lt;span class=&quot;keyword&quot;&gt;DESC&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Identify-I-O-Intensive-Queries&quot;&gt;&lt;a href=&quot;#Identify-I-O-Intensive-Queries&quot; class=&quot;headerlink&quot; title=&quot;Identify I&amp;#x2F;O-Intensive Queries&quot;&gt;&lt;/a&gt;Identify I&amp;#x2F;O-Intensive Queries&lt;/h3&gt;&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; pid, usename, state, query,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       io_read_bytes_per_sec,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       io_write_bytes_per_sec,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       io_read_ops_per_sec,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       io_write_ops_per_sec,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       io_wait&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; pg_pidstat&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; state &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;active&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;BY&lt;/span&gt; io_read_bytes_per_sec &lt;span class=&quot;operator&quot;&gt;+&lt;/span&gt; io_write_bytes_per_sec &lt;span class=&quot;keyword&quot;&gt;DESC&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Detect-Context-Switch-Hotspots&quot;&gt;&lt;a href=&quot;#Detect-Context-Switch-Hotspots&quot; class=&quot;headerlink&quot; title=&quot;Detect Context Switch Hotspots&quot;&gt;&lt;/a&gt;Detect Context Switch Hotspots&lt;/h3&gt;&lt;p&gt;High non-voluntary context switches indicate CPU contention — too many backends fighting for CPU time:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; pid, usename, query,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       voluntary_ctxt_switches_per_sec,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       nonvoluntary_ctxt_switches_per_sec&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; pg_pidstat&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; state &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;active&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;BY&lt;/span&gt; nonvoluntary_ctxt_switches_per_sec &lt;span class=&quot;keyword&quot;&gt;DESC&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Full-Troubleshooting-Dashboard&quot;&gt;&lt;a href=&quot;#Full-Troubleshooting-Dashboard&quot; class=&quot;headerlink&quot; title=&quot;Full Troubleshooting Dashboard&quot;&gt;&lt;/a&gt;Full Troubleshooting Dashboard&lt;/h3&gt;&lt;p&gt;Combine all metrics for a comprehensive view of backend resource usage:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; pid, datname, usename, state,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       &lt;span class=&quot;keyword&quot;&gt;left&lt;/span&gt;(query, &lt;span class=&quot;number&quot;&gt;80&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; query_preview,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       round(cpu_percent::&lt;span class=&quot;type&quot;&gt;numeric&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; cpu_pct,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       round(memory_usage_mb::&lt;span class=&quot;type&quot;&gt;numeric&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; mem_mb,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       round((io_read_bytes_per_sec &lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;1024&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;1024&lt;/span&gt;)::&lt;span class=&quot;type&quot;&gt;numeric&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; read_mb_s,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       round((io_write_bytes_per_sec &lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;1024&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;1024&lt;/span&gt;)::&lt;span class=&quot;type&quot;&gt;numeric&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; write_mb_s,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       io_wait,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       round(voluntary_ctxt_switches_per_sec::&lt;span class=&quot;type&quot;&gt;numeric&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; vol_cs,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       round(nonvoluntary_ctxt_switches_per_sec::&lt;span class=&quot;type&quot;&gt;numeric&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; nvol_cs&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; pg_pidstat&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; state &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;active&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;BY&lt;/span&gt; cpu_percent &lt;span class=&quot;keyword&quot;&gt;DESC&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Monitoring-Over-Time-with-pg-cron&quot;&gt;&lt;a href=&quot;#Monitoring-Over-Time-with-pg-cron&quot; class=&quot;headerlink&quot; title=&quot;Monitoring Over Time with pg_cron&quot;&gt;&lt;/a&gt;Monitoring Over Time with pg_cron&lt;/h3&gt;&lt;p&gt;Pair pg_pidstat with &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/citusdata/pg_cron&quot;&gt;pg_cron&lt;/a&gt; to build a historical resource usage table:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE TABLE&lt;/span&gt; backend_metrics_history (&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    captured_at timestamptz &lt;span class=&quot;keyword&quot;&gt;DEFAULT&lt;/span&gt; now(),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    pid &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    datname name,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    usename name,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    state text,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    cpu_percent float8,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    memory_usage_mb float8,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    io_read_bytes_per_sec float8,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    io_write_bytes_per_sec float8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Sample every 10 seconds via pg_cron&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; cron.schedule(&lt;span class=&quot;string&quot;&gt;&amp;#x27;backend-metrics&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;10 seconds&amp;#x27;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;$$&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;INSERT INTO&lt;/span&gt; backend_metrics_history&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    (pid, datname, usename, state, cpu_percent, memory_usage_mb,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     io_read_bytes_per_sec, io_write_bytes_per_sec)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; pid, datname, usename, state, cpu_percent, memory_usage_mb,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       io_read_bytes_per_sec, io_write_bytes_per_sec&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; pg_pidstat &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; state &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;active&amp;#x27;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;$$);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Why-pg-pidstat-Over-External-Monitoring&quot;&gt;&lt;a href=&quot;#Why-pg-pidstat-Over-External-Monitoring&quot; class=&quot;headerlink&quot; title=&quot;Why pg_pidstat Over External Monitoring?&quot;&gt;&lt;/a&gt;Why pg_pidstat Over External Monitoring?&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;External tools (pidstat, top)&lt;/th&gt;
&lt;th&gt;pg_pidstat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;PID-to-query mapping&lt;/td&gt;
&lt;td&gt;Manual correlation&lt;/td&gt;
&lt;td&gt;Automatic (joined with &lt;code&gt;pg_stat_activity&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query interface&lt;/td&gt;
&lt;td&gt;Shell parsing &amp;#x2F; custom scripts&lt;/td&gt;
&lt;td&gt;Standard SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alerting integration&lt;/td&gt;
&lt;td&gt;Requires glue code&lt;/td&gt;
&lt;td&gt;Use any SQL-based alerting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historical storage&lt;/td&gt;
&lt;td&gt;External time-series DB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INSERT INTO ... SELECT&lt;/code&gt; from the view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Granularity&lt;/td&gt;
&lt;td&gt;Per-process only&lt;/td&gt;
&lt;td&gt;Per-backend with database context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Agent on every host&lt;/td&gt;
&lt;td&gt;PostgreSQL extension — no extra process&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The key advantage is &lt;strong&gt;context&lt;/strong&gt;: knowing that PID 12345 uses 90% CPU is useful, but knowing it’s the &lt;code&gt;analytics&lt;/code&gt; user running a sequential scan on the &lt;code&gt;orders&lt;/code&gt; table in the &lt;code&gt;production&lt;/code&gt; database is actionable.&lt;/p&gt;
&lt;h2 id=&quot;Performance-Design&quot;&gt;&lt;a href=&quot;#Performance-Design&quot; class=&quot;headerlink&quot; title=&quot;Performance Design&quot;&gt;&lt;/a&gt;Performance Design&lt;/h2&gt;&lt;p&gt;pg_pidstat is designed for minimal overhead in production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Release-mode optimization&lt;/strong&gt;: &lt;code&gt;opt-level = 3&lt;/code&gt;, fat LTO, single codegen unit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lock-free reads&lt;/strong&gt;: Double-buffered shared memory means &lt;code&gt;SELECT&lt;/code&gt; from the view never blocks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brief exclusive writes&lt;/strong&gt;: Only during the buffer swap (microseconds)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bounded memory&lt;/strong&gt;: Fixed-size shared memory for up to 1024 backends&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1-second sampling&lt;/strong&gt;: Background worker reads &lt;code&gt;/proc&lt;/code&gt; once per second — negligible system load&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Conclusion&quot;&gt;&lt;a href=&quot;#Conclusion&quot; class=&quot;headerlink&quot; title=&quot;Conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;&lt;p&gt;pg_pidstat brings OS-level process monitoring into PostgreSQL itself. Instead of correlating PIDs across &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;pidstat&lt;/code&gt;, and &lt;code&gt;pg_stat_activity&lt;/code&gt;, you get CPU, memory, I&amp;#x2F;O, and context switch metrics in a single SQL view — with full connection context attached.&lt;/p&gt;
&lt;p&gt;For anyone running PostgreSQL on Linux and needing to quickly identify resource-heavy backends, pg_pidstat turns a multi-tool investigation into one query.&lt;/p&gt;
&lt;h2 id=&quot;References&quot;&gt;&lt;a href=&quot;#References&quot; class=&quot;headerlink&quot; title=&quot;References&quot;&gt;&lt;/a&gt;References&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg_pidstat&quot;&gt;pg_pidstat GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/pgcentralfoundation/pgrx&quot;&gt;pgrx — Rust Framework for PostgreSQL Extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW&quot;&gt;PostgreSQL pg_stat_activity Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/pg-pidstat-real-time-postgresql-backend-monitoring/&quot;&gt;https://isdaniel.github.io/pg-pidstat-real-time-postgresql-backend-monitoring/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>pg-walstream: High-Performance PostgreSQL WAL Streaming in Rust</title>
    <link href="https://isdaniel.github.io/pg-walstream-rust-postgresql-wal-streaming/"/>
    <id>https://isdaniel.github.io/pg-walstream-rust-postgresql-wal-streaming/</id>
    <published>2026-04-21T14:00:00.000Z</published>
    <updated>2026-04-21T14:00:00.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>A high-performance Rust library for parsing and streaming PostgreSQL WAL messages via logical replication — with load testing results showing 200K+ events/sec.</summary>
    <content type="html">&lt;h1 id=&quot;pg-walstream-High-Performance-PostgreSQL-WAL-Streaming-in-Rust&quot;&gt;&lt;a href=&quot;#pg-walstream-High-Performance-PostgreSQL-WAL-Streaming-in-Rust&quot; class=&quot;headerlink&quot; title=&quot;pg-walstream: High-Performance PostgreSQL WAL Streaming in Rust&quot;&gt;&lt;/a&gt;pg-walstream: High-Performance PostgreSQL WAL Streaming in Rust&lt;/h1&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg-walstream&quot;&gt;pg-walstream&lt;/a&gt; is a Rust library for parsing and streaming PostgreSQL Write-Ahead Log (WAL) messages through logical and physical replication protocols. It provides a type-safe, async-first interface for building real-time Change Data Capture (CDC) pipelines.&lt;/p&gt;
&lt;p&gt;If you need to react to database changes in real-time — event-driven architectures, data pipelines, audit logging, cache invalidation, or search index syncing — pg-walstream abstracts the complex PostgreSQL replication protocol into a clean Rust API.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://crates.io/crates/pg_walstream&quot;&gt;crates.io&lt;/a&gt; | &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://docs.rs/pg-walstream&quot;&gt;API Docs&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Background-PostgreSQL-WAL-and-Replication&quot;&gt;&lt;a href=&quot;#Background-PostgreSQL-WAL-and-Replication&quot; class=&quot;headerlink&quot; title=&quot;Background: PostgreSQL WAL and Replication&quot;&gt;&lt;/a&gt;Background: PostgreSQL WAL and Replication&lt;/h2&gt;&lt;p&gt;Before diving into pg-walstream, it helps to understand how PostgreSQL replication works at a fundamental level.&lt;/p&gt;
&lt;h3 id=&quot;What-is-WAL-Write-Ahead-Logging&quot;&gt;&lt;a href=&quot;#What-is-WAL-Write-Ahead-Logging&quot; class=&quot;headerlink&quot; title=&quot;What is WAL (Write-Ahead Logging)?&quot;&gt;&lt;/a&gt;What is WAL (Write-Ahead Logging)?&lt;/h3&gt;&lt;p&gt;WAL is PostgreSQL’s mechanism for ensuring data durability. The core idea: &lt;strong&gt;write the transaction log first, then write the actual data&lt;/strong&gt;. Every INSERT, UPDATE, DELETE, and DDL change is recorded sequentially in WAL files before the corresponding data pages are flushed to disk.&lt;/p&gt;
&lt;p&gt;This design provides two key benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Crash recovery&lt;/strong&gt; — if the system crashes, PostgreSQL can replay (REDO) the WAL to recover committed transactions that weren’t yet flushed to disk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I&amp;#x2F;O efficiency&lt;/strong&gt; — sequential WAL writes are much faster than random page writes, so PostgreSQL doesn’t need to flush dirty pages on every commit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each WAL record is identified by a &lt;strong&gt;Log Sequence Number (LSN)&lt;/strong&gt; — a monotonically increasing pointer into the WAL stream. LSN is the backbone of replication: it tells both the sender and receiver exactly where they are in the change history.&lt;/p&gt;
&lt;figure class=&quot;highlight n1ql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;               WAL Stream&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌───────┬───────┬───────┬───────┬───────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│ BEGIN │&lt;span class=&quot;keyword&quot;&gt;INSERT&lt;/span&gt; │&lt;span class=&quot;keyword&quot;&gt;UPDATE&lt;/span&gt; │&lt;span class=&quot;keyword&quot;&gt;DELETE&lt;/span&gt; │&lt;span class=&quot;keyword&quot;&gt;COMMIT&lt;/span&gt; │  ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│LSN: &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt; │LSN: &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt; │LSN: &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt; │LSN: &lt;span class=&quot;number&quot;&gt;4&lt;/span&gt; │LSN: &lt;span class=&quot;number&quot;&gt;5&lt;/span&gt; │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└───────┴───────┴───────┴───────┴───────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                      │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;           Replayed &lt;span class=&quot;keyword&quot;&gt;by&lt;/span&gt; standby / consumed &lt;span class=&quot;keyword&quot;&gt;by&lt;/span&gt; CDC&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;For a deeper dive into WAL internals, see my earlier post: &lt;a href=&quot;https://isdaniel.github.io/postgresql-wal-introduce/&quot;&gt;PostgreSQL WAL (Write-Ahead Logging) mechanism&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Physical-vs-Logical-Replication&quot;&gt;&lt;a href=&quot;#Physical-vs-Logical-Replication&quot; class=&quot;headerlink&quot; title=&quot;Physical vs. Logical Replication&quot;&gt;&lt;/a&gt;Physical vs. Logical Replication&lt;/h3&gt;&lt;p&gt;PostgreSQL supports two replication modes, each serving different use cases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Physical Replication&lt;/strong&gt; streams raw WAL bytes — the exact disk-level changes — to a standby server. The standby replays these byte-for-byte, producing an identical copy of the primary. This is what powers read replicas and high-availability setups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Logical Replication&lt;/strong&gt; decodes WAL into higher-level change events (INSERT, UPDATE, DELETE) using an output plugin (e.g., &lt;code&gt;pgoutput&lt;/code&gt;). Instead of raw disk blocks, consumers receive structured messages like “row X was inserted into table Y with these column values.” This is what powers CDC pipelines.&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Physical Replication:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;Primary&lt;/span&gt; ──[raw WAL bytes]──▶ Standby (byte&lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt;identical &lt;span class=&quot;keyword&quot;&gt;copy&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Logical Replication:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;Primary&lt;/span&gt; ──[pgoutput plugin]──▶ Decoded Change Events ──▶ CDC Consumer&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                                  (&lt;span class=&quot;keyword&quot;&gt;INSERT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;keyword&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;keyword&quot;&gt;DELETE&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;pg-walstream supports &lt;strong&gt;both&lt;/strong&gt; modes — physical replication for standby&amp;#x2F;backup scenarios and logical replication for CDC.&lt;/p&gt;
&lt;h3 id=&quot;The-Logical-Replication-Protocol&quot;&gt;&lt;a href=&quot;#The-Logical-Replication-Protocol&quot; class=&quot;headerlink&quot; title=&quot;The Logical Replication Protocol&quot;&gt;&lt;/a&gt;The Logical Replication Protocol&lt;/h3&gt;&lt;p&gt;When a client connects to PostgreSQL in replication mode, they communicate via the &lt;strong&gt;Streaming Replication Protocol&lt;/strong&gt;. For logical replication, the flow works as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Client creates a replication slot&lt;/strong&gt; — this tells PostgreSQL to retain WAL segments needed by this consumer, preventing them from being recycled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client starts replication&lt;/strong&gt; from a slot, specifying the output plugin (&lt;code&gt;pgoutput&lt;/code&gt;) and which publication to subscribe to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL streams messages&lt;/strong&gt; — the server continuously sends WAL data messages (&lt;code&gt;XLogData&lt;/code&gt;) containing decoded change events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client sends feedback&lt;/strong&gt; — periodically, the client reports its progress (flushed LSN, applied LSN) back to the server. This lets PostgreSQL know which WAL segments can be safely recycled.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The decoded messages follow the &lt;strong&gt;logical replication message format&lt;/strong&gt;, which has evolved across four protocol versions:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Protocol Version&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;PostgreSQL&lt;/th&gt;
&lt;th&gt;Key Additions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;v1&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;14+&lt;/td&gt;
&lt;td&gt;Core messages: BEGIN, COMMIT, INSERT, UPDATE, DELETE, TRUNCATE, RELATION, TYPE, ORIGIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;v2&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;14+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Streaming transactions&lt;/strong&gt;: STREAM_START, STREAM_STOP, STREAM_COMMIT, STREAM_ABORT — allows consuming large, in-progress transactions before COMMIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;v3&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;14+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Two-phase commit&lt;/strong&gt;: BEGIN_PREPARE, PREPARE, COMMIT_PREPARED, ROLLBACK_PREPARED, STREAM_PREPARE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;v4&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;17+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Parallel streaming&lt;/strong&gt;, &lt;code&gt;abort_lsn&lt;/code&gt; field for more precise abort handling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Each message type carries specific data. For example, an INSERT message contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Relation ID&lt;/strong&gt; — which table the row belongs to&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tuple data&lt;/strong&gt; — the column values of the new row, typed by OID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The RELATION message (sent once per table, or when a schema changes) maps the relation ID to a table name, namespace, and column definitions — so the consumer can interpret the tuple data.&lt;/p&gt;
&lt;h3 id=&quot;Why-Build-a-Library-for-This&quot;&gt;&lt;a href=&quot;#Why-Build-a-Library-for-This&quot; class=&quot;headerlink&quot; title=&quot;Why Build a Library for This?&quot;&gt;&lt;/a&gt;Why Build a Library for This?&lt;/h3&gt;&lt;p&gt;Implementing the replication protocol from scratch involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Managing the PostgreSQL wire protocol and authentication (cleartext, MD5, SCRAM-SHA-256)&lt;/li&gt;
&lt;li&gt;Parsing binary WAL messages with protocol-version-specific formats&lt;/li&gt;
&lt;li&gt;Tracking LSN positions and sending periodic feedback to avoid WAL bloat&lt;/li&gt;
&lt;li&gt;Handling connection drops, retries, and replication slot lifecycle&lt;/li&gt;
&lt;li&gt;Dealing with streaming transactions that may arrive interleaved&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;pg-walstream encapsulates all of this complexity into a type-safe Rust API, so you can focus on what to &lt;strong&gt;do&lt;/strong&gt; with the change events rather than how to &lt;strong&gt;receive&lt;/strong&gt; them.&lt;/p&gt;
&lt;h2 id=&quot;Key-Features&quot;&gt;&lt;a href=&quot;#Key-Features&quot; class=&quot;headerlink&quot; title=&quot;Key Features&quot;&gt;&lt;/a&gt;Key Features&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protocol v1–v4 support&lt;/strong&gt; including streaming transactions (v2), two-phase commit (v3), and parallel streaming (v4)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two connection backends&lt;/strong&gt;: &lt;code&gt;libpq&lt;/code&gt; (C FFI, default) and &lt;code&gt;rustls-tls&lt;/code&gt; (pure Rust, no runtime C deps)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero-copy buffers&lt;/strong&gt; via the &lt;code&gt;bytes&lt;/code&gt; crate — no unnecessary data cloning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serde-based deserialization&lt;/strong&gt; — map WAL events directly to Rust structs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic retry&lt;/strong&gt; with exponential backoff for transient failures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Async&amp;#x2F;await&lt;/strong&gt; with &lt;code&gt;tokio&lt;/code&gt; and &lt;code&gt;futures::Stream&lt;/code&gt; integration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory efficient&lt;/strong&gt; — all configurations stay under 18 MB RSS&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Architecture&quot;&gt;&lt;a href=&quot;#Architecture&quot; class=&quot;headerlink&quot; title=&quot;Architecture&quot;&gt;&lt;/a&gt;Architecture&lt;/h2&gt;&lt;figure class=&quot;highlight pgsql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;┌──────────────────────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│          Application Layer               │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  (Your CDC / &lt;span class=&quot;keyword&quot;&gt;Replication&lt;/span&gt; Logic)          │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└──────────────┬───────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;               │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌──────────────▼───────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│    LogicalReplicationStream              │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  - &lt;span class=&quot;keyword&quot;&gt;Connection&lt;/span&gt; management &amp;amp; retry         │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  - Event processing &amp;amp; LSN feedback       │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  - &lt;span class=&quot;keyword&quot;&gt;Snapshot&lt;/span&gt; export support               │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└──────────────┬───────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;               │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌──────────────▼───────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  LogicalReplicationParser                │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  - Protocol v1-v4 parsing                │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  - Zero-&lt;span class=&quot;keyword&quot;&gt;copy&lt;/span&gt; message deserialization     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  - Streaming &lt;span class=&quot;keyword&quot;&gt;transaction&lt;/span&gt; support         │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└──────────────┬───────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;               │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌──────────────▼───────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│     PgReplicationConnection              │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  ┌─────────────────┬──────────────────┐  │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  │  libpq backend  │ rustls-tls       │  │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  │  (C FFI)        │ (pure Rust)      │  │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  └─────────────────┴──────────────────┘  │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  Compile-&lt;span class=&quot;type&quot;&gt;time&lt;/span&gt; feature flag selection     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└──────────────┬───────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;               │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌──────────────▼───────────────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│     BufferReader / BufferWriter          │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  - Zero-&lt;span class=&quot;keyword&quot;&gt;copy&lt;/span&gt; operations (bytes crate)    │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  - Binary protocol handling              │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└──────────────────────────────────────────┘&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;The library has two connection backends selected at compile time:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Feature Flag&lt;/th&gt;
&lt;th&gt;Dependencies&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;libpq (default)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;libpq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;libpq-dev&lt;/code&gt;, &lt;code&gt;libclang-dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;FFI wrapper around PostgreSQL’s C client library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rustls-tls&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rustls-tls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmake&lt;/code&gt; (build-time only)&lt;/td&gt;
&lt;td&gt;Pure-Rust TLS via rustls + aws-lc-rs (hardware-accelerated)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;When both features are enabled, &lt;code&gt;rustls-tls&lt;/code&gt; takes priority automatically.&lt;/p&gt;
&lt;h2 id=&quot;Getting-Started&quot;&gt;&lt;a href=&quot;#Getting-Started&quot; class=&quot;headerlink&quot; title=&quot;Getting Started&quot;&gt;&lt;/a&gt;Getting Started&lt;/h2&gt;&lt;h3 id=&quot;Installation&quot;&gt;&lt;a href=&quot;#Installation&quot; class=&quot;headerlink&quot; title=&quot;Installation&quot;&gt;&lt;/a&gt;Installation&lt;/h3&gt;&lt;figure class=&quot;highlight toml&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Default (libpq backend)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;section&quot;&gt;[dependencies]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attr&quot;&gt;pg_walstream&lt;/span&gt; = &lt;span class=&quot;string&quot;&gt;&amp;quot;0.6.2&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Pure-Rust backend (no C runtime deps)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attr&quot;&gt;pg_walstream&lt;/span&gt; = &amp;#123; version = &lt;span class=&quot;string&quot;&gt;&amp;quot;0.6.2&amp;quot;&lt;/span&gt;, default-features = &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;, features = [&lt;span class=&quot;string&quot;&gt;&amp;quot;rustls-tls&amp;quot;&lt;/span&gt;] &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;PostgreSQL-Setup&quot;&gt;&lt;a href=&quot;#PostgreSQL-Setup&quot; class=&quot;headerlink&quot; title=&quot;PostgreSQL Setup&quot;&gt;&lt;/a&gt;PostgreSQL Setup&lt;/h3&gt;&lt;p&gt;Enable logical replication in &lt;code&gt;postgresql.conf&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight ini&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attr&quot;&gt;wal_level&lt;/span&gt; = logical&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attr&quot;&gt;max_replication_slots&lt;/span&gt; = &lt;span class=&quot;number&quot;&gt;4&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attr&quot;&gt;max_wal_senders&lt;/span&gt; = &lt;span class=&quot;number&quot;&gt;4&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Create a publication and replication user:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; PUBLICATION my_publication &lt;span class=&quot;keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;TABLE&lt;/span&gt; users, orders;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;USER&lt;/span&gt; replication_user &lt;span class=&quot;keyword&quot;&gt;WITH&lt;/span&gt; REPLICATION PASSWORD &lt;span class=&quot;string&quot;&gt;&amp;#x27;secure_password&amp;#x27;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ALL&lt;/span&gt; TABLES &lt;span class=&quot;keyword&quot;&gt;IN&lt;/span&gt; SCHEMA public &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; replication_user;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Basic-Streaming-Example&quot;&gt;&lt;a href=&quot;#Basic-Streaming-Example&quot; class=&quot;headerlink&quot; title=&quot;Basic Streaming Example&quot;&gt;&lt;/a&gt;Basic Streaming Example&lt;/h3&gt;&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;46&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;use&lt;/span&gt; pg_walstream::&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    LogicalReplicationStream, ReplicationStreamConfig, RetryConfig,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    StreamingMode, CancellationToken,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;use&lt;/span&gt; std::time::Duration;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[tokio::main]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;main&lt;/span&gt;() &lt;span class=&quot;punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;Result&lt;/span&gt;&amp;lt;(), &lt;span class=&quot;type&quot;&gt;Box&lt;/span&gt;&amp;lt;&lt;span class=&quot;keyword&quot;&gt;dyn&lt;/span&gt; std::error::Error&amp;gt;&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;config&lt;/span&gt; = ReplicationStreamConfig::&lt;span class=&quot;title function_ invoke__&quot;&gt;new&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;string&quot;&gt;&amp;quot;my_slot&amp;quot;&lt;/span&gt;.&lt;span class=&quot;title function_ invoke__&quot;&gt;to_string&lt;/span&gt;(),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;string&quot;&gt;&amp;quot;my_publication&amp;quot;&lt;/span&gt;.&lt;span class=&quot;title function_ invoke__&quot;&gt;to_string&lt;/span&gt;(),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;,                              &lt;span class=&quot;comment&quot;&gt;// Protocol version&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        StreamingMode::On,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        Duration::&lt;span class=&quot;title function_ invoke__&quot;&gt;from_secs&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;),        &lt;span class=&quot;comment&quot;&gt;// Feedback interval&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        Duration::&lt;span class=&quot;title function_ invoke__&quot;&gt;from_secs&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;30&lt;/span&gt;),        &lt;span class=&quot;comment&quot;&gt;// Connection timeout&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        Duration::&lt;span class=&quot;title function_ invoke__&quot;&gt;from_secs&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;60&lt;/span&gt;),        &lt;span class=&quot;comment&quot;&gt;// Health check interval&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        RetryConfig::&lt;span class=&quot;title function_ invoke__&quot;&gt;default&lt;/span&gt;(),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    );&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;mut &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;stream&lt;/span&gt; = LogicalReplicationStream::&lt;span class=&quot;title function_ invoke__&quot;&gt;new&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;string&quot;&gt;&amp;quot;postgresql://postgres:password@localhost:5432/mydb?replication=database&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        config,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ).&lt;span class=&quot;keyword&quot;&gt;await&lt;/span&gt;?;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    stream.&lt;span class=&quot;title function_ invoke__&quot;&gt;start&lt;/span&gt;(&lt;span class=&quot;literal&quot;&gt;None&lt;/span&gt;).&lt;span class=&quot;keyword&quot;&gt;await&lt;/span&gt;?;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;cancel_token&lt;/span&gt; = CancellationToken::&lt;span class=&quot;title function_ invoke__&quot;&gt;new&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;loop&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;match&lt;/span&gt; stream.&lt;span class=&quot;title function_ invoke__&quot;&gt;next_event_with_retry&lt;/span&gt;(&amp;amp;cancel_token).&lt;span class=&quot;keyword&quot;&gt;await&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;title function_ invoke__&quot;&gt;Ok&lt;/span&gt;(event) =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;built_in&quot;&gt;println!&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;Received: &amp;#123;:?&amp;#125;&amp;quot;&lt;/span&gt;, event);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                stream.shared_lsn_feedback.&lt;span class=&quot;title function_ invoke__&quot;&gt;update_applied_lsn&lt;/span&gt;(event.lsn.&lt;span class=&quot;title function_ invoke__&quot;&gt;value&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;title function_ invoke__&quot;&gt;Err&lt;/span&gt;(e) &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; matches!(e, pg_walstream::ReplicationError::&lt;span class=&quot;title function_ invoke__&quot;&gt;Cancelled&lt;/span&gt;(_)) =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;built_in&quot;&gt;println!&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;Shutting down gracefully&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;title function_ invoke__&quot;&gt;Err&lt;/span&gt;(e) =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;built_in&quot;&gt;eprintln!&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;Error: &amp;#123;&amp;#125;&amp;quot;&lt;/span&gt;, e);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;title function_ invoke__&quot;&gt;Ok&lt;/span&gt;(())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Typed-Deserialization&quot;&gt;&lt;a href=&quot;#Typed-Deserialization&quot; class=&quot;headerlink&quot; title=&quot;Typed Deserialization&quot;&gt;&lt;/a&gt;Typed Deserialization&lt;/h3&gt;&lt;p&gt;pg-walstream supports mapping WAL events directly to Rust structs via Serde:&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;use&lt;/span&gt; serde::Deserialize;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;use&lt;/span&gt; pg_walstream::EventType;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[derive(Debug, Deserialize)]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;User&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    id: &lt;span class=&quot;type&quot;&gt;i64&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    username: &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    email: &lt;span class=&quot;type&quot;&gt;Option&lt;/span&gt;&amp;lt;&lt;span class=&quot;type&quot;&gt;String&lt;/span&gt;&amp;gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    score: &lt;span class=&quot;type&quot;&gt;f64&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    active: &lt;span class=&quot;type&quot;&gt;bool&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// Inside your event loop:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;match&lt;/span&gt; &amp;amp;event.event_type &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    EventType::Insert &amp;#123; .. &amp;#125; =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;user&lt;/span&gt;: User = event.&lt;span class=&quot;title function_ invoke__&quot;&gt;deserialize_insert&lt;/span&gt;()?;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;println!&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;New user: &amp;#123;:?&amp;#125;&amp;quot;&lt;/span&gt;, user);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    EventType::Update &amp;#123; .. &amp;#125; =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; (old, new): (&lt;span class=&quot;type&quot;&gt;Option&lt;/span&gt;&amp;lt;User&amp;gt;, User) = event.&lt;span class=&quot;title function_ invoke__&quot;&gt;deserialize_update&lt;/span&gt;()?;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;println!&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;Updated: &amp;#123;:?&amp;#125; -&amp;gt; &amp;#123;:?&amp;#125;&amp;quot;&lt;/span&gt;, old, new);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    EventType::Delete &amp;#123; .. &amp;#125; =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;user&lt;/span&gt;: User = event.&lt;span class=&quot;title function_ invoke__&quot;&gt;deserialize_delete&lt;/span&gt;()?;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;println!&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;Deleted: &amp;#123;:?&amp;#125;&amp;quot;&lt;/span&gt;, user);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    _ =&amp;gt; &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Producer-Consumer-Pattern-with-tokio-spawn&quot;&gt;&lt;a href=&quot;#Producer-Consumer-Pattern-with-tokio-spawn&quot; class=&quot;headerlink&quot; title=&quot;Producer&amp;#x2F;Consumer Pattern with tokio::spawn&quot;&gt;&lt;/a&gt;Producer&amp;#x2F;Consumer Pattern with tokio::spawn&lt;/h3&gt;&lt;p&gt;For high-throughput scenarios, decouple WAL reading from event processing using channels:&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;use&lt;/span&gt; tokio::sync::mpsc;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;use&lt;/span&gt; pg_walstream::&amp;#123;ChangeEvent, ReplicationError&amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;run_producer&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; stream: LogicalReplicationStream,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    cancel_token: CancellationToken,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    tx: mpsc::Sender&amp;lt;ChangeEvent&amp;gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    stream.&lt;span class=&quot;title function_ invoke__&quot;&gt;start&lt;/span&gt;(&lt;span class=&quot;literal&quot;&gt;None&lt;/span&gt;).&lt;span class=&quot;keyword&quot;&gt;await&lt;/span&gt;.&lt;span class=&quot;title function_ invoke__&quot;&gt;unwrap&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;loop&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;match&lt;/span&gt; stream.&lt;span class=&quot;title function_ invoke__&quot;&gt;next_event_with_retry&lt;/span&gt;(&amp;amp;cancel_token).&lt;span class=&quot;keyword&quot;&gt;await&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;title function_ invoke__&quot;&gt;Ok&lt;/span&gt;(event) =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                stream.shared_lsn_feedback.&lt;span class=&quot;title function_ invoke__&quot;&gt;update_applied_lsn&lt;/span&gt;(event.lsn.&lt;span class=&quot;title function_ invoke__&quot;&gt;value&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; tx.&lt;span class=&quot;title function_ invoke__&quot;&gt;send&lt;/span&gt;(event).&lt;span class=&quot;keyword&quot;&gt;await&lt;/span&gt;.&lt;span class=&quot;title function_ invoke__&quot;&gt;is_err&lt;/span&gt;() &amp;#123; &lt;span class=&quot;keyword&quot;&gt;break&lt;/span&gt;; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;title function_ invoke__&quot;&gt;Err&lt;/span&gt;(ReplicationError::&lt;span class=&quot;title function_ invoke__&quot;&gt;Cancelled&lt;/span&gt;(_)) =&amp;gt; &lt;span class=&quot;keyword&quot;&gt;break&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;title function_ invoke__&quot;&gt;Err&lt;/span&gt;(e) =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;built_in&quot;&gt;eprintln!&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;Fatal: &amp;#123;e&amp;#125;&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    stream.&lt;span class=&quot;title function_ invoke__&quot;&gt;stop&lt;/span&gt;().&lt;span class=&quot;keyword&quot;&gt;await&lt;/span&gt;.&lt;span class=&quot;title function_ invoke__&quot;&gt;ok&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Load-Testing-Results&quot;&gt;&lt;a href=&quot;#Load-Testing-Results&quot; class=&quot;headerlink&quot; title=&quot;Load Testing Results&quot;&gt;&lt;/a&gt;Load Testing Results&lt;/h2&gt;&lt;p&gt;The library was benchmarked on an 8-core Intel Xeon Platinum 8370C (16 GB RAM, Ubuntu 22.04) across three PostgreSQL configurations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PG16&lt;/strong&gt; with protocol v4 + parallel streaming (rustls-tls backend)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PG18&lt;/strong&gt; with binary mode + direct TLS negotiation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PG18 + COPY&lt;/strong&gt; with COPY generator optimization&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DML-Throughput-events-sec&quot;&gt;&lt;a href=&quot;#DML-Throughput-events-sec&quot; class=&quot;headerlink&quot; title=&quot;DML Throughput (events&amp;#x2F;sec)&quot;&gt;&lt;/a&gt;DML Throughput (events&amp;#x2F;sec)&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG16&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG18 Binary+DirectTLS&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG18 +COPY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;148,533&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;168,205&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;209,193&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch-5000&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;132,623&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;151,820&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;190,687&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4-Writers&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;135,036&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;159,233&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;193,354&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed-DML&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;42,198&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;176,580&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;186,019&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch-100&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;22,270&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;141,597&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;199,780&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide-20col&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;18,917&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;172,772&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;173,283&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload-2KB&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;14,017&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;114,884&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;134,323&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Peak throughput: &lt;strong&gt;209,193 events&amp;#x2F;sec&lt;/strong&gt; (PG18 + COPY, Baseline scenario).&lt;/p&gt;
&lt;h3 id=&quot;Data-Throughput-MB-s&quot;&gt;&lt;a href=&quot;#Data-Throughput-MB-s&quot; class=&quot;headerlink&quot; title=&quot;Data Throughput (MB&amp;#x2F;s)&quot;&gt;&lt;/a&gt;Data Throughput (MB&amp;#x2F;s)&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG16&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG18 Binary+DirectTLS&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG18 +COPY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;30.4&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;31.1&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;38.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide-20col&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;21.3&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;50.7&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;51.5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload-2KB&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;28.3&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;43.9&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;57.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed-DML&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;7.9&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;32.1&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;33.8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch-100&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;4.6&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;26.2&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;37.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Best data throughput: &lt;strong&gt;57.1 MB&amp;#x2F;s&lt;/strong&gt; (PG18 + COPY, Payload-2KB scenario).&lt;/p&gt;
&lt;h3 id=&quot;Stress-Scaling-16-to-192-Concurrent-Writers&quot;&gt;&lt;a href=&quot;#Stress-Scaling-16-to-192-Concurrent-Writers&quot; class=&quot;headerlink&quot; title=&quot;Stress Scaling: 16 to 192 Concurrent Writers&quot;&gt;&lt;/a&gt;Stress Scaling: 16 to 192 Concurrent Writers&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;right&quot;&gt;Writers&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG16&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG18 Binary+DirectTLS&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG18 +COPY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;right&quot;&gt;16&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;125,657&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;130,625&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;185,044&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;right&quot;&gt;32&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;111,970&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;133,880&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;184,718&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;right&quot;&gt;64&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;103,937&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;125,082&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;182,349&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;right&quot;&gt;128&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;87,352&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;109,594&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;160,293&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;right&quot;&gt;192&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;71,316&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;98,482&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;171,585&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Under high concurrency (16 to 192 writers), PG16 degrades by &lt;strong&gt;43%&lt;/strong&gt; while PG18 + COPY only degrades by &lt;strong&gt;~7%&lt;/strong&gt;, demonstrating significantly better scalability.&lt;/p&gt;
&lt;h3 id=&quot;CPU-Efficiency-events-sec-per-1-CPU&quot;&gt;&lt;a href=&quot;#CPU-Efficiency-events-sec-per-1-CPU&quot; class=&quot;headerlink&quot; title=&quot;CPU Efficiency (events&amp;#x2F;sec per 1% CPU)&quot;&gt;&lt;/a&gt;CPU Efficiency (events&amp;#x2F;sec per 1% CPU)&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG16&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG18 Binary+DirectTLS&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;PG18 +COPY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;5,689&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;5,637&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;5,920&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch-5000&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;5,379&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;5,733&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;5,440&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide-20col&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2,369&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;5,059&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;5,517&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch-100&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;3,966&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;5,572&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;5,693&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;PG18 variants deliver consistently higher CPU efficiency, averaging &lt;strong&gt;5,200+ events&amp;#x2F;sec per 1% CPU&lt;/strong&gt; compared to PG16’s &lt;strong&gt;~4,700&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Memory-Usage&quot;&gt;&lt;a href=&quot;#Memory-Usage&quot; class=&quot;headerlink&quot; title=&quot;Memory Usage&quot;&gt;&lt;/a&gt;Memory Usage&lt;/h3&gt;&lt;p&gt;All configurations remain extremely lightweight — between &lt;strong&gt;15–18 MB RSS&lt;/strong&gt; regardless of load. Memory stays flat even under 192 concurrent writers, demonstrating the zero-copy buffer design pays off.&lt;/p&gt;
&lt;h3 id=&quot;Key-Takeaways-from-Load-Tests&quot;&gt;&lt;a href=&quot;#Key-Takeaways-from-Load-Tests&quot; class=&quot;headerlink&quot; title=&quot;Key Takeaways from Load Tests&quot;&gt;&lt;/a&gt;Key Takeaways from Load Tests&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;PG18 + COPY + binary mode&lt;/strong&gt; is the clear winner, peaking at &lt;strong&gt;209K events&amp;#x2F;sec&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stress resilience&lt;/strong&gt; — PG18 + COPY maintains throughput under heavy concurrency where PG16 degrades sharply&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU efficient&lt;/strong&gt; — the rustls-tls backend uses ~3x less CPU than libpq in prior benchmarks (4,252 vs 1,628 events&amp;#x2F;sec per 1% CPU)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory stable&lt;/strong&gt; — sub-18 MB footprint under all tested conditions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Binary mode + direct TLS&lt;/strong&gt; provide significant improvements even without COPY optimization&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Performance-Design-Decisions&quot;&gt;&lt;a href=&quot;#Performance-Design-Decisions&quot; class=&quot;headerlink&quot; title=&quot;Performance Design Decisions&quot;&gt;&lt;/a&gt;Performance Design Decisions&lt;/h2&gt;&lt;p&gt;Several design choices contribute to pg-walstream’s performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SmallVec&lt;/strong&gt; for tuple data — up to 16 columns stored inline on the stack, avoiding heap allocation for common cases&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom OidHasher&lt;/strong&gt; — eliminates SipHash overhead for 32-bit OID integer keys&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arc&amp;lt;str&amp;gt;&lt;/strong&gt; for column&amp;#x2F;namespace names — shared immutable strings across events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CachePadded atomics&lt;/strong&gt; for LSN feedback — avoids false sharing in concurrent scenarios&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feedback throttling&lt;/strong&gt; — time checks only every 128 events via bitmask (&lt;code&gt;count &amp;amp; 0x7F == 0&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;TCP-Tuning-for-Production&quot;&gt;&lt;a href=&quot;#TCP-Tuning-for-Production&quot; class=&quot;headerlink&quot; title=&quot;TCP Tuning for Production&quot;&gt;&lt;/a&gt;TCP Tuning for Production&lt;/h2&gt;&lt;p&gt;For high-throughput deployments, the following Linux kernel parameters are recommended:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;net.core.rmem_max = 67108864&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;net.core.wmem_max = 67108864&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;net.ipv4.tcp_rmem = 4096 262144 67108864&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;net.ipv4.tcp_wmem = 4096 262144 67108864&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;net.ipv4.tcp_congestion_control = bbr&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;net.core.netdev_max_backlog = 5000&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Conclusion&quot;&gt;&lt;a href=&quot;#Conclusion&quot; class=&quot;headerlink&quot; title=&quot;Conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;&lt;p&gt;pg-walstream fills a gap in the Rust ecosystem for a production-grade PostgreSQL WAL streaming library. With protocol v1–v4 support, dual connection backends, zero-copy parsing, and throughput exceeding 200K events&amp;#x2F;sec, it provides a solid foundation for building CDC pipelines, event-driven systems, and real-time data synchronization.&lt;/p&gt;
&lt;p&gt;The load testing results demonstrate that pairing pg-walstream with PostgreSQL 18’s binary mode and COPY optimization delivers exceptional performance and scalability — maintaining high throughput even under 192 concurrent writers while keeping memory usage under 18 MB.&lt;/p&gt;
&lt;h2 id=&quot;References&quot;&gt;&lt;a href=&quot;#References&quot; class=&quot;headerlink&quot; title=&quot;References&quot;&gt;&lt;/a&gt;References&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg-walstream&quot;&gt;pg-walstream GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://crates.io/crates/pg_walstream&quot;&gt;pg-walstream on crates.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://docs.rs/pg-walstream&quot;&gt;API Documentation on docs.rs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg-walstream/blob/main/LOAD_TEST_COMPARISON.md&quot;&gt;Load Test Comparison Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.postgresql.org/docs/current/logical-replication.html&quot;&gt;PostgreSQL Logical Replication Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/pg-walstream-rust-postgresql-wal-streaming/&quot;&gt;https://isdaniel.github.io/pg-walstream-rust-postgresql-wal-streaming/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>pg2any: A Rust CDC Library for Streaming PostgreSQL Changes to Any Database</title>
    <link href="https://isdaniel.github.io/pg2any-rust-introduce/"/>
    <id>https://isdaniel.github.io/pg2any-rust-introduce/</id>
    <published>2025-09-09T13:10:43.000Z</published>
    <updated>2025-09-09T13:10:43.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>pg2any is a Rust CDC library that streams PostgreSQL changes via logical replication to MySQL, SQL Server, or SQLite with crash-safe persistence</summary>
    <content type="html">&lt;h2 id=&quot;What-is-pg2any&quot;&gt;&lt;a href=&quot;#What-is-pg2any&quot; class=&quot;headerlink&quot; title=&quot;What is pg2any?&quot;&gt;&lt;/a&gt;What is pg2any?&lt;/h2&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg2any&quot;&gt;pg2any&lt;/a&gt; is a Rust library (published as &lt;code&gt;pg2any_lib&lt;/code&gt; on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://crates.io/crates/pg2any_lib&quot;&gt;crates.io&lt;/a&gt;) that builds production-ready Change Data Capture (CDC) pipelines. It reads PostgreSQL’s Write-Ahead Log (WAL) through logical replication and replays changes — inserts, updates, deletes, and truncates — to destination databases in real time.&lt;/p&gt;
&lt;p&gt;Supported destinations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt; (via SQLx)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL Server&lt;/strong&gt; (via Tiberius)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLite&lt;/strong&gt; (via SQLx)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each destination is behind a Cargo feature flag (&lt;code&gt;mysql&lt;/code&gt;, &lt;code&gt;sqlserver&lt;/code&gt;, &lt;code&gt;sqlite&lt;/code&gt;), so you compile only the drivers you need.&lt;/p&gt;
&lt;p&gt;A ready-to-run example application lives at &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/rust_playground/tree/main/pg2any-example&quot;&gt;pg2any-example&lt;/a&gt;, and the underlying PostgreSQL streaming replication protocol is handled by the companion crate &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg-walstream&quot;&gt;pg_walstream&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Architecture&quot;&gt;&lt;a href=&quot;#Architecture&quot; class=&quot;headerlink&quot; title=&quot;Architecture&quot;&gt;&lt;/a&gt;Architecture&lt;/h2&gt;&lt;p&gt;pg2any follows a &lt;strong&gt;producer–consumer&lt;/strong&gt; pattern with &lt;strong&gt;file-based transaction persistence&lt;/strong&gt; as the intermediary. This design gives crash safety: if the process dies mid-stream, committed-but-unexecuted transactions survive on disk and are replayed on restart.&lt;/p&gt;
&lt;figure class=&quot;highlight nix&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;┌─────────────────┐        ┌──────────────────────────────────────┐       ┌──────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   PostgreSQL    │        │          pg2any CDC Engine            │       │   Destination    │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│                 │  WAL   │                                      │  SQL  │  MySQL &lt;span class=&quot;symbol&quot;&gt;/&lt;/span&gt; MSSQL   │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  Logical Repli- │───────▶│  ┌──────────┐     ┌───────────────┐ │──────▶│  &lt;span class=&quot;symbol&quot;&gt;/&lt;/span&gt; SQLite        │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  cation Stream  │        │  │ Producer  │────▶│ File Storage  │ │       │                  │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│                 │        │  └──────────┘     └───────┬───────┘ │       └──────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└─────────────────┘        │                          │         │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                           │                   ┌──────▼───────┐ │       ┌──────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                           │                   │   Consumer   │ │       │   Prometheus     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                           │                   │ (Priority Q) │ │       │   &lt;span class=&quot;symbol&quot;&gt;/metrics&lt;/span&gt;       │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                           │                   └──────────────┘ │       │   &lt;span class=&quot;symbol&quot;&gt;/health&lt;/span&gt;        │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                           └──────────────────────────────────────┘       └──────────────────┘&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Three-Directory-Transaction-Lifecycle&quot;&gt;&lt;a href=&quot;#Three-Directory-Transaction-Lifecycle&quot; class=&quot;headerlink&quot; title=&quot;Three-Directory Transaction Lifecycle&quot;&gt;&lt;/a&gt;Three-Directory Transaction Lifecycle&lt;/h3&gt;&lt;p&gt;Transactions flow through three directories during their lifetime:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directory&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sql_data_tx/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores actual SQL content. Files are append-only and rotate at 64 MB segments for large transactions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sql_received_tx/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Metadata for &lt;strong&gt;in-progress&lt;/strong&gt; transactions (created at &lt;code&gt;BEGIN&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sql_pending_tx/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Metadata for &lt;strong&gt;committed&lt;/strong&gt; transactions ready for the consumer (atomically moved from &lt;code&gt;sql_received_tx/&lt;/code&gt; on &lt;code&gt;COMMIT&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This three-phase approach means that only fully committed transactions ever reach the consumer, and incomplete transactions are cleaned up on restart.&lt;/p&gt;
&lt;h3 id=&quot;Producer&quot;&gt;&lt;a href=&quot;#Producer&quot; class=&quot;headerlink&quot; title=&quot;Producer&quot;&gt;&lt;/a&gt;Producer&lt;/h3&gt;&lt;p&gt;The producer reads the logical replication stream event by event:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;On &lt;strong&gt;BEGIN&lt;/strong&gt; — creates a metadata file in &lt;code&gt;sql_received_tx/&lt;/code&gt; and a data file in &lt;code&gt;sql_data_tx/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On &lt;strong&gt;INSERT &amp;#x2F; UPDATE &amp;#x2F; DELETE &amp;#x2F; TRUNCATE&lt;/strong&gt; — converts each event to destination-dialect SQL and appends it to the data file via a &lt;code&gt;BufferedEventWriter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On &lt;strong&gt;COMMIT&lt;/strong&gt; — atomically moves metadata from &lt;code&gt;sql_received_tx/&lt;/code&gt; to &lt;code&gt;sql_pending_tx/&lt;/code&gt;, making the transaction visible to the consumer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For protocol version 2+, the producer also handles &lt;strong&gt;streaming transactions&lt;/strong&gt; (&lt;code&gt;StreamStart&lt;/code&gt; &amp;#x2F; &lt;code&gt;StreamStop&lt;/code&gt; &amp;#x2F; &lt;code&gt;StreamCommit&lt;/code&gt;), which allow PostgreSQL to send chunks of large in-progress transactions before the final commit.&lt;/p&gt;
&lt;h3 id=&quot;Consumer&quot;&gt;&lt;a href=&quot;#Consumer&quot; class=&quot;headerlink&quot; title=&quot;Consumer&quot;&gt;&lt;/a&gt;Consumer&lt;/h3&gt;&lt;p&gt;The consumer maintains a &lt;strong&gt;priority queue ordered by commit LSN&lt;/strong&gt; to guarantee correct replay order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reads pending transaction metadata from &lt;code&gt;sql_pending_tx/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Parses SQL from &lt;code&gt;sql_data_tx/&lt;/code&gt; using a streaming SQL parser (constant memory regardless of transaction size).&lt;/li&gt;
&lt;li&gt;Executes statements atomically in a destination-side database transaction.&lt;/li&gt;
&lt;li&gt;Invokes a &lt;strong&gt;PreCommitHook&lt;/strong&gt; — a callback that runs inside the destination transaction before &lt;code&gt;COMMIT&lt;/code&gt;, used to atomically persist the LSN checkpoint alongside the data. This eliminates the window where data is committed but the checkpoint is not (or vice versa).&lt;/li&gt;
&lt;li&gt;Commits, then deletes processed files.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Crash-Recovery&quot;&gt;&lt;a href=&quot;#Crash-Recovery&quot; class=&quot;headerlink&quot; title=&quot;Crash Recovery&quot;&gt;&lt;/a&gt;Crash Recovery&lt;/h3&gt;&lt;p&gt;On startup, pg2any scans:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sql_received_tx/&lt;/code&gt; for incomplete transactions → &lt;strong&gt;aborts&lt;/strong&gt; them.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sql_pending_tx/&lt;/code&gt; for committed-but-unexecuted transactions → &lt;strong&gt;replays&lt;/strong&gt; them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;LsnTracker&lt;/code&gt; persists the last successfully applied LSN, so replication resumes exactly where it left off.&lt;/p&gt;
&lt;h2 id=&quot;Key-Features&quot;&gt;&lt;a href=&quot;#Key-Features&quot; class=&quot;headerlink&quot; title=&quot;Key Features&quot;&gt;&lt;/a&gt;Key Features&lt;/h2&gt;&lt;h3 id=&quot;DML-Coalescing&quot;&gt;&lt;a href=&quot;#DML-Coalescing&quot; class=&quot;headerlink&quot; title=&quot;DML Coalescing&quot;&gt;&lt;/a&gt;DML Coalescing&lt;/h3&gt;&lt;p&gt;One of pg2any’s most impactful optimizations. Instead of executing individual DML statements one by one, the coalescing engine merges consecutive same-table operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple &lt;code&gt;INSERT&lt;/code&gt;s → multi-value &lt;code&gt;INSERT INTO ... VALUES (...), (...), (...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Multiple &lt;code&gt;UPDATE&lt;/code&gt;s → &lt;code&gt;CASE&lt;/code&gt;-&lt;code&gt;WHEN&lt;/code&gt; batch updates.&lt;/li&gt;
&lt;li&gt;Multiple &lt;code&gt;DELETE&lt;/code&gt;s → combined &lt;code&gt;WHERE&lt;/code&gt; clauses with &lt;code&gt;OR&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is applied across all three destination types, with dialect-aware identifier quoting (backticks for MySQL, brackets for SQL Server, double quotes for SQLite) and respects &lt;code&gt;max_allowed_packet&lt;/code&gt; limits (MySQL) with an 80% safety margin.&lt;/p&gt;
&lt;h3 id=&quot;Compressed-Storage&quot;&gt;&lt;a href=&quot;#Compressed-Storage&quot; class=&quot;headerlink&quot; title=&quot;Compressed Storage&quot;&gt;&lt;/a&gt;Compressed Storage&lt;/h3&gt;&lt;p&gt;When enabled via &lt;code&gt;PG2ANY_ENABLE_COMPRESSION=true&lt;/code&gt;, transaction files are stored as &lt;code&gt;.sql.gz&lt;/code&gt; with accompanying &lt;code&gt;.sql.gz.idx&lt;/code&gt; index files. Sync points are created every 1,000 statements, enabling O(1) seeking to arbitrary positions without decompressing the entire file — critical for efficient crash recovery of large transactions.&lt;/p&gt;
&lt;h3 id=&quot;Monitoring&quot;&gt;&lt;a href=&quot;#Monitoring&quot; class=&quot;headerlink&quot; title=&quot;Monitoring&quot;&gt;&lt;/a&gt;Monitoring&lt;/h3&gt;&lt;p&gt;With the &lt;code&gt;metrics&lt;/code&gt; feature enabled, pg2any exposes a Prometheus-compatible HTTP server (default port 8080):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /metrics&lt;/code&gt; — Prometheus text format with event counters, LSN progress, processing rates, error counts, and transaction statistics.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /health&lt;/code&gt; — JSON health status.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Metrics use &lt;code&gt;AtomicU64&lt;/code&gt; counters (lock-free) to minimize overhead on the hot path. When compiled without the &lt;code&gt;metrics&lt;/code&gt; feature, all metric calls become zero-cost no-ops.&lt;/p&gt;
&lt;h3 id=&quot;Protocol-Version-Support&quot;&gt;&lt;a href=&quot;#Protocol-Version-Support&quot; class=&quot;headerlink&quot; title=&quot;Protocol Version Support&quot;&gt;&lt;/a&gt;Protocol Version Support&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Capabilities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;td&gt;Basic logical replication (BEGIN, INSERT, UPDATE, DELETE, TRUNCATE, COMMIT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;td&gt;Adds streaming transactions for large in-progress transactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Adds two-phase commit support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v4&lt;/td&gt;
&lt;td&gt;Additional protocol capabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;Quick-Start&quot;&gt;&lt;a href=&quot;#Quick-Start&quot; class=&quot;headerlink&quot; title=&quot;Quick Start&quot;&gt;&lt;/a&gt;Quick Start&lt;/h2&gt;&lt;h3 id=&quot;Prerequisites&quot;&gt;&lt;a href=&quot;#Prerequisites&quot; class=&quot;headerlink&quot; title=&quot;Prerequisites&quot;&gt;&lt;/a&gt;Prerequisites&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;PostgreSQL 10+ with &lt;code&gt;wal_level = logical&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A destination database (MySQL 8.0+, SQL Server, or SQLite)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;PostgreSQL-Setup&quot;&gt;&lt;a href=&quot;#PostgreSQL-Setup&quot; class=&quot;headerlink&quot; title=&quot;PostgreSQL Setup&quot;&gt;&lt;/a&gt;PostgreSQL Setup&lt;/h3&gt;&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Verify logical replication is enabled&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SHOW&lt;/span&gt; wal_level;           &lt;span class=&quot;comment&quot;&gt;-- must be &amp;#x27;logical&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SHOW&lt;/span&gt; max_replication_slots;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SHOW&lt;/span&gt; max_wal_senders;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Create a publication for the tables you want to replicate&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; PUBLICATION cdc_pub &lt;span class=&quot;keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ALL&lt;/span&gt; TABLES;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Or for specific tables:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- CREATE PUBLICATION cdc_pub FOR TABLE orders, customers;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Using-pg2any-as-a-Library&quot;&gt;&lt;a href=&quot;#Using-pg2any-as-a-Library&quot; class=&quot;headerlink&quot; title=&quot;Using pg2any as a Library&quot;&gt;&lt;/a&gt;Using pg2any as a Library&lt;/h3&gt;&lt;p&gt;Add &lt;code&gt;pg2any_lib&lt;/code&gt; to your &lt;code&gt;Cargo.toml&lt;/code&gt; with the destination features you need:&lt;/p&gt;
&lt;figure class=&quot;highlight toml&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;section&quot;&gt;[dependencies]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attr&quot;&gt;pg2any_lib&lt;/span&gt; = &amp;#123; version = &lt;span class=&quot;string&quot;&gt;&amp;quot;0.9&amp;quot;&lt;/span&gt;, features = [&lt;span class=&quot;string&quot;&gt;&amp;quot;mysql&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;metrics&amp;quot;&lt;/span&gt;] &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attr&quot;&gt;tokio&lt;/span&gt; = &amp;#123; version = &lt;span class=&quot;string&quot;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;, features = [&lt;span class=&quot;string&quot;&gt;&amp;quot;full&amp;quot;&lt;/span&gt;] &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Configuration-via-Environment-Variables&quot;&gt;&lt;a href=&quot;#Configuration-via-Environment-Variables&quot; class=&quot;headerlink&quot; title=&quot;Configuration via Environment Variables&quot;&gt;&lt;/a&gt;Configuration via Environment Variables&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_SOURCE_CONNECTION_STRING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL URI with &lt;code&gt;?replication=database&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_DEST_TYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MySQL&lt;/code&gt;, &lt;code&gt;SqlServer&lt;/code&gt;, or &lt;code&gt;SQLite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_DEST_URI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Destination connection string&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_REPLICATION_SLOT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replication slot name&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_PUBLICATION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publication name&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_SCHEMA_MAPPING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Comma-separated &lt;code&gt;source:dest&lt;/code&gt; pairs (e.g., &lt;code&gt;public:cdc_db&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_PROTOCOL_VERSION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Protocol version (1–4)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_STREAMING_MODE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enable streaming transactions (requires v2+)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_BINARY_MODE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Binary format for protocol&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_CONNECTION_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Connection timeout (seconds)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;30&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_QUERY_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Query timeout (seconds)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;60&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_BUFFER_SIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transaction channel queue capacity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDC_BATCH_SIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Batch size for destination inserts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PG2ANY_ENABLE_COMPRESSION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enable gzip compression for SQL files&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PG2ANY_METRICS_PORT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prometheus HTTP port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8080&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;Run-with-Docker-Compose&quot;&gt;&lt;a href=&quot;#Run-with-Docker-Compose&quot; class=&quot;headerlink&quot; title=&quot;Run with Docker Compose&quot;&gt;&lt;/a&gt;Run with Docker Compose&lt;/h3&gt;&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/rust_playground/tree/main/pg2any-example&quot;&gt;example project&lt;/a&gt; includes a full Docker Compose stack with PostgreSQL, MySQL, the CDC application, and Prometheus:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;git &lt;span class=&quot;built_in&quot;&gt;clone&lt;/span&gt; https://github.com/isdaniel/rust_playground.git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;cd&lt;/span&gt; rust_playground/pg2any-example&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Start all services&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;docker-compose up -d&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Watch CDC logs&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;docker-compose logs -f cdc_app&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Design-Decisions-Worth-Noting&quot;&gt;&lt;a href=&quot;#Design-Decisions-Worth-Noting&quot; class=&quot;headerlink&quot; title=&quot;Design Decisions Worth Noting&quot;&gt;&lt;/a&gt;Design Decisions Worth Noting&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;File-based persistence over in-memory queues&lt;/strong&gt; — Using the filesystem as the intermediary between producer and consumer trades some latency for crash safety. If the process is killed, no committed transaction data is lost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PreCommitHook for atomic checkpoints&lt;/strong&gt; — Executing the LSN checkpoint update inside the same destination transaction as the data changes eliminates an entire class of consistency bugs where the checkpoint and data can diverge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Feature-gated compilation&lt;/strong&gt; — Database drivers and monitoring are behind Cargo features, so the binary only includes what you actually use. This reduces compile time, binary size, and attack surface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transaction segmentation at 64 MB&lt;/strong&gt; — Large transactions (e.g., bulk imports) are split across multiple files to prevent unbounded memory and disk usage.&lt;/p&gt;
&lt;h2 id=&quot;Testing&quot;&gt;&lt;a href=&quot;#Testing&quot; class=&quot;headerlink&quot; title=&quot;Testing&quot;&gt;&lt;/a&gt;Testing&lt;/h2&gt;&lt;p&gt;pg2any has 104+ tests across 16 test files, covering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Integration tests for all three destination types&lt;/li&gt;
&lt;li&gt;Streaming transaction correctness&lt;/li&gt;
&lt;li&gt;Compression and large file handling&lt;/li&gt;
&lt;li&gt;WHERE clause generation for UPDATE&amp;#x2F;DELETE with various replica identity configurations&lt;/li&gt;
&lt;li&gt;Position tracking for crash recovery&lt;/li&gt;
&lt;li&gt;Metrics logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Beyond unit and integration tests, the project runs &lt;strong&gt;chaos testing&lt;/strong&gt; in CI — randomly restarting the CDC application during pgbench workloads to validate graceful shutdown and recovery under real conditions.&lt;/p&gt;
&lt;h2 id=&quot;Links&quot;&gt;&lt;a href=&quot;#Links&quot; class=&quot;headerlink&quot; title=&quot;Links&quot;&gt;&lt;/a&gt;Links&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg2any&quot;&gt;pg2any source code (GitHub)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://crates.io/crates/pg2any_lib&quot;&gt;pg2any_lib on crates.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/rust_playground/tree/main/pg2any-example&quot;&gt;Example application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg-walstream&quot;&gt;pg_walstream — PostgreSQL replication protocol crate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/pg2any-rust-introduce/&quot;&gt;https://isdaniel.github.io/pg2any-rust-introduce/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a PostgreSQL Foreign Data Wrapper (FDW) in Rust with pgrx</title>
    <link href="https://isdaniel.github.io/rust-pgrx-extension-fdw/"/>
    <id>https://isdaniel.github.io/rust-pgrx-extension-fdw/</id>
    <published>2025-06-30T14:30:11.000Z</published>
    <updated>2025-06-30T14:30:11.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>Learn how to build a PostgreSQL Foreign Data Wrapper (FDW) in Rust using pgrx — query external data sources as regular PostgreSQL tables</summary>
    <content type="html">&lt;h1 id=&quot;🚀-Building-a-Simple-PostgreSQL-FDW-with-Rust-and-pgrx&quot;&gt;&lt;a href=&quot;#🚀-Building-a-Simple-PostgreSQL-FDW-with-Rust-and-pgrx&quot; class=&quot;headerlink&quot; title=&quot;🚀 Building a Simple PostgreSQL FDW with Rust and pgrx&quot;&gt;&lt;/a&gt;🚀 Building a Simple PostgreSQL FDW with Rust and pgrx&lt;/h1&gt;&lt;p&gt;PostgreSQL Foreign Data Wrappers (FDW) enable PostgreSQL to query external data sources as if they were regular tables. Traditionally, FDWs are written in C, but with &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/pgcentralfoundation/pgrx&quot;&gt;&lt;code&gt;pgrx&lt;/code&gt;&lt;/a&gt;, we can now build PostgreSQL extensions — including FDWs — in &lt;strong&gt;Rust&lt;/strong&gt;, unlocking safety and modern tooling.&lt;/p&gt;
&lt;p&gt;In this post, we’ll walk through creating a simple FDW using Rust and &lt;code&gt;pgrx&lt;/code&gt; that simulates reading rows from an external source (e.g., Redis or API). It is deliberately minimal so the core FDW lifecycle stays visible end to end.&lt;/p&gt;
&lt;h2 id=&quot;🛠️-What-Can-You-Build-with-pgrx&quot;&gt;&lt;a href=&quot;#🛠️-What-Can-You-Build-with-pgrx&quot; class=&quot;headerlink&quot; title=&quot;🛠️ What Can You Build with pgrx?&quot;&gt;&lt;/a&gt;🛠️ What Can You Build with &lt;code&gt;pgrx&lt;/code&gt;?&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;SQL Functions:&lt;/strong&gt; Scalar, aggregate, and set-returning functions.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Custom Types:&lt;/strong&gt; Define composite types or enums in Rust.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Foreign Data Wrappers (FDWs):&lt;/strong&gt; Like the one in your example — connect PostgreSQL to external systems (Redis, APIs, file systems, etc.).&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Index Access Methods:&lt;/strong&gt; Implement new index types.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Background Workers:&lt;/strong&gt; Run tasks in the background inside PostgreSQL.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Hooks:&lt;/strong&gt; Intercept or modify PostgreSQL internal behavior (like planner or executor hooks).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;🌐-Under-the-hood&quot;&gt;&lt;a href=&quot;#🌐-Under-the-hood&quot; class=&quot;headerlink&quot; title=&quot;🌐 Under the hood:&quot;&gt;&lt;/a&gt;🌐 Under the hood:&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;PostgreSQL communicates via C APIs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pgrx&lt;/code&gt; provides Rust-safe bindings to these APIs.&lt;/li&gt;
&lt;li&gt;Memory management is handled carefully via &lt;code&gt;PgMemoryContexts&lt;/code&gt;, matching PostgreSQL’s memory context model.&lt;/li&gt;
&lt;li&gt;Rust functions are exposed to PostgreSQL as SQL-callable functions with the &lt;code&gt;#[pg_extern]&lt;/code&gt; macro.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;🏗️-Key-Components-of-an-default-fdw&quot;&gt;&lt;a href=&quot;#🏗️-Key-Components-of-an-default-fdw&quot; class=&quot;headerlink&quot; title=&quot;🏗️ Key Components of an default_fdw&quot;&gt;&lt;/a&gt;🏗️ Key Components of an default_fdw&lt;/h2&gt;&lt;p&gt;PostgreSQL FDWs consist of several callback functions that handle different phases of query planning and execution:&lt;/p&gt;
&lt;p&gt;Example extension &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/rust_pg_extensions/blob/main/src/default_fdw.rs&quot;&gt;default_fdw&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Planning Phase:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GetForeignRelSize&lt;/code&gt;: Estimate rows.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetForeignPaths&lt;/code&gt;: Generate access paths.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetForeignPlan&lt;/code&gt;: Create the scan plan.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Execution Phase:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BeginForeignScan&lt;/code&gt;: Initialize the scan.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IterateForeignScan&lt;/code&gt;: Produce each row.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReScanForeignScan&lt;/code&gt;: Restart the scan if needed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EndForeignScan&lt;/code&gt;: Cleanup.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;How-to-use&quot;&gt;&lt;a href=&quot;#How-to-use&quot; class=&quot;headerlink&quot; title=&quot;How to use&quot;&gt;&lt;/a&gt;How to use&lt;/h3&gt;&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;foreign&lt;/span&gt; data wrapper default_wrapper&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  handler default_fdw_handler;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;create&lt;/span&gt; server my_default_server&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;foreign&lt;/span&gt; data wrapper default_wrapper&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  options (&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    foo &lt;span class=&quot;string&quot;&gt;&amp;#x27;bar&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  );&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;foreign&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;table&lt;/span&gt; hello (&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  id &lt;span class=&quot;type&quot;&gt;bigint&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  col text&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;server my_default_server options (&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	foo &lt;span class=&quot;string&quot;&gt;&amp;#x27;bar&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Then we can select hello table.&lt;/p&gt;
&lt;figure class=&quot;highlight axapta&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;select&lt;/span&gt; * &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; hello;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;🔧-Setting-Up-the-FDW-Handler&quot;&gt;&lt;a href=&quot;#🔧-Setting-Up-the-FDW-Handler&quot; class=&quot;headerlink&quot; title=&quot;🔧 Setting Up the FDW Handler&quot;&gt;&lt;/a&gt;🔧 Setting Up the FDW Handler&lt;/h2&gt;&lt;p&gt;The entry point is the FDW handler function, which PostgreSQL calls to retrieve a set of function pointers.&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[pg_extern(create_or_replace)]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;default_fdw_handler&lt;/span&gt;() &lt;span class=&quot;punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; PgBox&amp;lt;pg_sys::FdwRoutine&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; default_fdw_handler&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;mut &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;fdw_routine&lt;/span&gt; = PgBox::&amp;lt;pg_sys::FdwRoutine, AllocatedByRust&amp;gt;::&lt;span class=&quot;title function_ invoke__&quot;&gt;alloc_node&lt;/span&gt;(pg_sys::NodeTag::T_FdwRoutine);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// Planning callbacks&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.GetForeignRelSize = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(get_foreign_rel_size);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.GetForeignPaths = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(get_foreign_paths);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.GetForeignPlan = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(get_foreign_plan);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.ExplainForeignScan = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(explain_foreign_scan);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// Execution callbacks&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.BeginForeignScan = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(begin_foreign_scan);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.IterateForeignScan = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(iterate_foreign_scan);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.ReScanForeignScan = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(re_scan_foreign_scan);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.EndForeignScan = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(end_foreign_scan);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        fdw_routine.&lt;span class=&quot;title function_ invoke__&quot;&gt;into_pg_boxed&lt;/span&gt;()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;



&lt;h2 id=&quot;📦-Extracting-Foreign-Table-Options&quot;&gt;&lt;a href=&quot;#📦-Extracting-Foreign-Table-Options&quot; class=&quot;headerlink&quot; title=&quot;📦 Extracting Foreign Table Options&quot;&gt;&lt;/a&gt;📦 Extracting Foreign Table Options&lt;/h2&gt;&lt;p&gt;PostgreSQL allows specifying options like hostnames or credentials when creating a foreign table. This function retrieves those options:&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;get_foreign_table_options&lt;/span&gt;(relid: pg_sys::Oid) &lt;span class=&quot;punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; HashMap&amp;lt;&lt;span class=&quot;type&quot;&gt;String&lt;/span&gt;, &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt;&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;This is crucial when your FDW needs to connect to external systems like Redis, REST APIs, or filesystems.&lt;/p&gt;
&lt;h2 id=&quot;📊-Planner-Callbacks&quot;&gt;&lt;a href=&quot;#📊-Planner-Callbacks&quot; class=&quot;headerlink&quot; title=&quot;📊 Planner Callbacks&quot;&gt;&lt;/a&gt;📊 Planner Callbacks&lt;/h2&gt;&lt;h3 id=&quot;1️⃣-GetForeignRelSize&quot;&gt;&lt;a href=&quot;#1️⃣-GetForeignRelSize&quot; class=&quot;headerlink&quot; title=&quot;1️⃣ GetForeignRelSize&quot;&gt;&lt;/a&gt;1️⃣ &lt;strong&gt;GetForeignRelSize&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Estimates the number of rows in the foreign table.&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[pg_guard]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;get_foreign_rel_size&lt;/span&gt;(..., baserel: *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::RelOptInfo, ...) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; get_foreign_rel_size&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        (*baserel).rows = &lt;span class=&quot;number&quot;&gt;1000.0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;2️⃣-GetForeignPaths&quot;&gt;&lt;a href=&quot;#2️⃣-GetForeignPaths&quot; class=&quot;headerlink&quot; title=&quot;2️⃣ GetForeignPaths&quot;&gt;&lt;/a&gt;2️⃣ &lt;strong&gt;GetForeignPaths&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Defines possible access paths for the planner.&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[pg_guard]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;get_foreign_paths&lt;/span&gt;(..., baserel: *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::RelOptInfo, ...) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; get_foreign_paths&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;path&lt;/span&gt; = pg_sys::&lt;span class=&quot;title function_ invoke__&quot;&gt;create_foreignscan_path&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            ...,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            (*baserel).rows,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;number&quot;&gt;10.0&lt;/span&gt;,   &lt;span class=&quot;comment&quot;&gt;// startup cost&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;number&quot;&gt;100.0&lt;/span&gt;,  &lt;span class=&quot;comment&quot;&gt;// total cost&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        );&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        pg_sys::&lt;span class=&quot;title function_ invoke__&quot;&gt;add_path&lt;/span&gt;(baserel, path &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::Path);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;3️⃣-GetForeignPlan&quot;&gt;&lt;a href=&quot;#3️⃣-GetForeignPlan&quot; class=&quot;headerlink&quot; title=&quot;3️⃣ GetForeignPlan&quot;&gt;&lt;/a&gt;3️⃣ &lt;strong&gt;GetForeignPlan&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Generates the actual execution plan.&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[pg_guard]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;get_foreign_plan&lt;/span&gt;(...) &lt;span class=&quot;punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::ForeignScan &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; get_foreign_plan&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        pg_sys::&lt;span class=&quot;title function_ invoke__&quot;&gt;make_foreignscan&lt;/span&gt;(...)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;



&lt;h2 id=&quot;▶️-Execution-Callbacks&quot;&gt;&lt;a href=&quot;#▶️-Execution-Callbacks&quot; class=&quot;headerlink&quot; title=&quot;▶️ Execution Callbacks&quot;&gt;&lt;/a&gt;▶️ Execution Callbacks&lt;/h2&gt;&lt;h3 id=&quot;🏁-BeginForeignScan&quot;&gt;&lt;a href=&quot;#🏁-BeginForeignScan&quot; class=&quot;headerlink&quot; title=&quot;🏁 BeginForeignScan&quot;&gt;&lt;/a&gt;🏁 &lt;strong&gt;BeginForeignScan&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Initializes the scan.&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[pg_guard]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;begin_foreign_scan&lt;/span&gt;(node: *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::ForeignScanState, ...) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; begin_foreign_scan&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;relid&lt;/span&gt; = (*(*node).ss.ss_currentRelation).rd_id;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;options&lt;/span&gt; = &lt;span class=&quot;title function_ invoke__&quot;&gt;get_foreign_table_options&lt;/span&gt;(relid);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;Foreign table options: &amp;#123;:?&amp;#125;&amp;quot;&lt;/span&gt;, options);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;state&lt;/span&gt; = PgMemoryContexts::CurrentMemoryContext&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            .&lt;span class=&quot;title function_ invoke__&quot;&gt;leak_and_drop_on_delete&lt;/span&gt;(RedisFdwState &amp;#123; row: &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt; &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        (*node).fdw_state = state &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; std::ffi::c_void;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;🔁-IterateForeignScan&quot;&gt;&lt;a href=&quot;#🔁-IterateForeignScan&quot; class=&quot;headerlink&quot; title=&quot;🔁 IterateForeignScan&quot;&gt;&lt;/a&gt;🔁 &lt;strong&gt;IterateForeignScan&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Produces rows one at a time.&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[pg_guard]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;iterate_foreign_scan&lt;/span&gt;(node: *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::ForeignScanState) &lt;span class=&quot;punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::TupleTableSlot &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; iterate_foreign_scan&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;state&lt;/span&gt; = &amp;amp;&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; *((*node).fdw_state &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; RedisFdwState);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;slot&lt;/span&gt; = (*node).ss.ss_ScanTupleSlot;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;tupdesc&lt;/span&gt; = (*slot).tts_tupleDescriptor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;natts&lt;/span&gt; = (*tupdesc).natts &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;usize&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; state.row &amp;gt;= &lt;span class=&quot;number&quot;&gt;5&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;title function_ invoke__&quot;&gt;exec_clear_tuple&lt;/span&gt;(slot);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; slot;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;title function_ invoke__&quot;&gt;exec_clear_tuple&lt;/span&gt;(slot);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;values_ptr&lt;/span&gt; = PgMemoryContexts::&lt;span class=&quot;title function_ invoke__&quot;&gt;For&lt;/span&gt;((*slot).tts_mcxt)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            .&lt;span class=&quot;title function_ invoke__&quot;&gt;palloc&lt;/span&gt;(std::mem::size_of::&amp;lt;pg_sys::Datum&amp;gt;() * natts) &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::Datum;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;nulls_ptr&lt;/span&gt; = PgMemoryContexts::&lt;span class=&quot;title function_ invoke__&quot;&gt;For&lt;/span&gt;((*slot).tts_mcxt)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            .&lt;span class=&quot;title function_ invoke__&quot;&gt;palloc&lt;/span&gt;(std::mem::size_of::&amp;lt;&lt;span class=&quot;type&quot;&gt;bool&lt;/span&gt;&amp;gt;() * natts) &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;bool&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        *values_ptr.&lt;span class=&quot;title function_ invoke__&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;) = (state.row + &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;).&lt;span class=&quot;title function_ invoke__&quot;&gt;into&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;name&lt;/span&gt; = &lt;span class=&quot;built_in&quot;&gt;format!&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;hello_&amp;#123;&amp;#125;&amp;quot;&lt;/span&gt;, state.row + &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;cstring&lt;/span&gt; = CString::&lt;span class=&quot;title function_ invoke__&quot;&gt;new&lt;/span&gt;(name).&lt;span class=&quot;title function_ invoke__&quot;&gt;unwrap&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        *values_ptr.&lt;span class=&quot;title function_ invoke__&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;) = Datum::&lt;span class=&quot;title function_ invoke__&quot;&gt;from&lt;/span&gt;(pg_sys::&lt;span class=&quot;title function_ invoke__&quot;&gt;cstring_to_text&lt;/span&gt;(cstring.&lt;span class=&quot;title function_ invoke__&quot;&gt;as_ptr&lt;/span&gt;()));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        *nulls_ptr.&lt;span class=&quot;title function_ invoke__&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;) = &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        *nulls_ptr.&lt;span class=&quot;title function_ invoke__&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;) = &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        (*slot).tts_values = values_ptr;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        (*slot).tts_isnull = nulls_ptr;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        pg_sys::&lt;span class=&quot;title function_ invoke__&quot;&gt;ExecStoreVirtualTuple&lt;/span&gt;(slot);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        state.row += &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        slot&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;This example emits five rows with &lt;code&gt;(id, name)&lt;/code&gt; pairs like &lt;code&gt;(1, hello_1)&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;🔄-ReScanForeignScan&quot;&gt;&lt;a href=&quot;#🔄-ReScanForeignScan&quot; class=&quot;headerlink&quot; title=&quot;🔄 ReScanForeignScan&quot;&gt;&lt;/a&gt;🔄 &lt;strong&gt;ReScanForeignScan&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Handles rescan requests.&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[pg_guard]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;re_scan_foreign_scan&lt;/span&gt;(_node: *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::ForeignScanState) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; re_scan_foreign_scan&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;🛑-EndForeignScan&quot;&gt;&lt;a href=&quot;#🛑-EndForeignScan&quot; class=&quot;headerlink&quot; title=&quot;🛑 EndForeignScan&quot;&gt;&lt;/a&gt;🛑 &lt;strong&gt;EndForeignScan&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Frees resources.&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#[pg_guard]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;end_foreign_scan&lt;/span&gt;(node: *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::ForeignScanState) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    log!(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; end_foreign_scan&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; !(*node).fdw_state.&lt;span class=&quot;title function_ invoke__&quot;&gt;is_null&lt;/span&gt;() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            (*node).fdw_state = std::ptr::&lt;span class=&quot;title function_ invoke__&quot;&gt;null_mut&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;


&lt;h2 id=&quot;🗒️-Utilities&quot;&gt;&lt;a href=&quot;#🗒️-Utilities&quot; class=&quot;headerlink&quot; title=&quot;🗒️ Utilities&quot;&gt;&lt;/a&gt;🗒️ Utilities&lt;/h2&gt;&lt;p&gt;Tuple clearing is handled by this helper:&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;exec_clear_tuple&lt;/span&gt;(slot: *&lt;span class=&quot;keyword&quot;&gt;mut&lt;/span&gt; pg_sys::TupleTableSlot) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;Some&lt;/span&gt;(clear) = (*(*slot).tts_ops).clear &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;title function_ invoke__&quot;&gt;clear&lt;/span&gt;(slot);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;


&lt;h2 id=&quot;🏁-Conclusion&quot;&gt;&lt;a href=&quot;#🏁-Conclusion&quot; class=&quot;headerlink&quot; title=&quot;🏁 Conclusion&quot;&gt;&lt;/a&gt;🏁 Conclusion&lt;/h2&gt;&lt;p&gt;This post walked you through the basics of building a PostgreSQL FDW using Rust and &lt;code&gt;pgrx&lt;/code&gt;. While this example generates dummy data, the same structure can be extended to connect with real-world systems like Redis, REST APIs, or message queues.&lt;/p&gt;
&lt;h2 id=&quot;🚀-Next-Steps&quot;&gt;&lt;a href=&quot;#🚀-Next-Steps&quot; class=&quot;headerlink&quot; title=&quot;🚀 Next Steps&quot;&gt;&lt;/a&gt;🚀 Next Steps&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Add connection logic to Redis or any backend.&lt;/li&gt;
&lt;li&gt;Support &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; by implementing the modification callbacks.&lt;/li&gt;
&lt;li&gt;Package and distribute as a PostgreSQL extension.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;📚-References&quot;&gt;&lt;a href=&quot;#📚-References&quot; class=&quot;headerlink&quot; title=&quot;📚 References&quot;&gt;&lt;/a&gt;📚 References&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/pgcentralfoundation/pgrx&quot;&gt;pgrx GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.postgresql.org/docs/current/fdw-callbacks.html&quot;&gt;PostgreSQL FDW API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’d like, I can help refine this post further, format it for Medium&amp;#x2F;Dev.to, or extend it with Redis connection examples. Would you like that?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/rust-pgrx-extension-fdw/&quot;&gt;https://isdaniel.github.io/rust-pgrx-extension-fdw/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>AI-Powered PostgreSQL Performance Tuning with MCP - Introducing pgtuner_mcp</title>
    <link href="https://isdaniel.github.io/pgtuner-mcp-ai-powered-postgresql-performance/"/>
    <id>https://isdaniel.github.io/pgtuner-mcp-ai-powered-postgresql-performance/</id>
    <published>2025-01-13T02:30:00.000Z</published>
    <updated>2025-01-13T02:30:00.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>pgtuner_mcp is an MCP server for AI-driven PostgreSQL performance analysis, tuning advice, and health checks from your development workflow</summary>
    <content type="html">&lt;h1 id=&quot;AI-Powered-PostgreSQL-Performance-Tuning-with-MCP-Introducing-pgtuner-mcp&quot;&gt;&lt;a href=&quot;#AI-Powered-PostgreSQL-Performance-Tuning-with-MCP-Introducing-pgtuner-mcp&quot; class=&quot;headerlink&quot; title=&quot;AI-Powered PostgreSQL Performance Tuning with MCP - Introducing pgtuner_mcp&quot;&gt;&lt;/a&gt;AI-Powered PostgreSQL Performance Tuning with MCP - Introducing pgtuner_mcp&lt;/h1&gt;&lt;p&gt;Database performance optimization is one of the most critical yet challenging aspects of maintaining production systems. Identifying slow queries, optimizing indexes, and monitoring database health requires deep expertise and constant vigilance. Today, I’m excited to introduce &lt;strong&gt;pgtuner_mcp&lt;/strong&gt;, a Model Context Protocol (MCP) server that brings AI-powered PostgreSQL performance tuning capabilities directly into your development workflow.&lt;/p&gt;
&lt;h2 id=&quot;What-is-pgtuner-mcp&quot;&gt;&lt;a href=&quot;#What-is-pgtuner-mcp&quot; class=&quot;headerlink&quot; title=&quot;What is pgtuner_mcp?&quot;&gt;&lt;/a&gt;What is pgtuner_mcp?&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;pgtuner_mcp&lt;/strong&gt; is an intelligent PostgreSQL performance analysis server built on the Model Context Protocol (MCP). It bridges the gap between AI assistants (like Claude) and your PostgreSQL database, enabling natural language interactions for complex database optimization tasks.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pgtuner_mcp&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;Key-Capabilities&quot;&gt;&lt;a href=&quot;#Key-Capabilities&quot; class=&quot;headerlink&quot; title=&quot;Key Capabilities&quot;&gt;&lt;/a&gt;Key Capabilities&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Intelligent Query Analysis&lt;/strong&gt;: Identify slow queries with detailed statistics from &lt;code&gt;pg_stat_statements&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI-Powered Index Recommendations&lt;/strong&gt;: Get smart indexing suggestions based on actual workload patterns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hypothetical Index Testing&lt;/strong&gt;: Test indexes without creating them using HypoPG&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comprehensive Health Checks&lt;/strong&gt;: Monitor connections, cache efficiency, locks, and replication&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bloat Detection&lt;/strong&gt;: Identify and quantify table&amp;#x2F;index bloat for maintenance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vacuum Monitoring&lt;/strong&gt;: Track vacuum operations and autovacuum effectiveness&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I&amp;#x2F;O Analysis&lt;/strong&gt;: Analyze disk read&amp;#x2F;write patterns and identify bottlenecks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration Review&lt;/strong&gt;: Get recommendations for memory, checkpoint, and connection settings&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Architecture-Overview&quot;&gt;&lt;a href=&quot;#Architecture-Overview&quot; class=&quot;headerlink&quot; title=&quot;Architecture Overview&quot;&gt;&lt;/a&gt;Architecture Overview&lt;/h2&gt;&lt;p&gt;pgtuner_mcp leverages several PostgreSQL extensions and Python libraries to provide comprehensive analysi&lt;/p&gt;
&lt;figure class=&quot;highlight pgsql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;┌─────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  AI Assistant   │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  (Claude, etc)  │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└────────┬────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;         │ MCP Protocol&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;         ▼&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌─────────────────┐    ┌──────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  pgtuner_mcp    │───▶│   PostgreSQL     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  (Python MCP    │    │   + Extensions   │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   &lt;span class=&quot;keyword&quot;&gt;Server&lt;/span&gt;)       │    │  - pg_stat_      │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└─────────────────┘    │    statements    │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;         │              │  - hypopg        │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;         │              │  - pgstattuple   │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;         ▼              └──────────────────┘&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;┌─────────────────┐&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  Analysis &amp;amp;     │&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│  Recommendations│&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└─────────────────┘&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Core-Components&quot;&gt;&lt;a href=&quot;#Core-Components&quot; class=&quot;headerlink&quot; title=&quot;Core Components&quot;&gt;&lt;/a&gt;Core Components&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;MCP Server&lt;/strong&gt;: Provides tools, prompts, and resources via Model Context Protocol&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query Analyzer&lt;/strong&gt;: Parses and analyzes SQL using &lt;code&gt;pglast&lt;/code&gt; library&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance Metrics&lt;/strong&gt;: Collects statistics from PostgreSQL system views&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI Recommendations&lt;/strong&gt;: Generates intelligent suggestions based on workload patterns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple Transport Modes&lt;/strong&gt;: Supports stdio, SSE, and streamable HTTP&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Installation-and-Setup&quot;&gt;&lt;a href=&quot;#Installation-and-Setup&quot; class=&quot;headerlink&quot; title=&quot;Installation and Setup&quot;&gt;&lt;/a&gt;Installation and Setup&lt;/h2&gt;&lt;h3 id=&quot;Prerequisites&quot;&gt;&lt;a href=&quot;#Prerequisites&quot; class=&quot;headerlink&quot; title=&quot;Prerequisites&quot;&gt;&lt;/a&gt;Prerequisites&lt;/h3&gt;&lt;p&gt;Before installing pgtuner_mcp, ensure you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.10+&lt;/li&gt;
&lt;li&gt;PostgreSQL 12+ (recommended: 14+)&lt;/li&gt;
&lt;li&gt;Access to install PostgreSQL extensions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Quick-Installation&quot;&gt;&lt;a href=&quot;#Quick-Installation&quot; class=&quot;headerlink&quot; title=&quot;Quick Installation&quot;&gt;&lt;/a&gt;Quick Installation&lt;/h3&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Install via pip&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pip install pgtuner_mcp&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Or using uv (faster)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;uv pip install pgtuner_mcp&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;PostgreSQL-Extensions-Setup&quot;&gt;&lt;a href=&quot;#PostgreSQL-Extensions-Setup&quot; class=&quot;headerlink&quot; title=&quot;PostgreSQL Extensions Setup&quot;&gt;&lt;/a&gt;PostgreSQL Extensions Setup&lt;/h3&gt;&lt;p&gt;pgtuner_mcp requires specific PostgreSQL extensions for full functionality:&lt;/p&gt;
&lt;h4 id=&quot;1-pg-stat-statements-Required&quot;&gt;&lt;a href=&quot;#1-pg-stat-statements-Required&quot; class=&quot;headerlink&quot; title=&quot;1. pg_stat_statements (Required)&quot;&gt;&lt;/a&gt;1. pg_stat_statements (Required)&lt;/h4&gt;&lt;p&gt;This extension tracks query execution statistics:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Add to postgresql.conf&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;shared_preload_libraries &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;pg_stat_statements&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;compute_query_id &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; o&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pg_stat_statements.max &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;10000&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pg_stat_statements.track &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; top&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pg_stat_statements.track_utility &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Restart PostgreSQL, then:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; EXTENSION IF &lt;span class=&quot;keyword&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;EXISTS&lt;/span&gt; pg_stat_statements;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Verify installation&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; pg_stat_statements LIMIT &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&quot;2-HypoPG-Optional-Recommended&quot;&gt;&lt;a href=&quot;#2-HypoPG-Optional-Recommended&quot; class=&quot;headerlink&quot; title=&quot;2. HypoPG (Optional, Recommended)&quot;&gt;&lt;/a&gt;2. HypoPG (Optional, Recommended)&lt;/h4&gt;&lt;p&gt;Enables hypothetical index testing without disk usage:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; EXTENSION IF &lt;span class=&quot;keyword&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;EXISTS&lt;/span&gt; hypopg;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Verify installation&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; hypopg_list_indexes();&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&quot;3-pgstattuple-Optional-for-Bloat-Detection&quot;&gt;&lt;a href=&quot;#3-pgstattuple-Optional-for-Bloat-Detection&quot; class=&quot;headerlink&quot; title=&quot;3. pgstattuple (Optional, for Bloat Detection)&quot;&gt;&lt;/a&gt;3. pgstattuple (Optional, for Bloat Detection)&lt;/h4&gt;&lt;p&gt;Provides tuple-level statistics for bloat analysis:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; EXTENSION IF &lt;span class=&quot;keyword&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;EXISTS&lt;/span&gt; pgstattuple;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Verify installation&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; pgstattuple(&lt;span class=&quot;string&quot;&gt;&amp;#x27;pg_class&amp;#x27;&lt;/span&gt;) LIMIT &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;User-Permissions&quot;&gt;&lt;a href=&quot;#User-Permissions&quot; class=&quot;headerlink&quot; title=&quot;User Permissions&quot;&gt;&lt;/a&gt;User Permissions&lt;/h3&gt;&lt;p&gt;Create a dedicated monitoring user with minimal required permissions:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Create monitoring user&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;USER&lt;/span&gt; pgtuner_monitor &lt;span class=&quot;keyword&quot;&gt;WITH&lt;/span&gt; PASSWORD &lt;span class=&quot;string&quot;&gt;&amp;#x27;secure_password&amp;#x27;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Grant connection and schema access&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;CONNECT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; DATABASE your_database &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; USAGE &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; SCHEMA public &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Grant read access to user tables&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ALL&lt;/span&gt; TABLES &lt;span class=&quot;keyword&quot;&gt;IN&lt;/span&gt; SCHEMA public &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;DEFAULT&lt;/span&gt; PRIVILEGES &lt;span class=&quot;keyword&quot;&gt;IN&lt;/span&gt; SCHEMA public&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; TABLES &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Grant system statistics access (PostgreSQL 10+)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; pg_read_all_stats &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Grant access to pg_stat_statements&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; pg_stat_statements &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; pg_stat_statements_info &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- For bloat detection (PostgreSQL 14+)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; pg_stat_scan_tables &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- For HypoPG functions&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; hypopg_list_indexes &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FUNCTION&lt;/span&gt; hypopg_create_index(text) &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FUNCTION&lt;/span&gt; hypopg_drop_index(oid) &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FUNCTION&lt;/span&gt; hypopg_reset() &lt;span class=&quot;keyword&quot;&gt;TO&lt;/span&gt; pgtuner_monitor;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Configuration&quot;&gt;&lt;a href=&quot;#Configuration&quot; class=&quot;headerlink&quot; title=&quot;Configuration&quot;&gt;&lt;/a&gt;Configuration&lt;/h2&gt;&lt;h3 id=&quot;Server-Modes&quot;&gt;&lt;a href=&quot;#Server-Modes&quot; class=&quot;headerlink&quot; title=&quot;Server Modes&quot;&gt;&lt;/a&gt;Server Modes&lt;/h3&gt;&lt;p&gt;pgtuner_mcp supports three deployment modes:&lt;/p&gt;
&lt;h4 id=&quot;1-Standard-MCP-Mode-stdio&quot;&gt;&lt;a href=&quot;#1-Standard-MCP-Mode-stdio&quot; class=&quot;headerlink&quot; title=&quot;1. Standard MCP Mode (stdio)&quot;&gt;&lt;/a&gt;1. Standard MCP Mode (stdio)&lt;/h4&gt;&lt;p&gt;Best for MCP clients like Claude Desktop or Cline:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Default mode&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;python -m pgtuner_mcp&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Explicit stdio mode&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;python -m pgtuner_mcp --mode stdio&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Configuration for Claude Desktop&lt;/strong&gt; (&lt;code&gt;cline_mcp_settings.json&lt;/code&gt;):&lt;/p&gt;
&lt;figure class=&quot;highlight json&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;&amp;quot;mcpServers&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;attr&quot;&gt;&amp;quot;pgtuner_mcp&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;attr&quot;&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;attr&quot;&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;-m&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;pgtuner_mcp&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;attr&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;attr&quot;&gt;&amp;quot;DATABASE_URI&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;postgresql://pgtuner_monitor:password@localhost:5432/mydb&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;attr&quot;&gt;&amp;quot;disabled&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;attr&quot;&gt;&amp;quot;autoApprove&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&quot;2-HTTP-SSE-Mode-Legacy-Web-Applications&quot;&gt;&lt;a href=&quot;#2-HTTP-SSE-Mode-Legacy-Web-Applications&quot; class=&quot;headerlink&quot; title=&quot;2. HTTP SSE Mode (Legacy Web Applications)&quot;&gt;&lt;/a&gt;2. HTTP SSE Mode (Legacy Web Applications)&lt;/h4&gt;&lt;p&gt;Server-Sent Events for web-based MCP communication:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Start SSE server&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;python -m pgtuner_mcp --mode sse --host 0.0.0.0 --port 8080&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# With debug mode&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;python -m pgtuner_mcp --mode sse --debug&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Endpoints&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /sse&lt;/code&gt; - SSE connection endpoint&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /messages&lt;/code&gt; - Send messages&amp;#x2F;requests&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-Streamable-HTTP-Mode-Recommended-for-Web&quot;&gt;&lt;a href=&quot;#3-Streamable-HTTP-Mode-Recommended-for-Web&quot; class=&quot;headerlink&quot; title=&quot;3. Streamable HTTP Mode (Recommended for Web)&quot;&gt;&lt;/a&gt;3. Streamable HTTP Mode (Recommended for Web)&lt;/h4&gt;&lt;p&gt;Modern MCP protocol with single &lt;code&gt;/mcp&lt;/code&gt; endpoint:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Stateful mode (maintains session state)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;python -m pgtuner_mcp --mode streamable-http&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Stateless mode (serverless-friendly)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;python -m pgtuner_mcp --mode streamable-http --stateless&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Custom host/port&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;python -m pgtuner_mcp --mode streamable-http --host localhost --port 8080&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight json&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;&amp;quot;mcpServers&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;attr&quot;&gt;&amp;quot;pgtuner_mcp&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;http&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;attr&quot;&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;http://localhost:8080/mcp&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Environment-Variables&quot;&gt;&lt;a href=&quot;#Environment-Variables&quot; class=&quot;headerlink&quot; title=&quot;Environment Variables&quot;&gt;&lt;/a&gt;Environment Variables&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DATABASE_URI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL connection string&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PGTUNER_EXCLUDE_USERIDS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Comma-separated user OIDs to exclude&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Connection String Format&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight elixir&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;symbol&quot;&gt;postgresql:&lt;/span&gt;//&lt;span class=&quot;symbol&quot;&gt;user:&lt;/span&gt;password&lt;span class=&quot;variable&quot;&gt;@host&lt;/span&gt;&lt;span class=&quot;symbol&quot;&gt;:port/database&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;



&lt;h2 id=&quot;Available-Tools&quot;&gt;&lt;a href=&quot;#Available-Tools&quot; class=&quot;headerlink&quot; title=&quot;Available Tools&quot;&gt;&lt;/a&gt;Available Tools&lt;/h2&gt;&lt;p&gt;pgtuner_mcp provides 15+ specialized tools organized into categories:&lt;/p&gt;
&lt;h3 id=&quot;Performance-Analysis-Tools&quot;&gt;&lt;a href=&quot;#Performance-Analysis-Tools&quot; class=&quot;headerlink&quot; title=&quot;Performance Analysis Tools&quot;&gt;&lt;/a&gt;Performance Analysis Tools&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_slow_queries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieve slow queries with detailed statistics (time, calls, cache hit ratio)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;analyze_query&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyze execution plans with EXPLAIN ANALYZE and automated issue detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_table_stats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get table statistics: size, row counts, dead tuples, access patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;analyze_disk_io_patterns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyze I&amp;#x2F;O patterns, identify hot tables and bottlenecks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;Index-Tuning-Tools&quot;&gt;&lt;a href=&quot;#Index-Tuning-Tools&quot; class=&quot;headerlink&quot; title=&quot;Index Tuning Tools&quot;&gt;&lt;/a&gt;Index Tuning Tools&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_index_recommendations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI-powered index recommendations based on workload analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;explain_with_indexes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Test hypothetical indexes without creating them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;manage_hypothetical_indexes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create, list, drop, or reset HypoPG hypothetical indexes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;find_unused_indexes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Find unused and duplicate indexes for cleanup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;Database-Health-Tools&quot;&gt;&lt;a href=&quot;#Database-Health-Tools&quot; class=&quot;headerlink&quot; title=&quot;Database Health Tools&quot;&gt;&lt;/a&gt;Database Health Tools&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check_database_health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Comprehensive health check with scoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_active_queries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Monitor active queries and find long-running transactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;analyze_wait_events&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Identify I&amp;#x2F;O, lock, or CPU bottlenecks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;review_settings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Review PostgreSQL configuration with recommendations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;Bloat-Detection-Tools&quot;&gt;&lt;a href=&quot;#Bloat-Detection-Tools&quot; class=&quot;headerlink&quot; title=&quot;Bloat Detection Tools&quot;&gt;&lt;/a&gt;Bloat Detection Tools&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;analyze_table_bloat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyze table bloat using pgstattuple extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;analyze_index_bloat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyze B-tree index bloat (also supports GIN&amp;#x2F;Hash)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_bloat_summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Comprehensive bloat overview with maintenance priorities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;Vacuum-Monitoring-Tools&quot;&gt;&lt;a href=&quot;#Vacuum-Monitoring-Tools&quot; class=&quot;headerlink&quot; title=&quot;Vacuum Monitoring Tools&quot;&gt;&lt;/a&gt;Vacuum Monitoring Tools&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;monitor_vacuum_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Track VACUUM, VACUUM FULL, and autovacuum operations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;Docker-Deployment&quot;&gt;&lt;a href=&quot;#Docker-Deployment&quot; class=&quot;headerlink&quot; title=&quot;Docker Deployment&quot;&gt;&lt;/a&gt;Docker Deployment&lt;/h2&gt;&lt;p&gt;pgtuner_mcp is available as a Docker image for easy deployment:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Pull the image&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;docker pull dog830228/pgtuner_mcp&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Run in streamable HTTP mode (recommended)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;docker run -p 8080:8080 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  -e DATABASE_URI=postgresql://user:pass@host:5432/db \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  dog830228/pgtuner_mcp --mode streamable-http&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Run in stateless mode (serverless-friendly)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;docker run -p 8080:8080 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  -e DATABASE_URI=postgresql://user:pass@host:5432/db \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  dog830228/pgtuner_mcp --mode streamable-http --stateless&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Run in stdio mode for MCP clients&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;docker run -i \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  -e DATABASE_URI=postgresql://user:pass@host:5432/db \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  dog830228/pgtuner_mcp --mode stdio&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Real-World-Use-Cases&quot;&gt;&lt;a href=&quot;#Real-World-Use-Cases&quot; class=&quot;headerlink&quot; title=&quot;Real-World Use Cases&quot;&gt;&lt;/a&gt;Real-World Use Cases&lt;/h2&gt;&lt;h3 id=&quot;1-Slow-Query-Investigation&quot;&gt;&lt;a href=&quot;#1-Slow-Query-Investigation&quot; class=&quot;headerlink&quot; title=&quot;1. Slow Query Investigation&quot;&gt;&lt;/a&gt;1. Slow Query Investigation&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Application experiencing slow response times.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight n1ql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;User: &amp;quot;Find the slowest queries in my database&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;AI + pgtuner_mcp:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  1. Calls get_slow_queries(limit=10, order_by=&lt;span class=&quot;string&quot;&gt;&amp;quot;total_time&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;number&quot;&gt;2.&lt;/span&gt; Identifies top &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt; problematic queries&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;number&quot;&gt;3.&lt;/span&gt; Calls analyze_query() &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;each&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;number&quot;&gt;4.&lt;/span&gt; Detects sequential scans &lt;span class=&quot;keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;missing&lt;/span&gt; indexes&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;number&quot;&gt;5.&lt;/span&gt; Calls get_index_recommendations()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;number&quot;&gt;6.&lt;/span&gt; Provides &lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;INDEX&lt;/span&gt; statements &lt;span class=&quot;keyword&quot;&gt;with&lt;/span&gt; impact estimates&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;2-Index-Optimization&quot;&gt;&lt;a href=&quot;#2-Index-Optimization&quot; class=&quot;headerlink&quot; title=&quot;2. Index Optimization&quot;&gt;&lt;/a&gt;2. Index Optimization&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Database growing, need to optimize indexes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight csharp&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;User: &lt;span class=&quot;string&quot;&gt;&amp;quot;Help me optimize my database indexes&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;AI + pgtuner_mcp:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;number&quot;&gt;1.&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;Calls &lt;span class=&quot;title&quot;&gt;find_unused_indexes&lt;/span&gt;() to identify cleanup candidates&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;  2. Calls &lt;span class=&quot;title&quot;&gt;get_index_recommendations&lt;/span&gt;() &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; index suggestions&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;  3. Calls &lt;span class=&quot;title&quot;&gt;explain_with_indexes&lt;/span&gt;() to test hypothetical indexes&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;  4. Estimates storage savings &lt;span class=&quot;keyword&quot;&gt;and&lt;/span&gt; performance improvements&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;  5. Provides prioritized action plan&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;3-Health-Check-Before-Production-Deploy&quot;&gt;&lt;a href=&quot;#3-Health-Check-Before-Production-Deploy&quot; class=&quot;headerlink&quot; title=&quot;3. Health Check Before Production Deploy&quot;&gt;&lt;/a&gt;3. Health Check Before Production Deploy&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Pre-deployment database health validation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight routeros&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;User: &lt;span class=&quot;string&quot;&gt;&amp;quot;Is my database ready for production traffic?&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;AI + pgtuner_mcp:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  1. Calls check_database_health(&lt;span class=&quot;attribute&quot;&gt;verbose&lt;/span&gt;=&lt;span class=&quot;literal&quot;&gt;True&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  2. Analyzes&lt;span class=&quot;built_in&quot;&gt; connection pool &lt;/span&gt;capacity&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  3. Checks cache hit ratios&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  4. Reviews vacuum &lt;span class=&quot;keyword&quot;&gt;and&lt;/span&gt; autovacuum status&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  5. Analyzes wait events&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  6. Reviews configuration&lt;span class=&quot;built_in&quot;&gt; settings&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;&lt;/span&gt;  7. Provides comprehensive&lt;span class=&quot;built_in&quot;&gt; health &lt;/span&gt;report with recommendations&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;4-Performance-Regression-Investigation&quot;&gt;&lt;a href=&quot;#4-Performance-Regression-Investigation&quot; class=&quot;headerlink&quot; title=&quot;4. Performance Regression Investigation&quot;&gt;&lt;/a&gt;4. Performance Regression Investigation&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Performance degraded after recent changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight smali&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;User: &lt;span class=&quot;string&quot;&gt;&amp;quot;Why is my database slower than last week?&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;AI + pgtuner_mcp:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  1. Calls get_table_stats() to identify growth patterns&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  2. Calls analyze_disk_io_patterns() for I/O bottlenecks&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  3. Calls get_bloat_summary() to detect table/index bloat&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  4. Calls monitor_vacuum_progress() to&lt;span class=&quot;built_in&quot;&gt; check &lt;/span&gt;maintenance&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  5. Calls analyze_wait_events() to find resource contention&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  6. Identifies root causes&lt;span class=&quot;built_in&quot;&gt; and &lt;/span&gt;provides remediation steps&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Performance-Considerations&quot;&gt;&lt;a href=&quot;#Performance-Considerations&quot; class=&quot;headerlink&quot; title=&quot;Performance Considerations&quot;&gt;&lt;/a&gt;Performance Considerations&lt;/h2&gt;&lt;h3 id=&quot;Extension-Overhead&quot;&gt;&lt;a href=&quot;#Extension-Overhead&quot; class=&quot;headerlink&quot; title=&quot;Extension Overhead&quot;&gt;&lt;/a&gt;Extension Overhead&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Extension&lt;/th&gt;
&lt;th&gt;Performance Impact&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pg_stat_statements&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low (~1-2%)&lt;/td&gt;
&lt;td&gt;Always enable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;track_io_timing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low-Medium (~2-5%)&lt;/td&gt;
&lt;td&gt;Enable in production, test first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;track_functions = all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Enable for function-heavy workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pgstattuple&lt;/code&gt; functions&lt;/td&gt;
&lt;td&gt;Varies by table size&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;_approx&lt;/code&gt; for large tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HypoPG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero (in-memory only)&lt;/td&gt;
&lt;td&gt;Safe for all environments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Use &lt;code&gt;pg_test_timing&lt;/code&gt; to measure timing overhead on your specific hardware:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; pg_test_timing();&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Best-Practices&quot;&gt;&lt;a href=&quot;#Best-Practices&quot; class=&quot;headerlink&quot; title=&quot;Best Practices&quot;&gt;&lt;/a&gt;Best Practices&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use Approximate Analysis&lt;/strong&gt;: For large tables (&amp;gt;5GB), use &lt;code&gt;pgstattuple_approx&lt;/code&gt; instead of &lt;code&gt;pgstattuple&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filter System Users&lt;/strong&gt;: Exclude monitoring&amp;#x2F;replication users using &lt;code&gt;PGTUNER_EXCLUDE_USERIDS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limit Query History&lt;/strong&gt;: Configure &lt;code&gt;pg_stat_statements.max&lt;/code&gt; based on your workload&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regular Maintenance&lt;/strong&gt;: Use vacuum monitoring tools to ensure optimal performance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test Hypothetical Indexes&lt;/strong&gt;: Always test with HypoPG before creating real indexes&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Conclusion&quot;&gt;&lt;a href=&quot;#Conclusion&quot; class=&quot;headerlink&quot; title=&quot;Conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;&lt;p&gt;pgtuner_mcp represents a paradigm shift in database performance optimization. By combining the power of AI assistants with deep PostgreSQL expertise through the Model Context Protocol, it makes advanced database tuning accessible to developers at all skill levels.&lt;/p&gt;
&lt;p&gt;The tool doesn’t replace database administrators—it augments their capabilities and democratizes access to expert-level analysis. Whether you’re debugging a slow query, planning index strategies, or conducting pre-deployment health checks, pgtuner_mcp provides intelligent, context-aware assistance.&lt;/p&gt;
&lt;h3 id=&quot;Key-Takeaways&quot;&gt;&lt;a href=&quot;#Key-Takeaways&quot; class=&quot;headerlink&quot; title=&quot;Key Takeaways&quot;&gt;&lt;/a&gt;Key Takeaways&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AI-Native Performance Tuning&lt;/strong&gt;: Natural language interface to complex database operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Risk-Free Testing&lt;/strong&gt;: HypoPG enables index testing without disk usage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comprehensive Analysis&lt;/strong&gt;: 15+ tools covering queries, indexes, health, bloat, vacuum, and I&amp;#x2F;O&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexible Deployment&lt;/strong&gt;: stdio, HTTP SSE, or streamable HTTP modes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production-Ready&lt;/strong&gt;: Minimal overhead, proper permissions, comprehensive monitoring&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Whether you’re a seasoned DBA looking to leverage AI for faster workflows or a developer seeking to understand and optimize database performance, pgtuner_mcp offers a powerful, modern approach to PostgreSQL tuning.&lt;/p&gt;
&lt;h3 id=&quot;Resources&quot;&gt;&lt;a href=&quot;#Resources&quot; class=&quot;headerlink&quot; title=&quot;Resources&quot;&gt;&lt;/a&gt;Resources&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pgtuner_mcp&quot;&gt;pgtuner_mcp GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.postgresql.org/docs/current/performance-tips.html&quot;&gt;PostgreSQL Performance Tuning Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/HypoPG/hypopg&quot;&gt;HypoPG Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.postgresql.org/docs/current/pgstatstatements.html&quot;&gt;pg_stat_statements Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/pgtuner-mcp-ai-powered-postgresql-performance/&quot;&gt;https://isdaniel.github.io/pgtuner-mcp-ai-powered-postgresql-performance/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building Safe PostgreSQL Extensions with Rust - Introducing pg_where_guard</title>
    <link href="https://isdaniel.github.io/pg-where-guard-rust-postgresql-extension/"/>
    <id>https://isdaniel.github.io/pg-where-guard-rust-postgresql-extension/</id>
    <published>2025-01-02T23:20:00.000Z</published>
    <updated>2025-01-02T23:20:00.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>pg_where_guard is a Rust/pgrx PostgreSQL extension that blocks unsafe UPDATE and DELETE without WHERE clauses, preventing accidental data loss</summary>
    <content type="html">&lt;h1 id=&quot;Building-Safe-PostgreSQL-Extensions-with-Rust-Introducing-pg-where-guard&quot;&gt;&lt;a href=&quot;#Building-Safe-PostgreSQL-Extensions-with-Rust-Introducing-pg-where-guard&quot; class=&quot;headerlink&quot; title=&quot;Building Safe PostgreSQL Extensions with Rust - Introducing pg_where_guard&quot;&gt;&lt;/a&gt;Building Safe PostgreSQL Extensions with Rust - Introducing pg_where_guard&lt;/h1&gt;&lt;p&gt;Database safety is a critical concern for any production system. Accidental data loss from &lt;code&gt;DELETE&lt;/code&gt; or &lt;code&gt;UPDATE&lt;/code&gt; statements without &lt;code&gt;WHERE&lt;/code&gt; clauses can be catastrophic. Today, I’ll introduce &lt;strong&gt;pg_where_guard&lt;/strong&gt;, a PostgreSQL extension built with Rust and the pgrx framework that prevents these dangerous operations.&lt;/p&gt;
&lt;h2 id=&quot;What-is-pg-where-guard&quot;&gt;&lt;a href=&quot;#What-is-pg-where-guard&quot; class=&quot;headerlink&quot; title=&quot;What is pg_where_guard?&quot;&gt;&lt;/a&gt;What is pg_where_guard?&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;pg_where_guard&lt;/strong&gt; is a PostgreSQL extension that acts as a safety net for your database by intercepting and blocking potentially dangerous SQL operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DELETE Protection&lt;/strong&gt;: Prevents &lt;code&gt;DELETE FROM table&lt;/code&gt; without WHERE clause&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UPDATE Protection&lt;/strong&gt;: Prevents &lt;code&gt;UPDATE table SET ...&lt;/code&gt; without WHERE clause&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CTE Support&lt;/strong&gt;: Recursively checks Common Table Expressions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hook Integration&lt;/strong&gt;: Uses PostgreSQL’s &lt;code&gt;post_parse_analyze_hook&lt;/code&gt; for query interception&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory Safe&lt;/strong&gt;: Written in Rust with pgrx for safety and performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg_where_guard&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Why-Rust-for-PostgreSQL-Extensions&quot;&gt;&lt;a href=&quot;#Why-Rust-for-PostgreSQL-Extensions&quot; class=&quot;headerlink&quot; title=&quot;Why Rust for PostgreSQL Extensions?&quot;&gt;&lt;/a&gt;Why Rust for PostgreSQL Extensions?&lt;/h2&gt;&lt;p&gt;Building PostgreSQL extensions traditionally meant working with C and dealing with manual memory management, potential segmentation faults, and complex debugging. Rust changes this paradigm by offering:&lt;/p&gt;
&lt;h3 id=&quot;Performance&quot;&gt;&lt;a href=&quot;#Performance&quot; class=&quot;headerlink&quot; title=&quot;Performance&quot;&gt;&lt;/a&gt;Performance&lt;/h3&gt;&lt;p&gt;Zero-cost abstractions mean Rust code performs as well as equivalent C code while being much safer.&lt;/p&gt;
&lt;h3 id=&quot;pgrx-Framework&quot;&gt;&lt;a href=&quot;#pgrx-Framework&quot; class=&quot;headerlink&quot; title=&quot;pgrx Framework&quot;&gt;&lt;/a&gt;pgrx Framework&lt;/h3&gt;&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/pgcentralfoundation/pgrx&quot;&gt;pgrx framework&lt;/a&gt; provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Type-safe PostgreSQL API bindings&lt;/li&gt;
&lt;li&gt;Automatic SQL schema generation&lt;/li&gt;
&lt;li&gt;Comprehensive testing support&lt;/li&gt;
&lt;li&gt;Easy development workflow&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Technical-Architecture&quot;&gt;&lt;a href=&quot;#Technical-Architecture&quot; class=&quot;headerlink&quot; title=&quot;Technical Architecture&quot;&gt;&lt;/a&gt;Technical Architecture&lt;/h2&gt;&lt;h3 id=&quot;Hook-Based-Implementation&quot;&gt;&lt;a href=&quot;#Hook-Based-Implementation&quot; class=&quot;headerlink&quot; title=&quot;Hook-Based Implementation&quot;&gt;&lt;/a&gt;Hook-Based Implementation&lt;/h3&gt;&lt;p&gt;pg_where_guard leverages PostgreSQL’s hook system to intercept queries after parsing:&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// Hook registration in _PG_init&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;PREV_POST_PARSE_ANALYZE_HOOK = pg_sys::post_parse_analyze_hook;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pg_sys::post_parse_analyze_hook = &lt;span class=&quot;title function_ invoke__&quot;&gt;Some&lt;/span&gt;(delete_needs_where_check);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Query-Analysis-Engine&quot;&gt;&lt;a href=&quot;#Query-Analysis-Engine&quot; class=&quot;headerlink&quot; title=&quot;Query Analysis Engine&quot;&gt;&lt;/a&gt;Query Analysis Engine&lt;/h3&gt;&lt;p&gt;The extension examines the parsed query tree to detect dangerous operations:&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// Query checking logic&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;match&lt;/span&gt; query.commandType &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    pg_sys::CmdType::CMD_DELETE =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; !query.jointree.&lt;span class=&quot;title function_ invoke__&quot;&gt;is_null&lt;/span&gt;() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;jointree&lt;/span&gt; = &amp;amp;*query.jointree;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; jointree.quals.&lt;span class=&quot;title function_ invoke__&quot;&gt;is_null&lt;/span&gt;() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                error!(&lt;span class=&quot;string&quot;&gt;&amp;quot;DELETE requires a WHERE clause&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    pg_sys::CmdType::CMD_UPDATE =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; !query.jointree.&lt;span class=&quot;title function_ invoke__&quot;&gt;is_null&lt;/span&gt;() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;jointree&lt;/span&gt; = &amp;amp;*query.jointree;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; jointree.quals.&lt;span class=&quot;title function_ invoke__&quot;&gt;is_null&lt;/span&gt;() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                error!(&lt;span class=&quot;string&quot;&gt;&amp;quot;UPDATE requires a WHERE clause&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    _ =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// Other command types are allowed&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Key-Components&quot;&gt;&lt;a href=&quot;#Key-Components&quot; class=&quot;headerlink&quot; title=&quot;Key Components&quot;&gt;&lt;/a&gt;Key Components&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hook Function&lt;/strong&gt; (&lt;code&gt;delete_needs_where_check&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intercepts queries via &lt;code&gt;post_parse_analyze_hook&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Checks command types (DELETE&amp;#x2F;UPDATE)&lt;/li&gt;
&lt;li&gt;Validates presence of WHERE clauses&lt;/li&gt;
&lt;li&gt;Handles Common Table Expressions recursively&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query Analysis&lt;/strong&gt; (&lt;code&gt;check_query_for_where_clause&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Examines the query’s &lt;code&gt;jointree&lt;/code&gt; structure&lt;/li&gt;
&lt;li&gt;Looks for &lt;code&gt;quals&lt;/code&gt; (qualification&amp;#x2F;WHERE conditions)&lt;/li&gt;
&lt;li&gt;Throws errors for unqualified modifications&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extension Functions&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pg_where_guard_is_enabled()&lt;/code&gt;: Check if protection is active&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pg_where_guard_enable()&lt;/code&gt;: Enable protection&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Installation-and-Setup&quot;&gt;&lt;a href=&quot;#Installation-and-Setup&quot; class=&quot;headerlink&quot; title=&quot;Installation and Setup&quot;&gt;&lt;/a&gt;Installation and Setup&lt;/h2&gt;&lt;h3 id=&quot;Prerequisites&quot;&gt;&lt;a href=&quot;#Prerequisites&quot; class=&quot;headerlink&quot; title=&quot;Prerequisites&quot;&gt;&lt;/a&gt;Prerequisites&lt;/h3&gt;&lt;p&gt;Before installing pg_where_guard, ensure you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rust toolchain (1.70+)&lt;/li&gt;
&lt;li&gt;pgrx framework&lt;/li&gt;
&lt;li&gt;PostgreSQL development headers&lt;/li&gt;
&lt;li&gt;cargo-pgrx&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Build-and-Install&quot;&gt;&lt;a href=&quot;#Build-and-Install&quot; class=&quot;headerlink&quot; title=&quot;Build and Install&quot;&gt;&lt;/a&gt;Build and Install&lt;/h3&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Clone the repository&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;git &lt;span class=&quot;built_in&quot;&gt;clone&lt;/span&gt; https://github.com/isdaniel/pg_where_guard.git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;cd&lt;/span&gt; pg_where_guard&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Install cargo-pgrx if not already installed&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cargo install cargo-pgrx&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Initialize pgrx for your PostgreSQL version&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cargo pgrx init&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Install the extension&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cargo pgrx install&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Database-Setup&quot;&gt;&lt;a href=&quot;#Database-Setup&quot; class=&quot;headerlink&quot; title=&quot;Database Setup&quot;&gt;&lt;/a&gt;Database Setup&lt;/h3&gt;&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Create the extension&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; EXTENSION pg_where_guard;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Verify installation&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; pg_where_guard_is_enabled();  &lt;span class=&quot;comment&quot;&gt;-- Returns: true&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Usage-Examples&quot;&gt;&lt;a href=&quot;#Usage-Examples&quot; class=&quot;headerlink&quot; title=&quot;Usage Examples&quot;&gt;&lt;/a&gt;Usage Examples&lt;/h2&gt;&lt;h3 id=&quot;Safe-Operations-Allowed&quot;&gt;&lt;a href=&quot;#Safe-Operations-Allowed&quot; class=&quot;headerlink&quot; title=&quot;Safe Operations (Allowed)&quot;&gt;&lt;/a&gt;Safe Operations (Allowed)&lt;/h3&gt;&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Create a test table&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE TABLE&lt;/span&gt; employees (&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    id SERIAL &lt;span class=&quot;keyword&quot;&gt;PRIMARY KEY&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    name TEXT &lt;span class=&quot;keyword&quot;&gt;NOT NULL&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    department TEXT,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    salary &lt;span class=&quot;type&quot;&gt;INTEGER&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- Insert test data&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;INSERT INTO&lt;/span&gt; employees (name, department, salary) &lt;span class=&quot;keyword&quot;&gt;VALUES&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    (&lt;span class=&quot;string&quot;&gt;&amp;#x27;Alice Johnson&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;Engineering&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;75000&lt;/span&gt;),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    (&lt;span class=&quot;string&quot;&gt;&amp;#x27;Bob Smith&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;Marketing&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;65000&lt;/span&gt;),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    (&lt;span class=&quot;string&quot;&gt;&amp;#x27;Charlie Brown&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;#x27;Engineering&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;80000&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- These operations work fine (have WHERE clauses)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;UPDATE&lt;/span&gt; employees &lt;span class=&quot;keyword&quot;&gt;SET&lt;/span&gt; salary &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;78000&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; name &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;Alice Johnson&amp;#x27;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; employees &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; department &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;Marketing&amp;#x27;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Dangerous-Operations-Blocked&quot;&gt;&lt;a href=&quot;#Dangerous-Operations-Blocked&quot; class=&quot;headerlink&quot; title=&quot;Dangerous Operations (Blocked)&quot;&gt;&lt;/a&gt;Dangerous Operations (Blocked)&lt;/h3&gt;&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- These commands will FAIL due to pg_where_guard protection:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- This will fail: UPDATE without WHERE clause&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;UPDATE&lt;/span&gt; employees &lt;span class=&quot;keyword&quot;&gt;SET&lt;/span&gt; salary &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;100000&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- ERROR: UPDATE requires a WHERE clause&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- This will fail: DELETE without WHERE clause&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; employees;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- ERROR: DELETE requires a WHERE clause&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Common-Table-Expression-Support&quot;&gt;&lt;a href=&quot;#Common-Table-Expression-Support&quot; class=&quot;headerlink&quot; title=&quot;Common Table Expression Support&quot;&gt;&lt;/a&gt;Common Table Expression Support&lt;/h3&gt;&lt;p&gt;The extension also protects CTEs:&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;-- This would also be blocked&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;WITH&lt;/span&gt; department_update &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; (&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;UPDATE&lt;/span&gt; employees &lt;span class=&quot;keyword&quot;&gt;SET&lt;/span&gt; salary &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; salary &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;1.1&lt;/span&gt;  &lt;span class=&quot;comment&quot;&gt;-- No WHERE clause!&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    RETURNING &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; department_update;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Performance-Considerations&quot;&gt;&lt;a href=&quot;#Performance-Considerations&quot; class=&quot;headerlink&quot; title=&quot;Performance Considerations&quot;&gt;&lt;/a&gt;Performance Considerations&lt;/h2&gt;&lt;h3 id=&quot;Minimal-Overhead&quot;&gt;&lt;a href=&quot;#Minimal-Overhead&quot; class=&quot;headerlink&quot; title=&quot;Minimal Overhead&quot;&gt;&lt;/a&gt;Minimal Overhead&lt;/h3&gt;&lt;p&gt;pg_where_guard adds minimal performance overhead because it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only analyzes DELETE and UPDATE statements&lt;/li&gt;
&lt;li&gt;Performs lightweight checks on the parsed query tree&lt;/li&gt;
&lt;li&gt;Uses efficient Rust code with zero-cost abstractions&lt;/li&gt;
&lt;li&gt;Operates at parse time, not execution time&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Production-Readiness&quot;&gt;&lt;a href=&quot;#Production-Readiness&quot; class=&quot;headerlink&quot; title=&quot;Production Readiness&quot;&gt;&lt;/a&gt;Production Readiness&lt;/h3&gt;&lt;p&gt;The extension is designed for production use with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Comprehensive error handling&lt;/li&gt;
&lt;li&gt;Memory-safe implementation&lt;/li&gt;
&lt;li&gt;Minimal system resource usage&lt;/li&gt;
&lt;li&gt;Support for PostgreSQL 12-16&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Development-and-Testing&quot;&gt;&lt;a href=&quot;#Development-and-Testing&quot; class=&quot;headerlink&quot; title=&quot;Development and Testing&quot;&gt;&lt;/a&gt;Development and Testing&lt;/h2&gt;&lt;h3 id=&quot;Project-Structure&quot;&gt;&lt;a href=&quot;#Project-Structure&quot; class=&quot;headerlink&quot; title=&quot;Project Structure&quot;&gt;&lt;/a&gt;Project Structure&lt;/h3&gt;&lt;figure class=&quot;highlight pgsql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;pg_where_guard/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── Cargo.toml              # Rust project &lt;span class=&quot;keyword&quot;&gt;configuration&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── pg_where_guard.control  # PostgreSQL &lt;span class=&quot;keyword&quot;&gt;extension&lt;/span&gt; control file&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── src/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   ├── lib.rs             # Main &lt;span class=&quot;keyword&quot;&gt;extension&lt;/span&gt; code&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   └── bin/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│       └── pgrx_embed.rs  # pgrx &lt;span class=&quot;keyword&quot;&gt;schema&lt;/span&gt; generation&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── &lt;span class=&quot;keyword&quot;&gt;sql&lt;/span&gt;/                   # &lt;span class=&quot;keyword&quot;&gt;SQL&lt;/span&gt; test scripts&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└── tests/                 # Test files&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Running-Tests&quot;&gt;&lt;a href=&quot;#Running-Tests&quot; class=&quot;headerlink&quot; title=&quot;Running Tests&quot;&gt;&lt;/a&gt;Running Tests&lt;/h3&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Run the test suite&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cargo pgrx &lt;span class=&quot;built_in&quot;&gt;test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Test with specific PostgreSQL version&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cargo pgrx &lt;span class=&quot;built_in&quot;&gt;test&lt;/span&gt; pg15&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Development-Workflow&quot;&gt;&lt;a href=&quot;#Development-Workflow&quot; class=&quot;headerlink&quot; title=&quot;Development Workflow&quot;&gt;&lt;/a&gt;Development Workflow&lt;/h3&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Start a development PostgreSQL instance&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cargo pgrx run&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Install the extension in development&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cargo pgrx install --debug&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Benefits-of-the-Rust-pgrx-Approach&quot;&gt;&lt;a href=&quot;#Benefits-of-the-Rust-pgrx-Approach&quot; class=&quot;headerlink&quot; title=&quot;Benefits of the Rust + pgrx Approach&quot;&gt;&lt;/a&gt;Benefits of the Rust + pgrx Approach&lt;/h2&gt;&lt;h3 id=&quot;Developer-Experience&quot;&gt;&lt;a href=&quot;#Developer-Experience&quot; class=&quot;headerlink&quot; title=&quot;Developer Experience&quot;&gt;&lt;/a&gt;Developer Experience&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;: Compile-time guarantees prevent runtime errors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modern Tooling&lt;/strong&gt;: Cargo ecosystem and excellent IDE support&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing&lt;/strong&gt;: Built-in unit testing and integration testing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Automatic documentation generation&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Safety-Guarantees&quot;&gt;&lt;a href=&quot;#Safety-Guarantees&quot; class=&quot;headerlink&quot; title=&quot;Safety Guarantees&quot;&gt;&lt;/a&gt;Safety Guarantees&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Memory Safety&lt;/strong&gt;: No buffer overflows or memory leaks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thread Safety&lt;/strong&gt;: Rust’s ownership model prevents data races&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;: Explicit error handling with Result types&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Null Safety&lt;/strong&gt;: No null pointer dereferences&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Performance-Benefits&quot;&gt;&lt;a href=&quot;#Performance-Benefits&quot; class=&quot;headerlink&quot; title=&quot;Performance Benefits&quot;&gt;&lt;/a&gt;Performance Benefits&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Zero-Cost Abstractions&lt;/strong&gt;: High-level code without runtime overhead&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimized Compilation&lt;/strong&gt;: LLVM backend generates efficient machine code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimal Dependencies&lt;/strong&gt;: Small runtime footprint&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficient Resource Usage&lt;/strong&gt;: Predictable memory usage patterns&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Comparison-with-Traditional-C-Extensions&quot;&gt;&lt;a href=&quot;#Comparison-with-Traditional-C-Extensions&quot; class=&quot;headerlink&quot; title=&quot;Comparison with Traditional C Extensions&quot;&gt;&lt;/a&gt;Comparison with Traditional C Extensions&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;C Extension&lt;/th&gt;
&lt;th&gt;Rust + pgrx Extension&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Memory Safety&lt;/td&gt;
&lt;td&gt;Manual management&lt;/td&gt;
&lt;td&gt;Automatic, compile-time guaranteed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Development Speed&lt;/td&gt;
&lt;td&gt;Slow, error-prone&lt;/td&gt;
&lt;td&gt;Fast, safe development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;GDB, complex&lt;/td&gt;
&lt;td&gt;Standard Rust tooling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Manual, limited&lt;/td&gt;
&lt;td&gt;Built-in unit&amp;#x2F;integration tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;High overhead&lt;/td&gt;
&lt;td&gt;Low overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Optimal&lt;/td&gt;
&lt;td&gt;Near-optimal with safety&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;Integration-Possibilities&quot;&gt;&lt;a href=&quot;#Integration-Possibilities&quot; class=&quot;headerlink&quot; title=&quot;Integration Possibilities&quot;&gt;&lt;/a&gt;Integration Possibilities&lt;/h3&gt;&lt;p&gt;pg_where_guard can be integrated with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Database Migration Tools&lt;/strong&gt;: Validate migrations before execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ORM Frameworks&lt;/strong&gt;: Add safety checks to generated queries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring Systems&lt;/strong&gt;: Alert on attempted dangerous operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit Systems&lt;/strong&gt;: Log blocked operations for compliance&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Conclusion&quot;&gt;&lt;a href=&quot;#Conclusion&quot; class=&quot;headerlink&quot; title=&quot;Conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;&lt;p&gt;pg_where_guard demonstrates the power of modern Rust tooling for PostgreSQL extension development. By combining Rust’s safety guarantees with pgrx’s ease of use, we can build robust database tools that protect against common but dangerous operations.&lt;/p&gt;
&lt;p&gt;The extension serves as both a practical safety tool and an example of how Rust is revolutionizing systems programming beyond traditional applications. As the PostgreSQL ecosystem continues to evolve, Rust-based extensions like pg_where_guard pave the way for safer, more maintainable database tools.&lt;/p&gt;
&lt;h3 id=&quot;Key-Takeaways&quot;&gt;&lt;a href=&quot;#Key-Takeaways&quot; class=&quot;headerlink&quot; title=&quot;Key Takeaways&quot;&gt;&lt;/a&gt;Key Takeaways&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Safety First&lt;/strong&gt;: Rust eliminates entire classes of bugs that plague C extensions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Productivity&lt;/strong&gt;: pgrx makes PostgreSQL extension development accessible&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Memory safety doesn’t require sacrificing performance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Future-Proof&lt;/strong&gt;: Rust’s growing ecosystem ensures long-term maintainability&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Whether you’re looking to protect your database from accidental data loss or explore modern PostgreSQL extension development, pg_where_guard offers a compelling example of what’s possible with Rust and pgrx.&lt;/p&gt;
&lt;h3 id=&quot;Resources&quot;&gt;&lt;a href=&quot;#Resources&quot; class=&quot;headerlink&quot; title=&quot;Resources&quot;&gt;&lt;/a&gt;Resources&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/isdaniel/pg_where_guard&quot;&gt;pg_where_guard GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/pgcentralfoundation/pgrx&quot;&gt;pgrx Framework Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.postgresql.org/docs/current/extend.html&quot;&gt;PostgreSQL Extension Development Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/pg-where-guard-rust-postgresql-extension/&quot;&gt;https://isdaniel.github.io/pg-where-guard-rust-postgresql-extension/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How can we use vscode debug PostgreSQL running process by source code?</title>
    <link href="https://isdaniel.github.io/postgresql-vscode-sourcecode-debugger/"/>
    <id>https://isdaniel.github.io/postgresql-vscode-sourcecode-debugger/</id>
    <published>2023-06-02T14:30:11.000Z</published>
    <updated>2023-06-02T14:30:11.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>Step-by-step guide to debug a running PostgreSQL process with VSCode on Linux — build from source with debug symbols and set breakpoints</summary>
    <content type="html">&lt;h2 id=&quot;Foreword&quot;&gt;&lt;a href=&quot;#Foreword&quot; class=&quot;headerlink&quot; title=&quot;Foreword&quot;&gt;&lt;/a&gt;Foreword&lt;/h2&gt;&lt;p&gt;Using gdb for command-line debugging still feels inconvenient. I initially wanted to find a simpler way to directly debug the PostgreSQL source code under Windows. After searching for a while, I found that Visual Studio (VS) was the only option available, but it is heavy and the steps are quite complex. Since most real environments run on Linux, it is better to debug the PostgreSQL source code under Linux.&lt;/p&gt;
&lt;h2 id=&quot;How-to-build-install-PostgreSQL-from-source-code&quot;&gt;&lt;a href=&quot;#How-to-build-install-PostgreSQL-from-source-code&quot; class=&quot;headerlink&quot; title=&quot;How to build &amp;amp; install PostgreSQL from source code.&quot;&gt;&lt;/a&gt;How to build &amp;amp; install PostgreSQL from source code.&lt;/h2&gt;&lt;p&gt;I used Ubuntu Linux environment, the first step we might need to install pre-requirement tool for PostgreSQL build.&lt;/p&gt;
&lt;figure class=&quot;highlight q&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;sudo apt-&lt;span class=&quot;built_in&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;update&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sudo apt-&lt;span class=&quot;built_in&quot;&gt;get&lt;/span&gt; install build-essential libreadline-&lt;span class=&quot;built_in&quot;&gt;dev&lt;/span&gt; zlib1g-&lt;span class=&quot;built_in&quot;&gt;dev&lt;/span&gt; flex bison libxml2-&lt;span class=&quot;built_in&quot;&gt;dev&lt;/span&gt; libxslt-&lt;span class=&quot;built_in&quot;&gt;dev&lt;/span&gt; libssl-&lt;span class=&quot;built_in&quot;&gt;dev&lt;/span&gt; libxml2-utils xsltproc ccache libsystemd-&lt;span class=&quot;built_in&quot;&gt;dev&lt;/span&gt; -y&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;download PostgreSQL source code.&lt;/p&gt;
&lt;figure class=&quot;highlight apache&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attribute&quot;&gt;wget&lt;/span&gt; https://ftp.postgresql.org/pub/source/v14.&lt;span class=&quot;number&quot;&gt;8&lt;/span&gt;/postgresql-&lt;span class=&quot;number&quot;&gt;14&lt;/span&gt;.&lt;span class=&quot;number&quot;&gt;8&lt;/span&gt;.tar.gz&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attribute&quot;&gt;tar&lt;/span&gt; xvfz postgresql-&lt;span class=&quot;number&quot;&gt;14&lt;/span&gt;.&lt;span class=&quot;number&quot;&gt;8&lt;/span&gt;.tar.gz&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attribute&quot;&gt;cd&lt;/span&gt; postgresql-&lt;span class=&quot;number&quot;&gt;14&lt;/span&gt;.&lt;span class=&quot;number&quot;&gt;8&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;we would need to make sure the path (&lt;code&gt;--prefix&lt;/code&gt;) exist in your system.  &lt;/p&gt;
&lt;figure class=&quot;highlight jboss-cli&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;string&quot;&gt;./configure&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--prefix=/home/daniel/postgresql-14&lt;/span&gt;.8/pgsql &lt;span class=&quot;params&quot;&gt;--with-icu&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--with-openssl&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--with-systemd&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--with-libxml&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--enable-debug&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#or debug -g3 mode&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;string&quot;&gt;./configure&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--prefix=/home/daniel/postgresql-14&lt;/span&gt;.8/pgsql &lt;span class=&quot;params&quot;&gt;--with-icu&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--with-openssl&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--with-systemd&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--with-libxml&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;--enable-debug&lt;/span&gt; CFLAGS=&lt;span class=&quot;string&quot;&gt;&amp;quot;-DGCC_HASCLASSVISIBILITY -O0 -Wall -W -g3 -gdwarf-2&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;make -j 8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;make install&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;we must build with &lt;code&gt;--enable-debug&lt;/code&gt; parameter, otherwise we can’t debug with our source code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here are commands we would use later.&lt;/p&gt;
&lt;figure class=&quot;highlight awk&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;regexp&quot;&gt;/home/&lt;/span&gt;daniel&lt;span class=&quot;regexp&quot;&gt;/postgresql-14.8/&lt;/span&gt;pgsql&lt;span class=&quot;regexp&quot;&gt;/bin/&lt;/span&gt;psql&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;regexp&quot;&gt;/home/&lt;/span&gt;daniel&lt;span class=&quot;regexp&quot;&gt;/postgresql-14.8/&lt;/span&gt;pgsql&lt;span class=&quot;regexp&quot;&gt;/bin/i&lt;/span&gt;nitdb -D &lt;span class=&quot;regexp&quot;&gt;/home/&lt;/span&gt;daniel&lt;span class=&quot;regexp&quot;&gt;/postgresql-14.8/&lt;/span&gt;pgsql/data&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;regexp&quot;&gt;/home/&lt;/span&gt;daniel&lt;span class=&quot;regexp&quot;&gt;/postgresql-14.8/&lt;/span&gt;pgsql&lt;span class=&quot;regexp&quot;&gt;/bin/&lt;/span&gt;pg_ctl -D &lt;span class=&quot;regexp&quot;&gt;/home/&lt;/span&gt;daniel&lt;span class=&quot;regexp&quot;&gt;/postgresql-14.8/&lt;/span&gt;pgsql/data -l logfile start&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;setup-PostgreSQL-environment-path&quot;&gt;&lt;a href=&quot;#setup-PostgreSQL-environment-path&quot; class=&quot;headerlink&quot; title=&quot;setup PostgreSQL environment path&quot;&gt;&lt;/a&gt;setup PostgreSQL environment path&lt;/h2&gt;&lt;p&gt;I would recommend setup PostgreSQL environment path, after we build &amp;amp; install PostgreSQL program from source code that would assist with us easier our next steps.&lt;/p&gt;
&lt;p&gt;Identify your shell: Determine which shell you are using by running the following command:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$SHELL&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;It will display the path to your current shell.&lt;/p&gt;
&lt;p&gt;Locate the configuration file: The configuration file you need to modify depends on your shell. Here are the common ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bash: ~&amp;#x2F;.bashrc or ~&amp;#x2F;.bash_profile&lt;/li&gt;
&lt;li&gt;Zsh: ~&amp;#x2F;.zshrc or ~&amp;#x2F;.zprofile&lt;/li&gt;
&lt;li&gt;Fish: ~&amp;#x2F;.config&amp;#x2F;fish&amp;#x2F;config.fish&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Open the configuration file: Use a text editor, such as nano or vim, to open the configuration file. For example, if you are using Bash and the file is &lt;code&gt;~/.bashrc&lt;/code&gt;, you can &lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;vim ~/.bashrc&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;please modify with your environment &lt;code&gt;PGDATA&lt;/code&gt; &amp;amp; &lt;code&gt;PATH&lt;/code&gt; setting align with your PostgreSQL build bin &amp;amp; data path.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;because my build parameter used &lt;code&gt;--prefix=/home/daniel/postgresql-14.8/pgsql&lt;/code&gt; so the setting would be align with that. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;highlight routeros&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;PGDATA&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;/home/daniel/postgresql-14.8/pgsql/data&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;PATH&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;/home/daniel/postgresql-14.8/pgsql/bin:&lt;span class=&quot;variable&quot;&gt;$PATH&lt;/span&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Update the environment: To apply the changes to your current session, run the following command in your terminal:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;source&lt;/span&gt; ~/.bashrc  &lt;span class=&quot;comment&quot;&gt;# or the appropriate file for your shell&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;build-pem-file-to-a-create-user&quot;&gt;&lt;a href=&quot;#build-pem-file-to-a-create-user&quot; class=&quot;headerlink&quot; title=&quot;build pem file to a create user&quot;&gt;&lt;/a&gt;build pem file to a create user&lt;/h2&gt;&lt;p&gt;To create the keys, a preferred command is ssh-keygen, which is available with OpenSSH utilities.&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;ssh-keygen -m PEM -t rsa -b 4096&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;danielss@postgresql-debuger:~$ ssh-keygen -m PEM -t rsa -b 4096&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Generating public/private rsa key pair.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Enter file &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;which&lt;/span&gt; to save the key (/home/danielss/.ssh/id_rsa): /home/danielss/.ssh/danielkey&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Created directory &lt;span class=&quot;string&quot;&gt;&amp;#x27;/home/danielss/.ssh&amp;#x27;&lt;/span&gt;.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Enter passphrase (empty &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; no passphrase):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Enter same passphrase again:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Your identification has been saved &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; /home/danielss/.ssh/danielkey&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Your public key has been saved &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; /home/danielss/.ssh/danielkey.pub&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;When you create an Azure VM by specifying the public key, Azure copies the public key (in the .pub format) to the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; folder on the VM. SSH keys in &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; ensure that connecting clients present the corresponding private key during an SSH connection.&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;danielss@postgresql-debuger:~/.ssh$ ll&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;total 20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;drwx------ 2 danielss danielss 4096 Jun  9 02:51 ./&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;drwxr-xr-x 4 danielss danielss 4096 Jun  9 02:52 ../&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-rw-rw-r-- 1 danielss danielss  753 Jun  9 02:51 authorized_keys&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-rw------- 1 danielss danielss 3247 Jun  9 02:50 danielkey&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-rw-r--r-- 1 danielss danielss  753 Jun  9 02:50 danielkey.pub&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;copy &lt;code&gt;danielkey&lt;/code&gt; (pem file) to your local machine, then you might login via the pem file.&lt;/p&gt;
&lt;h3 id=&quot;permission-problem-on-pem-file&quot;&gt;&lt;a href=&quot;#permission-problem-on-pem-file&quot; class=&quot;headerlink&quot; title=&quot;permission problem on pem file&quot;&gt;&lt;/a&gt;permission problem on pem file&lt;/h3&gt;&lt;p&gt;Confirm that the permissions of the &lt;code&gt;.ssh&lt;/code&gt; directory and the authorized_keys file on the server are set correctly. Run the following commands on the server:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;chmod&lt;/span&gt; 700 ~/.ssh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;chmod&lt;/span&gt; 600 ~/.ssh/authorized_keys&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;These commands ensure that the directory has read, write, and execute permissions only for the user, and the &lt;code&gt;authorized_keys&lt;/code&gt; file has read and write permissions only for the user.&lt;/p&gt;
&lt;h2 id=&quot;setup-vscode&quot;&gt;&lt;a href=&quot;#setup-vscode&quot; class=&quot;headerlink&quot; title=&quot;setup vscode&quot;&gt;&lt;/a&gt;setup vscode&lt;/h2&gt;&lt;p&gt;Please follow below config&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modify “program” column with PostgreSQL binary file path from json file &lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;highlight json&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;0.2.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;attr&quot;&gt;&amp;quot;configurations&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;dbg postgresql&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;cppdbg&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;attr&quot;&gt;&amp;quot;request&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;attach&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;attr&quot;&gt;&amp;quot;program&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;/home/daniel/postgresql-14.8/pgsql/bin/postgres&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;attr&quot;&gt;&amp;quot;processId&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;$&amp;#123;command:pickProcess&amp;#125;&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;attr&quot;&gt;&amp;quot;MIMode&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;gdb&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;attr&quot;&gt;&amp;quot;setupCommands&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;punctuation&quot;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;attr&quot;&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;Enable pretty-printing for gdb&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;attr&quot;&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;-enable-pretty-printing&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;attr&quot;&gt;&amp;quot;ignoreFailures&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;          &lt;span class=&quot;punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;punctuation&quot;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Final used ssh with pem file to login your VM via VsCode.&lt;/p&gt;
&lt;h2 id=&quot;demo&quot;&gt;&lt;a href=&quot;#demo&quot; class=&quot;headerlink&quot; title=&quot;demo&quot;&gt;&lt;/a&gt;demo&lt;/h2&gt;&lt;p&gt;Here is the demo for vscode debug PostgreSQL running process by sourcecode.&lt;/p&gt;
&lt;p&gt;&lt;img data-src=&quot;/../images/pg-debugger/postgres_debuger.gif&quot; alt=&quot;img&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;How-can-solve-“ptrace-Operation-not-permitted”-problem&quot;&gt;&lt;a href=&quot;#How-can-solve-“ptrace-Operation-not-permitted”-problem&quot; class=&quot;headerlink&quot; title=&quot;How can solve “ptrace:Operation not permitted” problem?&quot;&gt;&lt;/a&gt;How can solve “ptrace:Operation not permitted” problem?&lt;/h2&gt;&lt;p&gt;Regarding to &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/microsoft/MIEngine/wiki/Troubleshoot-attaching-to-processes-using-GDB&quot;&gt;Troubleshoot attaching to processes using GDB&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Run the following command as super user: &lt;code&gt;echo 0| sudo tee /proc/sys/kernel/yama/ptrace_scope&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will set the ptrace level to 0, after this just with user permissions you can attach to processes which are not launched by the debugger.&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;postgres@postgresql-debuger:~$ &lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; 0| &lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;tee&lt;/span&gt; /proc/sys/kernel/yama/ptrace_scope&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;[&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt;] password &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; postgres:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;0&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;More information&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/microsoft/MIEngine/wiki/Troubleshoot-attaching-to-processes-using-GDB&quot;&gt;https://github.com/microsoft/MIEngine/wiki/Troubleshoot-attaching-to-processes-using-GDB&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://learn.microsoft.com/en-us/azure/virtual-machines/linux/create-ssh-keys-detailed#overview-of-ssh-and-keys&quot;&gt;https://learn.microsoft.com/en-us/azure/virtual-machines/linux/create-ssh-keys-detailed#overview-of-ssh-and-keys&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/postgresql-vscode-sourcecode-debugger/&quot;&gt;https://isdaniel.github.io/postgresql-vscode-sourcecode-debugger/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Azure PostgreSQL Flexible long term backup with Managed Identity</title>
    <link href="https://isdaniel.github.io/azure-flexible-longterm-backup/"/>
    <id>https://isdaniel.github.io/azure-flexible-longterm-backup/</id>
    <published>2022-12-25T14:30:11.000Z</published>
    <updated>2022-12-25T14:30:11.000Z</updated>
    <author><name>Daniel Shih</name></author>
    <summary>Step-by-step guide to configure long-term backup (beyond 35 days) on Azure PostgreSQL Flexible Server using Managed Identity, pg_dump, and Azure Blob Storage</summary>
    <content type="html">&lt;h2 id=&quot;Introduction&quot;&gt;&lt;a href=&quot;#Introduction&quot; class=&quot;headerlink&quot; title=&quot;Introduction&quot;&gt;&lt;/a&gt;Introduction&lt;/h2&gt;&lt;p&gt;This ariticle will guide us how to do long-term backup (more than 35 days) on Azure PostgreSQL Flexible.&lt;/p&gt;
&lt;h2 id=&quot;Prerequisites&quot;&gt;&lt;a href=&quot;#Prerequisites&quot; class=&quot;headerlink&quot; title=&quot;Prerequisites:&quot;&gt;&lt;/a&gt;Prerequisites:&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;A Postgres flexible server and an Azure VM (Linux (ubuntu 20.04)) that has access to it.&lt;/li&gt;
&lt;li&gt;A MI (Managed Identity) in your subscription.&lt;/li&gt;
&lt;li&gt;Please kindly make sure backup Postgres flexible server version align with pg_dump version.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the sample code of Azure PostgreSQL Flexible long term backup with Managed Identity.&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;start program&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;=====================&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;start installing postgresql client suit&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; apt update&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; apt -y install postgresql-client&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;======================&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;postgresql client install ends&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;start mount storage&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; dpkg -i packages-microsoft-prod.deb&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; apt-get update&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; apt-get install blobfuse -y&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; apt-get install jq -y&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; [ ! -f blob.conf  ]; &lt;span class=&quot;keyword&quot;&gt;then&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;accountName &amp;lt;&amp;lt;Your blob accountName&amp;gt;&amp;gt;&amp;#x27;&lt;/span&gt; &amp;gt;&amp;gt; blob.conf &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;authType MSI&amp;#x27;&lt;/span&gt; &amp;gt;&amp;gt; fuse_connection.cfg&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;identityObjectId &amp;lt;&amp;lt;Your blob accountKey&amp;gt;&amp;gt;&amp;#x27;&lt;/span&gt; &amp;gt;&amp;gt; blob.conf &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;containerName &amp;lt;&amp;lt;Your blob Container Name&amp;gt;&amp;gt;&amp;#x27;&lt;/span&gt; &amp;gt;&amp;gt; blob.conf &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#create a folder which can mount to blob&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;mkdir&lt;/span&gt; ~/data&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; blobfuse ~/data --tmp-path=/mnt/resource/mycontainer  --config-file=./blob.conf -o attr_timeout=240 -o entry_timeout=240 -o negative_timeout=120&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;======= starting backup============&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;DNAME=`&lt;span class=&quot;built_in&quot;&gt;date&lt;/span&gt; +%Y%m%d%H%M%S`&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;export&lt;/span&gt; PGPASSWORD=`curl -s &lt;span class=&quot;string&quot;&gt;&amp;#x27;http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&amp;amp;resource=https%3A%2F%2Fossrdbms-aad.database.windows.net&amp;amp;client_id=&amp;lt;&amp;lt;MI client ID&amp;gt;&amp;gt;&amp;#x27;&lt;/span&gt; -H Metadata:&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt; | jq -r .access_token`&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pg_dump --host=test-conn.postgres.database.azure.com --username=&lt;span class=&quot;string&quot;&gt;&amp;#x27;MI-Demo&amp;#x27;&lt;/span&gt; -Fc -c  testdb &amp;gt; ~/data/dump&lt;span class=&quot;variable&quot;&gt;$DNAME&lt;/span&gt;.sql&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Experiments&quot;&gt;&lt;a href=&quot;#Experiments&quot; class=&quot;headerlink&quot; title=&quot;Experiments:&quot;&gt;&lt;/a&gt;Experiments:&lt;/h2&gt;&lt;h3 id=&quot;Here-is-the-guideline-for-using-managed-identity-to-connect-to-your-DB-server-in-VM&quot;&gt;&lt;a href=&quot;#Here-is-the-guideline-for-using-managed-identity-to-connect-to-your-DB-server-in-VM&quot; class=&quot;headerlink&quot; title=&quot;Here is the guideline for using managed identity to connect to your DB server in VM.&quot;&gt;&lt;/a&gt;Here is the guideline for using managed identity to connect to your DB server in VM.&lt;/h3&gt;&lt;p&gt;We would make sure the Authentication setting that was PostgreSQL and Azure Active Directory authentication.&lt;/p&gt;
&lt;p&gt;&lt;img data-src=&quot;https://i.imgur.com/BVNlWCx.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Selected which managed identity you want to add to the PostgreSQL flexible server.&lt;/p&gt;
&lt;p&gt;&lt;img data-src=&quot;https://i.imgur.com/61LOp1w.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;When we had added managed identity in Azure portal that will add the managed identity in the PostgreSQL server.&lt;/p&gt;
&lt;p&gt;In below script, we need to replace &lt;code&gt;&amp;lt;&amp;lt;MI client ID&amp;gt;&amp;gt;&lt;/code&gt; by your MI Client Id and replace by your login MI as red frames that would tell Azure PostgreSQL server verify MI user by access token instead of user password.&lt;/p&gt;
&lt;p&gt;&lt;img data-src=&quot;https://i.imgur.com/r8IBDz8.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;After we replace all content by your really connect information that I pointed out, we can try to run the script and the backup file would be back up by pg_dump without password.&lt;/p&gt;
&lt;p&gt;&lt;img data-src=&quot;https://i.imgur.com/qhOIDsV.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;Here-is-guideline-to-guide-us-how-to-mount-Linux-VM-from-blob-storage-by-MI&quot;&gt;&lt;a href=&quot;#Here-is-guideline-to-guide-us-how-to-mount-Linux-VM-from-blob-storage-by-MI&quot; class=&quot;headerlink&quot; title=&quot;Here is guideline to guide us how to mount Linux VM from blob storage by MI.&quot;&gt;&lt;/a&gt;Here is guideline to guide us how to mount Linux VM from blob storage by MI.&lt;/h3&gt;&lt;p&gt;If you’re using User assigned managed identity, please add the identity in &lt;code&gt;User assigned&lt;/code&gt; configuration of your Linux VM as shown below (choose which MI you want to use to verify.)&lt;/p&gt;
&lt;p&gt;&lt;img data-src=&quot;https://i.imgur.com/BgKLeeS.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Please following the step “Managed Identities -&amp;gt; Azure role assignments -&amp;gt; Add role assignment (Preview)” to choose which Blob storage you want to mounted and verified by the MI.&lt;/p&gt;
&lt;p&gt;&lt;img data-src=&quot;https://i.imgur.com/1Tpvopj.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Edit the storage account information which mark as red frame from the script which I provided to you.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AccountName: blob Account Name&lt;/li&gt;
&lt;li&gt;AuthType: auth type must be MSI&lt;/li&gt;
&lt;li&gt;IdentityObjectId: Filled with your managed identity object it as below red arrow.&lt;/li&gt;
&lt;li&gt;ContainerName: blob Container Name&lt;br&gt;&lt;img data-src=&quot;https://i.imgur.com/cJLGI1i.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After the execution, you could see the dump file has successfully uploaded to storage account.&lt;/p&gt;
&lt;p&gt;&lt;img data-src=&quot;https://i.imgur.com/ROSZvsp.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;More-information&quot;&gt;&lt;a href=&quot;#More-information&quot; class=&quot;headerlink&quot; title=&quot;More information&quot;&gt;&lt;/a&gt;More information&lt;/h2&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://techcommunity.microsoft.com/t5/azure-paas-blog/mount-blob-storage-on-linux-vm-using-managed-identities-or/ba-p/1821744&quot;&gt;https://techcommunity.microsoft.com/t5/azure-paas-blog/mount-blob-storage-on-linux-vm-using-managed-identities-or/ba-p/1821744&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/Azure/azure-storage-fuse/blob/master/README.md#valid-authentication-setups&quot;&gt;https://github.com/Azure/azure-storage-fuse/blob/master/README.md#valid-authentication-setups&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;此文作者&lt;/strong&gt;：Daniel Shih(石頭)&lt;br /&gt;&lt;strong&gt;此文地址&lt;/strong&gt;： &lt;a href=&quot;https://isdaniel.github.io/azure-flexible-longterm-backup/&quot;&gt;https://isdaniel.github.io/azure-flexible-longterm-backup/&lt;/a&gt; &lt;br /&gt;&lt;strong&gt;版權聲明&lt;/strong&gt;：本博客所有文章除特別聲明外，均採用 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/3.0/tw/&quot;&gt;CC BY-NC-SA 3.0 TW&lt;/a&gt; 許可協議。轉載請註明出處！&lt;/p&gt;
</content>
  </entry>
</feed>
