<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://postsql.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://postsql.github.io/" rel="alternate" type="text/html" /><updated>2026-10-20T22:14:56+00:00</updated><id>https://postsql.github.io/feed.xml</id><title type="html">PostSQL Engineering Blog</title><subtitle>PostgreSQL architecture, performance engineering, and extensions — why and how PostgreSQL is so much more than just an SQL database.</subtitle><author><name>Hannu Krosing</name></author><entry><title type="html">Evolving pgbench into a Universal Database Benchmark Engine: OLTP, HTAP, Property Graphs, and Vector Search</title><link href="https://postsql.github.io/2026/10/19/evolving-pgbench.html" rel="alternate" type="text/html" title="Evolving pgbench into a Universal Database Benchmark Engine: OLTP, HTAP, Property Graphs, and Vector Search" /><published>2026-10-19T12:00:00+00:00</published><updated>2026-10-19T12:00:00+00:00</updated><id>https://postsql.github.io/2026/10/19/evolving-pgbench</id><content type="html" xml:base="https://postsql.github.io/2026/10/19/evolving-pgbench.html"><![CDATA[<ul id="markdown-toc">
  <li><a href="#1-from-an-8000-line-monolith-to-a-modular-architecture" id="markdown-toc-1-from-an-8000-line-monolith-to-a-modular-architecture">1. From an 8,000-Line Monolith to a Modular Architecture</a></li>
  <li><a href="#2-turning-the-scripting-language-into-a-full-workload-dsl" id="markdown-toc-2-turning-the-scripting-language-into-a-full-workload-dsl">2. Turning the Scripting Language into a Full Workload DSL</a></li>
  <li><a href="#3-orchestrating-complex-multi-client-topologies" id="markdown-toc-3-orchestrating-complex-multi-client-topologies">3. Orchestrating Complex Multi-Client Topologies</a></li>
  <li><a href="#4-first-class-telemetry-hdr-latency-recallk-and-index-aging" id="markdown-toc-4-first-class-telemetry-hdr-latency-recallk-and-index-aging">4. First-Class Telemetry: HDR Latency, Recall@K, and Index Aging</a></li>
  <li><a href="#5-eight-built-in-workload-suites-srcbinpgbenchworkloads" id="markdown-toc-5-eight-built-in-workload-suites-srcbinpgbenchworkloads">5. Eight Built-In Workload Suites (<code class="language-plaintext highlighter-rouge">src/bin/pgbench/workloads/</code>)</a></li>
  <li><a href="#6-workload-as-code-jsonjsonc-configuration--replay---config----dump-config" id="markdown-toc-6-workload-as-code-jsonjsonc-configuration--replay---config----dump-config">6. Workload as Code: JSON/JSONC Configuration &amp; Replay (<code class="language-plaintext highlighter-rouge">--config</code> &amp; <code class="language-plaintext highlighter-rouge">--dump-config</code>)</a>    <ul>
      <li><a href="#61-zero-dependency-jsonjsonc-via-libpgcommon" id="markdown-toc-61-zero-dependency-jsonjsonc-via-libpgcommon">6.1 Zero-Dependency JSON/JSONC via <code class="language-plaintext highlighter-rouge">libpgcommon</code></a></li>
      <li><a href="#62-multi-stage-initialization-orchestrator-init_steps" id="markdown-toc-62-multi-stage-initialization-orchestrator-init_steps">6.2 Multi-Stage Initialization Orchestrator (<code class="language-plaintext highlighter-rouge">init_steps[]</code>)</a></li>
      <li><a href="#63-relocatable-paths--two-pass-deterministic-precedence" id="markdown-toc-63-relocatable-paths--two-pass-deterministic-precedence">6.3 Relocatable Paths &amp; Two-Pass Deterministic Precedence</a></li>
    </ul>
  </li>
  <li><a href="#7-seeing-it-in-action" id="markdown-toc-7-seeing-it-in-action">7. Seeing It in Action</a>    <ul>
      <li><a href="#71-single-command-multi-group-property-graph-benchmark" id="markdown-toc-71-single-command-multi-group-property-graph-benchmark">7.1 Single-Command Multi-Group Property Graph Benchmark</a></li>
      <li><a href="#72-single-command-vector-search-with-named-recall-slas" id="markdown-toc-72-single-command-vector-search-with-named-recall-slas">7.2 Single-Command Vector Search with Named Recall SLAs</a></li>
    </ul>
  </li>
  <li><a href="#8-closing-the-telemetry-loop-direct-postgresql-database-logging---log-db--live-wasm-benchmark-viewer" id="markdown-toc-8-closing-the-telemetry-loop-direct-postgresql-database-logging---log-db--live-wasm-benchmark-viewer">8. Closing the Telemetry Loop: Direct PostgreSQL Database Logging (<code class="language-plaintext highlighter-rouge">--log-db</code>) &amp; Live WASM Benchmark Viewer</a>    <ul>
      <li><a href="#81-three-tier-relational-telemetry--unlogged-stream-tables" id="markdown-toc-81-three-tier-relational-telemetry--unlogged-stream-tables">8.1 Three-Tier Relational Telemetry &amp; <code class="language-plaintext highlighter-rouge">UNLOGGED</code> Stream Tables</a></li>
      <li><a href="#82-built-in-sql-run-inspection--regression-comparison-srfs" id="markdown-toc-82-built-in-sql-run-inspection--regression-comparison-srfs">8.2 Built-In SQL Run Inspection &amp; Regression Comparison SRFs</a></li>
      <li><a href="#83-zero-framework-web-viewer-bottlepy--watercss--439-byte-webassembly-projector--metatemplate-sql-explorer" id="markdown-toc-83-zero-framework-web-viewer-bottlepy--watercss--439-byte-webassembly-projector--metatemplate-sql-explorer">8.3 Zero-Framework Web Viewer (<code class="language-plaintext highlighter-rouge">bottle.py</code> + <code class="language-plaintext highlighter-rouge">Water.css</code> + 439-Byte WebAssembly Projector + Metatemplate SQL Explorer)</a></li>
    </ul>
  </li>
  <li><a href="#9-built-for-autonomous-ai-agents-experimental-agent-oriented-documentation--skill" id="markdown-toc-9-built-for-autonomous-ai-agents-experimental-agent-oriented-documentation--skill">9. Built for Autonomous AI Agents: Experimental Agent-Oriented Documentation &amp; Skill</a></li>
</ul>

<p>For over two decades, <code class="language-plaintext highlighter-rouge">pgbench</code> has been the standard tool for quick PostgreSQL performance testing. Its built-in banking transaction (<code class="language-plaintext highlighter-rouge">pgbench_accounts</code>, <code class="language-plaintext highlighter-rouge">pgbench_branches</code>, <code class="language-plaintext highlighter-rouge">pgbench_tellers</code>, <code class="language-plaintext highlighter-rouge">pgbench_history</code>) is simple, fast, and familiar to every PostgreSQL engineer.</p>

<p>However, modern database workloads look very different from a four-table banking loop. Today, PostgreSQL powers:</p>

<ul>
  <li><strong>Complex multi-transaction OLTP</strong> with skewed data distributions, keying/think times, and deliberate business-rule rollbacks;</li>
  <li><strong>Hybrid Transactional/Analytical Processing (HTAP)</strong> where high-throughput operational writers run alongside multi-join analytical query streams;</li>
  <li><strong>Property Graph workloads</strong> with power-law social neighborhoods, temporal financial fraud chains, and causally ordered event streams;</li>
  <li><strong>Vector &amp; RAG workloads</strong> where latency and QPS mean nothing without simultaneously measuring <strong>Recall@K</strong> and <strong>index degradation under continuous write churn</strong>.</li>
</ul>

<p>Historically, testing any of these workloads required spinning up heavy external drivers in Java, Python, or Go—introducing client-side bottlenecks, dependency sprawl, and multi-gigabyte CSV staging steps.</p>

<p>What if <code class="language-plaintext highlighter-rouge">pgbench</code> itself could natively drive all of these workloads at C speed, generate realistic data directly inside PostgreSQL in parallel, and validate both performance and result quality SLAs out of the box?</p>

<p>Here is a look at how we refactored and extended <code class="language-plaintext highlighter-rouge">pgbench</code> into a modular, multi-paradigm benchmarking engine—complete with an in-database <code class="language-plaintext highlighter-rouge">contrib/pgbench</code> extension and <strong>8 ready-to-run workload suites</strong>.</p>

<hr />

<h2 id="1-from-an-8000-line-monolith-to-a-modular-architecture">1. From an 8,000-Line Monolith to a Modular Architecture</h2>

<p>Before adding new capabilities, we needed a clean foundation. Originally, nearly all of <code class="language-plaintext highlighter-rouge">pgbench</code> lived inside a single monolithic file (<code class="language-plaintext highlighter-rouge">pgbench.c</code>). We decomposed the codebase into focused, testable subsystems and extracted shared deterministic PRNG and data-generation primitives into <code class="language-plaintext highlighter-rouge">src/common/pgbench_funcs.c</code>:</p>

<p><img src="/assets/images/pgbench/pgbench_evolution_blog_post_diagram_1.svg" alt="pgbench_evolution_blog_post diagram 1" /></p>

<p>By sharing <code class="language-plaintext highlighter-rouge">pgbench_funcs.c</code> between the <code class="language-plaintext highlighter-rouge">pgbench</code> client binary and the new <code class="language-plaintext highlighter-rouge">contrib/pgbench</code> server extension (<code class="language-plaintext highlighter-rouge">CREATE EXTENSION pgbench;</code>), the exact same deterministic distributions (<code class="language-plaintext highlighter-rouge">random_zipfian</code>, <code class="language-plaintext highlighter-rouge">nurand</code>, <code class="language-plaintext highlighter-rouge">c_last</code>, <code class="language-plaintext highlighter-rouge">random_vector</code>, <code class="language-plaintext highlighter-rouge">ldbc_correlated_edge</code>, <code class="language-plaintext highlighter-rouge">ldbc_flashmob_ts</code>) are available both in client-side <code class="language-plaintext highlighter-rouge">\set</code> expressions and inside parallel <code class="language-plaintext highlighter-rouge">INSERT INTO ... SELECT FROM generate_series(...)</code> server-side data loaders.</p>

<hr />

<h2 id="2-turning-the-scripting-language-into-a-full-workload-dsl">2. Turning the Scripting Language into a Full Workload DSL</h2>

<p>Standard <code class="language-plaintext highlighter-rouge">pgbench</code> scripts only supported 64-bit integers, floating-point numbers, and simple <code class="language-plaintext highlighter-rouge">\if</code> branches. Real-world benchmarks require rich types, loops, error handling, and modular composition:</p>

<ul>
  <li><strong>Rich Native Data Types (<code class="language-plaintext highlighter-rouge">variable.c</code>)</strong>: Variables now natively hold <code class="language-plaintext highlighter-rouge">text</code> strings, <code class="language-plaintext highlighter-rouge">timestamp</code> values, <code class="language-plaintext highlighter-rouge">bytea</code> payloads, PostgreSQL 1-D arrays (<code class="language-plaintext highlighter-rouge">{1,2,3}</code>), and dense <code class="language-plaintext highlighter-rouge">float4</code> vectors (<code class="language-plaintext highlighter-rouge">[0.12,-0.34,...]</code>).</li>
  <li><strong>Control Flow &amp; Exception Handling (<code class="language-plaintext highlighter-rouge">commands.c</code>)</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">\for i = 1 TO :ol_cnt ... \endfor</code> and <code class="language-plaintext highlighter-rouge">\while &lt;cond&gt; ... \endwhile</code> for building multi-item order arrays or iterative graph traversals.</li>
      <li><code class="language-plaintext highlighter-rouge">\try ... \catch ... \endtry</code> and <code class="language-plaintext highlighter-rouge">\expect_error &lt;sqlstate&gt;</code> for gracefully handling expected business-rule rollbacks (such as invalid item IDs or financial fraud aborts) and tracking them separately as <strong>expected rollbacks</strong> rather than benchmark failures.</li>
    </ul>
  </li>
  <li><strong>Sub-Script Composition (<code class="language-plaintext highlighter-rouge">\call</code>)</strong>: Complex workflows can chain reusable sub-scripts via <code class="language-plaintext highlighter-rouge">\call sub_script.sql param1=:val1</code>, with automatic lexical variable scoping and a 16-frame call-stack depth guard.</li>
  <li><strong>Array-Aware Query Capture (<code class="language-plaintext highlighter-rouge">\gset_col</code> &amp; <code class="language-plaintext highlighter-rouge">\gset_meta</code>)</strong>: While <code class="language-plaintext highlighter-rouge">\gset</code> captures columns from a single row, <code class="language-plaintext highlighter-rouge">\gset_col</code> collects an entire result-set column across multiple rows (e.g., <code class="language-plaintext highlighter-rouge">LIMIT 10</code>) into a single <code class="language-plaintext highlighter-rouge">PGBT_ARRAY</code> variable—essential for feeding multi-hop graph discoveries or top-\(K\) vector neighbor IDs into downstream commands.</li>
  <li><strong>Realistic User Pacing</strong>: <code class="language-plaintext highlighter-rouge">\keying_time</code> and <code class="language-plaintext highlighter-rouge">\think_time</code> implement truncated negative-exponential delays using the non-blocking client state machine, allowing thousands of simulated terminals to sleep without blocking OS worker threads.</li>
</ul>

<hr />

<h2 id="3-orchestrating-complex-multi-client-topologies">3. Orchestrating Complex Multi-Client Topologies</h2>

<p>Modern benchmarks rarely run a single homogeneous loop. We added four major orchestration primitives to <code class="language-plaintext highlighter-rouge">engine.c</code> and <code class="language-plaintext highlighter-rouge">dataset.c</code>:</p>

<ol>
  <li><strong>Heterogeneous Client Groups (<code class="language-plaintext highlighter-rouge">--group</code>)</strong>:
A single <code class="language-plaintext highlighter-rouge">pgbench</code> invocation can now partition clients into independent workload groups—for example, running 16 high-rate OLTP clients alongside 4 sequential OLAP reporting streams and 1 rate-limited refresh stream, each with its own <code class="language-plaintext highlighter-rouge">--sla</code> latency and throughput contracts.</li>
  <li><strong>Lock-Free Cross-Client Dynamic Reservoirs (<code class="language-plaintext highlighter-rouge">--reservoir</code>)</strong>:
In read/write graph and vector benchmarks, readers need to query entities that were <em>just</em> inserted by concurrent writers, without running expensive <code class="language-plaintext highlighter-rouge">SELECT id FROM tbl ORDER BY random() LIMIT 1</code> queries. Writers push newly created IDs via <code class="language-plaintext highlighter-rouge">\reservoir_push</code>, and readers sample or pop them in \(O(1)\) time via <code class="language-plaintext highlighter-rouge">\reservoir_sample</code> / <code class="language-plaintext highlighter-rouge">\reservoir_pop</code> using lock-free atomic Algorithm R reservoir sampling.</li>
  <li><strong>Causal Dependency Watermarks (<code class="language-plaintext highlighter-rouge">\begin_dependent_write</code> / <code class="language-plaintext highlighter-rouge">\wait_for_watermark</code>)</strong>:
When multiple writer threads insert timestamped graph events out of wall-clock order, a lock-free <strong>Multi-Writer Min-Watermark Gatekeeper</strong> tracks the safe global lower-bound timestamp across all active writers so readers never observe half-written causal dependencies.</li>
  <li><strong>Phased Execution Barriers (<code class="language-plaintext highlighter-rouge">\phase_barrier</code>)</strong>:
Analytical benchmarks often require strict phase transitions (e.g., <em>Power Run</em> \(\rightarrow\) <em>Micro-Batch Refresh</em> \(\rightarrow\) <em>Throughput Run</em>). <code class="language-plaintext highlighter-rouge">\phase_barrier &lt;name&gt;</code> synchronizes all worker threads at a generation-counted pthread barrier before advancing to the next phase.</li>
</ol>

<hr />

<h2 id="4-first-class-telemetry-hdr-latency-recallk-and-index-aging">4. First-Class Telemetry: HDR Latency, Recall@K, and Index Aging</h2>

<p>Throughput alone can be deceptive—especially in approximate nearest-neighbor (ANN) vector search, where an index can achieve 10,000 QPS by returning completely wrong neighbors, or gradually lose accuracy as updates accumulate.</p>

<p>We upgraded <code class="language-plaintext highlighter-rouge">stats.c</code> with three telemetry engines:</p>

<ul>
  <li><strong>HDR Latency &amp; Schedule-Lag Percentiles</strong>: Every script and client group tracks <code class="language-plaintext highlighter-rouge">p50</code>, <code class="language-plaintext highlighter-rouge">p90</code>, <code class="language-plaintext highlighter-rouge">p95</code>, <code class="language-plaintext highlighter-rouge">p99</code>, and <code class="language-plaintext highlighter-rouge">p99.9</code> latency, plus open-loop schedule lag percentiles (<code class="language-plaintext highlighter-rouge">--rate</code> / <code class="language-plaintext highlighter-rouge">--tcr</code>) and on-time compliance percentages.</li>
  <li><strong>In-Process Recall@K &amp; Named Quality Metrics (<code class="language-plaintext highlighter-rouge">\record_recall</code>)</strong>:
Scripts compare retrieved neighbor arrays against precomputed exact ground-truth arrays directly in C (<code class="language-plaintext highlighter-rouge">\record_recall :retrieved :gt 10 r_prefilter</code>), recording mean recall, <code class="language-plaintext highlighter-rouge">p5</code>/<code class="language-plaintext highlighter-rouge">p50</code> tail recall, and a 10-bucket distribution histogram (<code class="language-plaintext highlighter-rouge">[0.0-0.1)</code> through <code class="language-plaintext highlighter-rouge">[0.9-1.0]</code>).</li>
  <li><strong>Zero-Allocation Sliding-Window Recall Degradation</strong>:
During continuous vector ingestion and churn, <code class="language-plaintext highlighter-rouge">pgbench</code> maintains fixed-size 256-sample circular ring buffers (<code class="language-plaintext highlighter-rouge">early_ring</code> and <code class="language-plaintext highlighter-rouge">recent_ring</code>) to compare <strong>Initial Recall (\(Q_1\))</strong> against <strong>Mature Recall (\(Q_4\))</strong>, quantifying index degradation (<code class="language-plaintext highlighter-rouge">delta</code> and <code class="language-plaintext highlighter-rouge">degradation_pct</code>) with zero heap allocations on the hot path.</li>
  <li><strong>CI/CD SLA Gating (<code class="language-plaintext highlighter-rouge">--sla</code>) &amp; <code class="language-plaintext highlighter-rouge">--json-report</code></strong>:
Any metric—latency percentiles, TPS, schedule lag, result validation failures, or named recall (<code class="language-plaintext highlighter-rouge">--sla="total:r_prefilter:recall&gt;=0.90"</code>)—can be enforced as a pass/fail gate that exits with code <code class="language-plaintext highlighter-rouge">2</code> on violation and emits a structured JSON report.</li>
</ul>

<hr />

<h2 id="5-eight-built-in-workload-suites-srcbinpgbenchworkloads">5. Eight Built-In Workload Suites (<code class="language-plaintext highlighter-rouge">src/bin/pgbench/workloads/</code>)</h2>

<p>Using these primitives, we built eight self-contained workload suites under <code class="language-plaintext highlighter-rouge">src/bin/pgbench/workloads/</code>—all using parallel in-database data generation with zero external data files required:</p>

<table>
  <thead>
    <tr>
      <th>Workload Suite</th>
      <th>Inspiration / Domain</th>
      <th>Highlights</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">pgbench-b</code></strong></td>
      <td>Stored-Procedure Banking OLTP</td>
      <td>Server-side PL/pgSQL <code class="language-plaintext highlighter-rouge">CALL</code> variant of the classic banking benchmark to measure network round-trip reduction.</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">pgbench-c</code></strong></td>
      <td>Retail Order-Entry OLTP <em>(modeled after TPC-C)</em></td>
      <td>9 tables, 5 weighted transactions (<code class="language-plaintext highlighter-rouge">new_order</code>, <code class="language-plaintext highlighter-rouge">payment</code>, <code class="language-plaintext highlighter-rouge">order_status</code>, <code class="language-plaintext highlighter-rouge">delivery</code>, <code class="language-plaintext highlighter-rouge">stock_level</code>), NURand skew, and 1% expected rollbacks.</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">pgbench-e</code></strong></td>
      <td>Financial Brokerage OLTP <em>(modeled after TPC-E)</em></td>
      <td>16 tables, 10 stored procedures, customer positions, market watch, and trade-order/result execution.</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">pgbench-h</code></strong></td>
      <td>Decision Support OLAP <em>(modeled after TPC-H)</em></td>
      <td>8 tables, 8 analytical queries (<code class="language-plaintext highlighter-rouge">Q1</code>, <code class="language-plaintext highlighter-rouge">Q3</code>, <code class="language-plaintext highlighter-rouge">Q5</code>, <code class="language-plaintext highlighter-rouge">Q6</code>, <code class="language-plaintext highlighter-rouge">Q10</code>, <code class="language-plaintext highlighter-rouge">Q12</code>, <code class="language-plaintext highlighter-rouge">Q14</code>, <code class="language-plaintext highlighter-rouge">Q19</code>), and concurrent <code class="language-plaintext highlighter-rouge">RF1</code>/<code class="language-plaintext highlighter-rouge">RF2</code> refresh streams.</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">pgbench-ds</code></strong></td>
      <td>Multi-Channel Retail OLAP <em>(modeled after TPC-DS)</em></td>
      <td>13 star-schema fact/dimension tables, window-function cross-channel rollups, and fact-table maintenance.</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">pgbench-htap</code></strong></td>
      <td>Mixed OLTP + OLAP <em>(modeled after CH-benCHmark)</em></td>
      <td>Runs <code class="language-plaintext highlighter-rouge">pgbench-c</code> operational transactions concurrently against <code class="language-plaintext highlighter-rouge">pgbench-h</code>-style analytical queries over the same live tables.</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">pgbench-graph</code></strong> &amp; <strong><code class="language-plaintext highlighter-rouge">pgbench-graph-pgq</code></strong></td>
      <td>Property Graphs <em>(inspired by LDBC SNB, FinBench, LSQB, Graphalytics)</em></td>
      <td>Covers LSQB (<code class="language-plaintext highlighter-rouge">Q1</code>–<code class="language-plaintext highlighter-rouge">Q9</code>), SNB Interactive v1/v2 (<code class="language-plaintext highlighter-rouge">IS</code>, <code class="language-plaintext highlighter-rouge">IC</code>, <code class="language-plaintext highlighter-rouge">IU</code>, <code class="language-plaintext highlighter-rouge">ID</code> cascading deletes), phased SNB BI, FinBench fraud detection, and Graphalytics (<code class="language-plaintext highlighter-rouge">BFS</code>, <code class="language-plaintext highlighter-rouge">WCC</code>, <code class="language-plaintext highlighter-rouge">SSSP</code>, <code class="language-plaintext highlighter-rouge">PageRank</code>, <code class="language-plaintext highlighter-rouge">LCC</code>)—available in both <strong>relational SQL</strong> (<code class="language-plaintext highlighter-rouge">pgbench-graph</code>) and <strong>ISO SQL/PGQ <code class="language-plaintext highlighter-rouge">GRAPH_TABLE</code></strong> (<code class="language-plaintext highlighter-rouge">pgbench-graph-pgq</code>)!</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">pgbench-vec</code></strong></td>
      <td>Vector Similarity &amp; RAG <em>(inspired by VecBench)</em></td>
      <td>Covers all 6 vector operational modes: Unfiltered Top-\(K\), Pre-Filtered Hybrid, Post-Filtered Oversampling, Streaming Ingestion, Multi-Vector Score Fusion, and Continuous Aging &amp; Degradation.</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>[!NOTE]
<strong>Fair-Use &amp; Trademark Attribution</strong>: All built-in suites are custom PostgreSQL workloads inspired by or modeled after published industry and academic specifications (TPC, LDBC, CH-benCHmark, VecBench). Metrics produced by these suites are for PostgreSQL engineering, regression testing, and comparative tuning, and are not official certified benchmark results.</p>
</blockquote>

<hr />

<h2 id="6-workload-as-code-jsonjsonc-configuration--replay---config----dump-config">6. Workload as Code: JSON/JSONC Configuration &amp; Replay (<code class="language-plaintext highlighter-rouge">--config</code> &amp; <code class="language-plaintext highlighter-rouge">--dump-config</code>)</h2>

<p>While modular execution groups, dynamic reservoirs, and vector telemetry made <code class="language-plaintext highlighter-rouge">pgbench</code> extraordinarily capable, running an advanced workload initially required a 20-flag command line (<code class="language-plaintext highlighter-rouge">-c</code>, <code class="language-plaintext highlighter-rouge">-j</code>, <code class="language-plaintext highlighter-rouge">-t</code>, <code class="language-plaintext highlighter-rouge">-D</code>, <code class="language-plaintext highlighter-rouge">-f</code>, <code class="language-plaintext highlighter-rouge">--group</code>, <code class="language-plaintext highlighter-rouge">--reservoir</code>, <code class="language-plaintext highlighter-rouge">--sla</code>, <code class="language-plaintext highlighter-rouge">--dataset</code>) and a brittle 3-stage shell script (<code class="language-plaintext highlighter-rouge">schema.sql</code> \(\rightarrow\) parallel <code class="language-plaintext highlighter-rouge">init.sql</code> \(\rightarrow\) <code class="language-plaintext highlighter-rouge">constraints.sql</code>). Furthermore, relative SQL script paths resolved relative to the caller’s working directory (<code class="language-plaintext highlighter-rouge">$PWD</code>), forcing users to <code class="language-plaintext highlighter-rouge">cd</code> into workload subdirectories.</p>

<p>To eliminate this operational friction, <code class="language-plaintext highlighter-rouge">config.c</code> introduces native declarative workload configuration (<code class="language-plaintext highlighter-rouge">--config=&lt;file&gt;</code>) and configuration export (<code class="language-plaintext highlighter-rouge">--dump-config=&lt;file|-&gt;</code>):</p>

<h3 id="61-zero-dependency-jsonjsonc-via-libpgcommon">6.1 Zero-Dependency JSON/JSONC via <code class="language-plaintext highlighter-rouge">libpgcommon</code></h3>
<p>PostgreSQL frontend tools avoid heavy external dependencies like <code class="language-plaintext highlighter-rouge">libyaml</code>. We built a lightweight DOM parser on top of PostgreSQL’s core <code class="language-plaintext highlighter-rouge">common/jsonapi.h</code> engine in <code class="language-plaintext highlighter-rouge">libpgcommon</code>. Before tokenization, a single-pass JSONC preprocessor strips <code class="language-plaintext highlighter-rouge">//</code>, <code class="language-plaintext highlighter-rouge">#</code>, and <code class="language-plaintext highlighter-rouge">/* ... */</code> comments as well as trailing commas in arrays and objects, while preserving every newline character (<code class="language-plaintext highlighter-rouge">\n</code>) verbatim. As a result, syntax or semantic errors cite the exact 1-based line number in the user’s <code class="language-plaintext highlighter-rouge">.pgbench.json</code> file.</p>

<h3 id="62-multi-stage-initialization-orchestrator-init_steps">6.2 Multi-Stage Initialization Orchestrator (<code class="language-plaintext highlighter-rouge">init_steps[]</code>)</h3>
<p>Workload schemas, parallel in-database data loaders, and index/constraint builds can now be codified directly in the configuration file:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"init_steps"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"schema"</span><span class="p">,</span><span class="w"> </span><span class="nl">"file"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pgbench_c_schema.sql"</span><span class="w"> </span><span class="p">},</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"data"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"file"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pgbench_c_init.sql"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"clients"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"threads"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"transactions"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"variables"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_workers"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"constraints"</span><span class="p">,</span><span class="w"> </span><span class="nl">"file"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pgbench_c_constraints.sql"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p>Running <code class="language-plaintext highlighter-rouge">pgbench --config=workloads/pgbench-c/pgbench_c.pgbench.json -i</code> executes each initialization phase with its own concurrency and variable overrides in a single invocation, with fail-fast validation.</p>

<h3 id="63-relocatable-paths--two-pass-deterministic-precedence">6.3 Relocatable Paths &amp; Two-Pass Deterministic Precedence</h3>
<ul>
  <li><strong>Relocatable Configurations</strong>: All relative script files (<code class="language-plaintext highlighter-rouge">.sql</code>) and binary vector datasets (<code class="language-plaintext highlighter-rouge">.fvecs</code>, <code class="language-plaintext highlighter-rouge">.ivecs</code>) automatically resolve relative to the directory containing the configuration file (<code class="language-plaintext highlighter-rouge">dirname(config)</code>). Configs can be invoked from anywhere in the filesystem.</li>
  <li>
    <p><strong>Deterministic Precedence</strong>: <code class="language-plaintext highlighter-rouge">pgbench</code> implements a two-pass resolution model where built-in defaults are overridden by <code class="language-plaintext highlighter-rouge">--config</code> files in appearance order, which in turn are <strong>always overridden by explicit CLI flags</strong> regardless of flag positioning:</p>

\[\text{Built-in Defaults} \quad&lt;\quad \text{--config file(s)} \quad&lt;\quad \text{Explicit CLI Flags } (-c, -T, -D, \text{--sla})\]
  </li>
  <li><strong>Input/Output Symmetry</strong>: Any <code class="language-plaintext highlighter-rouge">--json-report</code> file can be fed straight back into <code class="language-plaintext highlighter-rouge">pgbench --config=out.json</code> to reproduce the exact benchmark run. Passing <code class="language-plaintext highlighter-rouge">--dump-config=&lt;file&gt;</code> (or <code class="language-plaintext highlighter-rouge">--dump-config=-</code> for <code class="language-plaintext highlighter-rouge">stdout</code>) serializes the fully merged effective configuration as clean JSON and exits immediately without requiring a database connection.</li>
  <li><strong>28 Bundled Workload Configs</strong>: Ready-to-run <code class="language-plaintext highlighter-rouge">.pgbench.json</code> files are included for all built-in workloads under <code class="language-plaintext highlighter-rouge">src/bin/pgbench/workloads/</code> across relational (<code class="language-plaintext highlighter-rouge">pgbench-b</code> through <code class="language-plaintext highlighter-rouge">pgbench-ds</code>), HTAP (<code class="language-plaintext highlighter-rouge">pgbench-htap</code>), graph (<code class="language-plaintext highlighter-rouge">pgbench-graph</code>, <code class="language-plaintext highlighter-rouge">pgbench-graph-sql-ud</code>, <code class="language-plaintext highlighter-rouge">pgbench-graph-pgq</code>, <code class="language-plaintext highlighter-rouge">pgbench-graph-plpgq</code>), and vector (<code class="language-plaintext highlighter-rouge">pgbench-vec</code>) suites.</li>
</ul>

<hr />

<h2 id="7-seeing-it-in-action">7. Seeing It in Action</h2>

<p>With <code class="language-plaintext highlighter-rouge">--config</code>, running full multi-group, multi-phase benchmarks collapses from sprawling shell scripts into single, clean commands.</p>

<h3 id="71-single-command-multi-group-property-graph-benchmark">7.1 Single-Command Multi-Group Property Graph Benchmark</h3>

<p>Instead of running four separate shell commands, initializing and driving the concurrent reader/writer LDBC-inspired SNB Interactive graph benchmark is now a one-liner:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1. Multi-Stage In-Database Graph Initialization (Schema -&gt; 4-Worker Parallel Gen -&gt; Indexes)</span>
pgbench <span class="nt">--config</span><span class="o">=</span>workloads/pgbench-graph/snb_interactive.pgbench.json <span class="nt">-i</span>

<span class="c"># 2. Run Concurrent Reader &amp; Writer Execution Groups with Shared Reservoirs &amp; SLAs</span>
pgbench <span class="nt">--config</span><span class="o">=</span>workloads/pgbench-graph/snb_interactive.pgbench.json

<span class="c"># 3. On-the-fly CLI Overrides (scale clients or duration without modifying the JSON file)</span>
pgbench <span class="nt">--config</span><span class="o">=</span>workloads/pgbench-graph/snb_interactive.pgbench.json <span class="nt">-T</span> 120 <span class="nt">--sla</span><span class="o">=</span><span class="s2">"total:p95&lt;=25ms"</span>
</code></pre></div></div>

<p>Under the hood, <code class="language-plaintext highlighter-rouge">snb_interactive.pgbench.json</code> declares the multi-stage pipeline, shared key reservoirs (<code class="language-plaintext highlighter-rouge">persons:4096</code>, <code class="language-plaintext highlighter-rouge">messages:8192</code>), the <code class="language-plaintext highlighter-rouge">snb_writers</code> and <code class="language-plaintext highlighter-rouge">snb_readers</code> groups, and SLA rules. When executed manually or inspecting the raw CLI equivalence, it maps to:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pgbench <span class="nt">-n</span> <span class="nt">-T</span> 60 <span class="nt">-s</span> 1 <span class="se">\</span>
        <span class="nt">--reservoir</span><span class="o">=</span>persons:4096 <span class="nt">--reservoir</span><span class="o">=</span>messages:8192 <span class="se">\</span>
        <span class="nt">--group</span><span class="o">=</span><span class="s2">"name=snb_writers,clients=2,threads=2,rate=100,file=workloads/pgbench-graph/pgbench_ldbc_snb_iu.sql@80,file=workloads/pgbench-graph/pgbench_ldbc_snb_id.sql@20"</span> <span class="se">\</span>
        <span class="nt">--group</span><span class="o">=</span><span class="s2">"name=snb_readers,clients=8,threads=4,file=workloads/pgbench-graph/pgbench_ldbc_snb_ic.sql@30,file=workloads/pgbench-graph/pgbench_ldbc_snb_is.sql@70"</span> <span class="se">\</span>
        <span class="nt">--sla</span><span class="o">=</span><span class="s2">"snb_writers:p95&lt;=15ms"</span> <span class="se">\</span>
        <span class="nt">--sla</span><span class="o">=</span><span class="s2">"snb_readers:p95&lt;=50ms"</span> <span class="se">\</span>
        <span class="nt">--sla</span><span class="o">=</span><span class="s2">"total:validation_failures&lt;=0"</span> <span class="se">\</span>
        <span class="nt">--json-report</span><span class="o">=</span>graph_snb_interactive.json
</code></pre></div></div>

<h3 id="72-single-command-vector-search-with-named-recall-slas">7.2 Single-Command Vector Search with Named Recall SLAs</h3>

<p>Similarly, initializing and executing the hybrid vector benchmark with named Recall@K contracts is completely streamlined:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1. Multi-Stage Vector Init (Schema -&gt; Clustered Vector Gen -&gt; Constraints -&gt; Ground Truth)</span>
pgbench <span class="nt">--config</span><span class="o">=</span>workloads/pgbench-vec/vec_hybrid.pgbench.json <span class="nt">-i</span>

<span class="c"># 2. Execute Hybrid Filtered + Multi-Vector + Streaming Ingestion with Named Recall SLAs</span>
pgbench <span class="nt">--config</span><span class="o">=</span>workloads/pgbench-vec/vec_hybrid.pgbench.json

<span class="c"># 3. Export Effective Merged Configuration to stdout or file</span>
pgbench <span class="nt">--config</span><span class="o">=</span>workloads/pgbench-vec/vec_hybrid.pgbench.json <span class="nt">-c</span> 8 <span class="nt">-T</span> 120 <span class="nt">--dump-config</span><span class="o">=</span>-
</code></pre></div></div>

<p>Or executed via the equivalent direct CLI flags:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pgbench <span class="nt">-n</span> <span class="nt">-c</span> 4 <span class="nt">-j</span> 2 <span class="nt">-T</span> 60 <span class="nt">-D</span> <span class="nv">scale</span><span class="o">=</span>1 <span class="nt">--reservoir</span><span class="o">=</span>vec_ids:1024 <span class="se">\</span>
        <span class="nt">-f</span> workloads/pgbench-vec/pgbench_vec_search.sql@30 <span class="se">\</span>
        <span class="nt">-f</span> workloads/pgbench-vec/pgbench_vec_filtered.sql@25 <span class="se">\</span>
        <span class="nt">-f</span> workloads/pgbench-vec/pgbench_vec_postfiltered.sql@20 <span class="se">\</span>
        <span class="nt">-f</span> workloads/pgbench-vec/pgbench_vec_multivec.sql@15 <span class="se">\</span>
        <span class="nt">-f</span> workloads/pgbench-vec/pgbench_vec_insert.sql@10 <span class="se">\</span>
        <span class="nt">--sla</span><span class="o">=</span><span class="s2">"total:r_unfiltered:recall&gt;=0.95"</span> <span class="se">\</span>
        <span class="nt">--sla</span><span class="o">=</span><span class="s2">"total:r_prefilter:recall&gt;=0.90"</span> <span class="se">\</span>
        <span class="nt">--sla</span><span class="o">=</span><span class="s2">"total:r_multivec:recall&gt;=0.85"</span> <span class="se">\</span>
        <span class="nt">--sla</span><span class="o">=</span><span class="s2">"total:p95&lt;=50ms"</span> <span class="se">\</span>
        <span class="nt">--json-report</span><span class="o">=</span>vec_benchmark.json
</code></pre></div></div>

<hr />

<h2 id="8-closing-the-telemetry-loop-direct-postgresql-database-logging---log-db--live-wasm-benchmark-viewer">8. Closing the Telemetry Loop: Direct PostgreSQL Database Logging (<code class="language-plaintext highlighter-rouge">--log-db</code>) &amp; Live WASM Benchmark Viewer</h2>

<p>Even with <code class="language-plaintext highlighter-rouge">--json-report</code> and <code class="language-plaintext highlighter-rouge">-l</code> per-transaction log files, managing historical benchmark runs across parameter sweeps or CI/CD branches remained painful: teams still had to scrape <code class="language-plaintext highlighter-rouge">pgbench_log.&lt;pid&gt;.&lt;tid&gt;</code> flat files, stitch together shell scripts to load <code class="language-plaintext highlighter-rouge">COPY</code> streams into external tables, and maintain ad-hoc Python notebooks just to compare two runs.</p>

<p>With <code class="language-plaintext highlighter-rouge">logdb.c</code>, <code class="language-plaintext highlighter-rouge">pgbench</code> now supports <strong>first-class direct PostgreSQL database logging and live telemetry</strong> (<code class="language-plaintext highlighter-rouge">--log-db=&lt;conninfo|self&gt;</code>, detailed in <a href="/pgbench/pgbench_db_logging_design.html"><code class="language-plaintext highlighter-rouge">pgbench_db_logging_design.md</code></a> and <a href="/pgbench/pgbench_viewer_design.html"><code class="language-plaintext highlighter-rouge">pgbench_viewer_design.md</code></a>):</p>

<h3 id="81-three-tier-relational-telemetry--unlogged-stream-tables">8.1 Three-Tier Relational Telemetry &amp; <code class="language-plaintext highlighter-rouge">UNLOGGED</code> Stream Tables</h3>
<p>Passing <code class="language-plaintext highlighter-rouge">--log-db=self</code> (to log into the benchmarked database itself) or <code class="language-plaintext highlighter-rouge">--log-db="host=metrics-db dbname=perf"</code> automatically provisions a 9-table, 3-tier schema (defaulting to <code class="language-plaintext highlighter-rouge">public</code>, or customized via <code class="language-plaintext highlighter-rouge">--log-db-schema=&lt;schema&gt;</code>):</p>

<ul>
  <li><strong>Tier A — Run Metadata &amp; Final Summary Tables (<code class="language-plaintext highlighter-rouge">LOGGED</code> by default)</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">pgbench_runs</code>: Complete run catalog (<code class="language-plaintext highlighter-rouge">run_id</code>, <code class="language-plaintext highlighter-rouge">run_label</code>, <code class="language-plaintext highlighter-rouge">status</code>, <code class="language-plaintext highlighter-rouge">config_jsonb</code>, <code class="language-plaintext highlighter-rouge">report_jsonb</code>, overall TPS, HDR latency percentiles <code class="language-plaintext highlighter-rouge">p50</code>/<code class="language-plaintext highlighter-rouge">p90</code>/<code class="language-plaintext highlighter-rouge">p95</code>/<code class="language-plaintext highlighter-rouge">p99</code>/<code class="language-plaintext highlighter-rouge">p99.9</code>, schedule lag, <code class="language-plaintext highlighter-rouge">sla_all_passed</code>, and <code class="language-plaintext highlighter-rouge">validation_passed</code>/<code class="language-plaintext highlighter-rouge">failed</code>).</li>
      <li><code class="language-plaintext highlighter-rouge">pgbench_run_groups</code>, <code class="language-plaintext highlighter-rouge">pgbench_run_scripts</code>, <code class="language-plaintext highlighter-rouge">pgbench_run_commands</code>, <code class="language-plaintext highlighter-rouge">pgbench_run_recalls</code>, and <code class="language-plaintext highlighter-rouge">pgbench_run_slas</code>: Normalized breakdowns for multi-group workloads, per-script percentiles, per-command latencies (<code class="language-plaintext highlighter-rouge">-r</code>), Recall@K histograms + <code class="language-plaintext highlighter-rouge">Q1</code>-vs-<code class="language-plaintext highlighter-rouge">Q4</code> aging degradation, and SLA contract evaluations.</li>
    </ul>
  </li>
  <li><strong>Tier B — Live Time-Series Progress &amp; Interval Aggregation (<code class="language-plaintext highlighter-rouge">UNLOGGED</code> by default)</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">pgbench_progress_log</code> (<code class="language-plaintext highlighter-rouge">-P</code>) and <code class="language-plaintext highlighter-rouge">pgbench_agg_log</code> (<code class="language-plaintext highlighter-rouge">--aggregate-interval</code>), streamed over dedicated <code class="language-plaintext highlighter-rouge">COPY ... FROM STDIN</code> connections and committed after every progress/aggregation tick so external dashboards see live time-series curves while the benchmark is still running.</li>
    </ul>
  </li>
  <li><strong>Tier C — Per-Transaction &amp; Per-Query Microsecond Trace (<code class="language-plaintext highlighter-rouge">UNLOGGED</code> by default)</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">pgbench_tx_log</code> (<code class="language-plaintext highlighter-rouge">-l</code>), streamed via buffered <code class="language-plaintext highlighter-rouge">COPY ... FROM STDIN</code> (<code class="language-plaintext highlighter-rouge">--log-db-mode=stream</code>, periodically committing every 64 KB or 1 second for live visibility) or spooled to local disk and bulk-loaded at teardown (<code class="language-plaintext highlighter-rouge">--log-db-mode=deferred</code> for zero mid-run network/CPU perturbation).</li>
    </ul>
  </li>
  <li><strong>Configurable WAL Persistence (<code class="language-plaintext highlighter-rouge">--log-db-unlogged=stream|all|off</code>)</strong>:
By default (<code class="language-plaintext highlighter-rouge">stream</code>), high-rate Tier B/C tables are created as <code class="language-plaintext highlighter-rouge">UNLOGGED</code> to avoid polluting WAL write amplification during the benchmark, while Tier A summary catalogs remain crash-safe <code class="language-plaintext highlighter-rouge">LOGGED</code> tables.</li>
</ul>

<h3 id="82-built-in-sql-run-inspection--regression-comparison-srfs">8.2 Built-In SQL Run Inspection &amp; Regression Comparison SRFs</h3>
<p>Alongside the telemetry tables, <code class="language-plaintext highlighter-rouge">logdb.c</code> installs <strong>2 views</strong> (<code class="language-plaintext highlighter-rouge">pgbench_runs_v</code>, <code class="language-plaintext highlighter-rouge">pgbench_live_progress_v</code>) and <strong>6 SQL functions</strong> directly in the logging schema—allowing engineers to inspect or compare runs from <code class="language-plaintext highlighter-rouge">psql</code> without any external tooling:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 1. Pretty-print the most recent run (or pass a specific run_id or run_label)</span>
<span class="k">SELECT</span> <span class="n">section</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">metric</span><span class="p">,</span> <span class="n">value</span> <span class="k">FROM</span> <span class="n">pgbench_show_run</span><span class="p">();</span>

<span class="c1">-- 2. Pairwise baseline-vs-candidate regression diff (by run_id or run_label)</span>
<span class="c1">--    Automatically computes delta, delta_pct, and IMPROVEMENT / REGRESSION verdicts!</span>
<span class="k">SELECT</span> <span class="n">category</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">metric</span><span class="p">,</span> <span class="n">baseline_val</span><span class="p">,</span> <span class="n">candidate_val</span><span class="p">,</span> <span class="n">delta_pct</span><span class="p">,</span> <span class="n">status</span>
  <span class="k">FROM</span> <span class="n">pgbench_compare_runs</span><span class="p">(</span><span class="s1">'baseline-hnsw-m16'</span><span class="p">,</span> <span class="s1">'candidate-hnsw-m32'</span><span class="p">);</span>

<span class="c1">-- 3. N-way side-by-side comparison matrix across an array of run_ids</span>
<span class="k">SELECT</span> <span class="n">category</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">metric</span><span class="p">,</span> <span class="n">values_by_run</span><span class="p">,</span> <span class="n">pct_vs_first</span>
  <span class="k">FROM</span> <span class="n">pgbench_compare_runs</span><span class="p">(</span><span class="n">ARRAY</span><span class="p">[</span><span class="mi">101</span><span class="p">,</span> <span class="mi">102</span><span class="p">,</span> <span class="mi">103</span><span class="p">]::</span><span class="nb">bigint</span><span class="p">[]);</span>
</code></pre></div></div>

<h3 id="83-zero-framework-web-viewer-bottlepy--watercss--439-byte-webassembly-projector--metatemplate-sql-explorer">8.3 Zero-Framework Web Viewer (<code class="language-plaintext highlighter-rouge">bottle.py</code> + <code class="language-plaintext highlighter-rouge">Water.css</code> + 439-Byte WebAssembly Projector + Metatemplate SQL Explorer)</h3>
<p>To visualize both completed and <strong>currently running</strong> benchmarks without pulling in React, Node.js, or heavy charting bundles, <code class="language-plaintext highlighter-rouge">src/bin/pgbench/viewer/pgbench_viewer.py</code> (documented in <code class="language-plaintext highlighter-rouge">src/bin/pgbench/viewer/README.md</code>) provides a self-contained web UI built on four minimalist pillars:</p>

<ol>
  <li><strong>Single-JSON-from-Database Architecture (<code class="language-plaintext highlighter-rouge">pgbench_viewer_payload_json</code>)</strong>:
Every Phase 1 view (<code class="language-plaintext highlighter-rouge">single</code>, <code class="language-plaintext highlighter-rouge">compare</code>, <code class="language-plaintext highlighter-rouge">multi</code>, and <code class="language-plaintext highlighter-rouge">live</code>) executes a single <code class="language-plaintext highlighter-rouge">SELECT pgbench_viewer_payload_json(...)</code> query. For active <code class="language-plaintext highlighter-rouge">status = 'running'</code> runs, PostgreSQL computes real-time <code class="language-plaintext highlighter-rouge">p50</code>/<code class="language-plaintext highlighter-rouge">p90</code>/<code class="language-plaintext highlighter-rouge">p95</code>/<code class="language-plaintext highlighter-rouge">p99</code>/<code class="language-plaintext highlighter-rouge">p99.9</code> latency percentiles and live Recall@K directly from committed <code class="language-plaintext highlighter-rouge">pgbench_tx_log</code> and <code class="language-plaintext highlighter-rouge">pgbench_progress_log</code> batches.</li>
  <li><strong>Classless <code class="language-plaintext highlighter-rouge">Water.css</code> (<code class="language-plaintext highlighter-rouge">css/</code>) + Modular <code class="language-plaintext highlighter-rouge">bottle.py</code> (<code class="language-plaintext highlighter-rouge">html/*.html.stpl</code>)</strong>:
Semantic HTML tables (<code class="language-plaintext highlighter-rouge">&lt;table&gt;</code>, <code class="language-plaintext highlighter-rouge">&lt;details&gt;</code>, <code class="language-plaintext highlighter-rouge">&lt;kbd&gt;</code>, <code class="language-plaintext highlighter-rouge">&lt;mark&gt;</code>) are styled automatically by <code class="language-plaintext highlighter-rouge">css/water.css</code> and <code class="language-plaintext highlighter-rouge">css/viewer.css</code> and rendered from modular <code class="language-plaintext highlighter-rouge">SimpleTemplate</code> views.</li>
  <li><strong>439-Byte Hand-Crafted WebAssembly Diagram Projector (<code class="language-plaintext highlighter-rouge">wasm/pgbench_chart.wasm</code> &amp; <code class="language-plaintext highlighter-rouge">src/</code>)</strong>:
Time-series TPS, latency percentiles, Recall@K histograms, and <code class="language-plaintext highlighter-rouge">Q1</code>-vs-<code class="language-plaintext highlighter-rouge">Q4</code> aging bars are projected into SVG <code class="language-plaintext highlighter-rouge">&lt;polyline&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;rect&gt;</code> coordinates in browser memory via a 439-byte WASM binary (<code class="language-plaintext highlighter-rouge">wasm/pgbench_chart.wasm</code>, built from <code class="language-plaintext highlighter-rouge">src/pgbench_chart.wat</code>, <code class="language-plaintext highlighter-rouge">src/pgbench_chart.c</code>, and <code class="language-plaintext highlighter-rouge">src/build_wasm.py</code>, exporting <code class="language-plaintext highlighter-rouge">series_min</code>, <code class="language-plaintext highlighter-rouge">series_max</code>, <code class="language-plaintext highlighter-rouge">series_mean</code>, <code class="language-plaintext highlighter-rouge">project_1d</code> over <code class="language-plaintext highlighter-rouge">Float64Array</code>).</li>
  <li><strong>Phase 2 SQL Data Explorer with Python Metatemplate Generator</strong>:
The <code class="language-plaintext highlighter-rouge">/explorer</code> tab lets users run 6 curated diagnostic presets (or custom read-only <code class="language-plaintext highlighter-rouge">SELECT</code>/<code class="language-plaintext highlighter-rouge">WITH</code> queries over <code class="language-plaintext highlighter-rouge">pgbench_*</code> tables). A Python metatemplate generator inspects the PostgreSQL result-set columns at runtime, synthesizes a custom <code class="language-plaintext highlighter-rouge">bottle.SimpleTemplate</code> (<code class="language-plaintext highlighter-rouge">stpl</code>) template with clickable <code class="language-plaintext highlighter-rouge">run_id</code> badges, verdict pills, and right-aligned numeric formatting, and auto-renders a WASM time-series or bar chart whenever numeric series columns are detected.</li>
</ol>

<hr />

<h2 id="9-built-for-autonomous-ai-agents-experimental-agent-oriented-documentation--skill">9. Built for Autonomous AI Agents: Experimental Agent-Oriented Documentation &amp; Skill</h2>

<p>As database infrastructure becomes increasingly automated, benchmarking is no longer performed exclusively by human engineers. Autonomous coding agents, LLM assistants, and automated CI/CD pipelines regularly evaluate PostgreSQL performance, validate optimizer improvements, and investigate latency regressions. However, traditional human-oriented documentation (verbose man pages and narrative prose) is notorious for tripping up AI agents—causing option hallucinations, skipped multi-stage initialization steps, unparsed terminal logs, and unintentional trademark violations.</p>

<p>To solve this, the new <code class="language-plaintext highlighter-rouge">pgbench</code> ecosystem introduces <strong>experimental agent-oriented documentation and an integrated agent skill</strong>:</p>

<ul>
  <li><strong>Integrated Agent Skill (<code class="language-plaintext highlighter-rouge">pgbench-new</code>)</strong>: Packaged as a specialized agent skill (<a href="/pgbench/pgbench_agent_skill.html"><code class="language-plaintext highlighter-rouge">pgbench_agent_skill.md</code></a>), it equips autonomous agents with explicit trigger definitions, invariant execution pipelines (<code class="language-plaintext highlighter-rouge">schema</code> \(\rightarrow\) <code class="language-plaintext highlighter-rouge">init</code> \(\rightarrow\) <code class="language-plaintext highlighter-rouge">constraints</code>), unambiguous exit code semantics (<code class="language-plaintext highlighter-rouge">0</code> = pass, <code class="language-plaintext highlighter-rouge">1</code> = fatal/execution error, <code class="language-plaintext highlighter-rouge">2</code> = SLA contract breach), and strict trademark/fair-use guardrails.</li>
  <li><strong>Deterministic Agent Decision Matrix &amp; Recipes</strong>: Accompanied by an agent decision matrix (<a href="/pgbench/pgbench_agent_decision_matrix.html"><code class="language-plaintext highlighter-rouge">pgbench_agent_decision_matrix.md</code></a>) and structured YAML recipes (<a href="/pgbench/pgbench_agent_recipes.html"><code class="language-plaintext highlighter-rouge">pgbench_agent_recipes.yaml</code></a>), agents can programmatically select the ideal benchmark suite, calculate appropriate scale factors, and enforce SLA contracts based on natural-language user requests without trial-and-error.</li>
  <li><strong>Seamless Synergy with <code class="language-plaintext highlighter-rouge">--config</code>, <code class="language-plaintext highlighter-rouge">--json-report</code> &amp; <code class="language-plaintext highlighter-rouge">--log-db</code></strong>: Instead of synthesizing brittle 25-flag command-line invocations, agents can read, generate, or mutate <code class="language-plaintext highlighter-rouge">.pgbench.json</code> files, validate them offline via <code class="language-plaintext highlighter-rouge">pgbench --config=... --dump-config=-</code>, stream runs directly into PostgreSQL via <code class="language-plaintext highlighter-rouge">--log-db=self --run-label=...</code>, and query <code class="language-plaintext highlighter-rouge">pgbench_compare_runs()</code> or <code class="language-plaintext highlighter-rouge">--json-report</code> for reliable closed-loop benchmarking and automated parameter optimization.</li>
</ul>

<hr />

<p>With these additions, <code class="language-plaintext highlighter-rouge">pgbench</code> bridges the gap between micro-benchmarks and heavyweight external test harnesses—giving PostgreSQL developers, DBAs, and AI agents a unified, zero-dependency, configuration-driven engine to test, store, compare, and visualize transactional, analytical, graph, and vector workloads alike.</p>]]></content><author><name>Hannu Krosing</name></author><summary type="html"><![CDATA[]]></summary></entry></feed>