Skip to content

bench: benchmark-suite plan + Phase 1 perf harness foundation (19) - #270

Merged
devopam merged 5 commits into
mainfrom
claude/postgresql-mcp-planning-8KssU
Jul 18, 2026
Merged

bench: benchmark-suite plan + Phase 1 perf harness foundation (19)#270
devopam merged 5 commits into
mainfrom
claude/postgresql-mcp-planning-8KssU

Conversation

@devopam

@devopam devopam commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Kicks off the public, evidence-driven benchmark suite (roadmap 19). Two parts:

1. The plandocs/plans/benchmark-suite.md (+ roadmap §19). The thesis, the non-negotiable framing rule (we never claim MCPg out-executes Postgres — the perf tier exists to disarm that objection and prove the overhead is sub-ms, which earns credibility for the real token win in v2), the methodology, TPC-H scale, the break-even model, the honest-caveats table, and the locked v1 decisions (v1 = performance + static HTML dashboard; SF10 ceiling).

2. Phase 1 foundation — the performance harness, designed by the Opus architect grounded in MCPg's real APIs:

  • benchmarks/perf/paths.pyNativeRunner (a persistent psycopg connection replicating MCPg's exact BEGIN READ ONLY … ROLLBACK envelope, minus all MCPg overhead — the fair floor, not psql -c) and ServerSideRunner (the real in-process run_select over the real SafeSqlDriver + pool, keeping the genuine per-call allocation + serialization).
  • benchmarks/perf/queries.py — the two-axis query set (compute × result-size), heavy tier from TPC-H (Q1/Q3/Q5/Q6).
  • benchmarks/perf/stats.py — percentiles + seeded bootstrap median CI + warm-up drop (pure, unit-tested).
  • benchmarks/perf/schema.py — structured JSON result schema (provenance passed in).
  • benchmarks/perf/runner.py — orchestrates paths × queries (cold + warm) → JSON, gc-disabled around timed regions.
  • benchmarks/datasets/ — TPC-H schema/index DDL + a DuckDB→COPY loader (SF1/SF10; data regenerated locally, never committed).
  • pyproject.toml — dev-only bench group (duckdb); pytest pythonpath += "." so the benchmark package is importable in its tests.

Next phases (own PRs): the end-to-end transport paths, the overhead-decomposition waterfall (the t_db == native gate), the concurrency sweep, and the self-contained HTML dashboard — then the v1 writeup.

Verification: 8 new unit tests pass; full suite 2813 passed; ruff check ., ruff format --check ., mypy src/mcpg all clean. The DB-touching harness is an operator tool (runs against a live PostgreSQL), not unit-tested — matching the existing benchmarks/bench.py.

Roadmap linkage

Advances roadmap row: 19.1 (and lands the §19 plan).

Checklist

  • Tests added/updated first (TDD); suite passes locally
  • ruff, ruff format, and mypy src/mcpg pass
  • CHANGELOG.md — n/a (no shipped-surface change; benchmarks are an operator tool)
  • Roadmap row cited above (19.1)
  • No hand-edits to src/mcpg/_vendor/ (the vendor tree no longer exists — 18.1)

🤖 Generated with Claude Code

https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc


Generated by Claude Code

Summary by Sourcery

Introduce a reproducible performance benchmarking harness and documented plan for the MCPg benchmark suite, including initial native vs server-side latency measurements on TPC-H workloads.

Enhancements:

  • Add a structured performance benchmarking harness with native and server-side runners, query taxonomy, statistics aggregation, and JSON result schema under a new benchmarks package.
  • Provide scripts and SQL to generate and load a TPC-H dataset into PostgreSQL for local benchmark runs, without committing data files.

Build:

  • Define a dev-only bench dependency group (DuckDB) and adjust pytest pythonpath so the benchmarks package is importable in its tests.

Documentation:

  • Document the benchmark-suite roadmap and methodology, including performance and token-efficiency objectives, datasets, and phasing in a new benchmark plan and feature-shortlist entry.

Tests:

  • Add unit tests covering benchmark statistics, query coverage across compute and result-size axes, and serialization of performance run results.

claude added 3 commits July 18, 2026 03:01
Plan-first spec for a reproducible benchmark that makes a conclusive,
publishable case for MCPg on the axes where the evidence is unassailable —
token efficiency + agent reliability at negligible performance overhead, with
safety guarantees. Explicitly does NOT claim MCPg out-executes PostgreSQL:
the perf tier disarms that objection by decomposing the sub-ms overhead
(native vs server-side vs end-to-end, TPC-H heavy tier), and the win is
measured on tokens (Tier-A deterministic I/O accounting + Tier-B agent-loop
runs) net of the 252-tool context cost via a break-even model. Reusable,
self-contained HTML dashboard. Adds roadmap section 19 (19.1–19.5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Fold in the session decisions: v1 = the performance objective + a self-
contained static HTML dashboard (token study becomes v2, inheriting the same
dashboard); published heavy scale capped at TPC-H SF10; static HTML over a
Grafana/Prometheus live variant (a result is a snapshot, not a live feed).
Re-phased 19.1-19.6 (v1 = 19.1-19.3 perf + dashboard + writeup; v2 = 19.4-19.6
token accounting + agent study + combined writeup) and replaced the open-
questions section with the locked decisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
…19.1)

First slice of the v1 performance benchmark (roadmap 19, per
docs/plans/benchmark-suite.md), grounded in the Opus architect's design:

- benchmarks/perf/paths.py: the NativeRunner (persistent psycopg replicating
  MCPg's exact BEGIN READ ONLY … ROLLBACK envelope, minus all MCPg overhead —
  the fair floor, not psql -c) and ServerSideRunner (real in-process run_select
  over the real SafeSqlDriver + pool, keeping the genuine per-call allocation +
  serialization).
- benchmarks/perf/queries.py: the two-axis query set (compute x result-size),
  heavy tier from TPC-H (Q1/Q3/Q5/Q6), result-size axis to isolate serialize cost.
- benchmarks/perf/stats.py: percentiles + seeded bootstrap median CI + warm-up
  drop (pure, unit-tested).
- benchmarks/perf/schema.py: the structured JSON result schema (provenance
  passed in, never Date.now()).
- benchmarks/perf/runner.py: orchestrates paths x queries (cold + warm) → JSON,
  gc-disabled around timed regions.
- benchmarks/datasets/: TPC-H schema/index DDL + a DuckDB→COPY loader (SF1/SF10,
  data regenerated locally, never committed).
- pyproject: dev-only `bench` group (duckdb); pytest pythonpath += "." so the
  benchmarks package is importable in its unit tests.

Honest framing baked in (README + docstrings): MCPg does not out-execute
Postgres; the harness proves the overhead is sub-ms and t_db == native. The
end-to-end transport paths, the overhead-decomposition waterfall (the
t_db==native gate), the concurrency sweep, and the HTML dashboard are the next
phases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc

@gemini-code-assist-2 gemini-code-assist-2 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Phase 1 of the MCPg benchmark suite, which includes a reproducible performance harness comparing native PostgreSQL execution against MCPg server-side paths using TPC-H datasets, statistical aggregation utilities, and associated unit tests. The review feedback highlights two critical robustness improvements: ensuring a ROLLBACK is always executed in the native runner's transaction envelope using a try...finally block to prevent aborted transaction states on persistent connections, and wrapping database and runner initializations in nested try...finally blocks to prevent connection pool leaks in case of setup failures.

Comment thread benchmarks/perf/paths.py
Comment on lines +57 to +68
async def run_once(self, sql: str, *, max_rows: int) -> int:
conn = self._conn
start = time.perf_counter_ns()
async with conn.cursor(row_factory=dict_row) as cur:
await cur.execute("BEGIN TRANSACTION READ ONLY")
await cur.execute(sql)
rows = await cur.fetchall()
# Match run_select's max_rows truncation + dict materialization so
# the serialization work compared is like-for-like.
_ = [dict(r) for r in rows[:max_rows]]
await cur.execute("ROLLBACK")
return time.perf_counter_ns() - start

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Since the connection is created with autocommit=True and is persistent across multiple benchmark iterations, manually executing BEGIN TRANSACTION READ ONLY without a try...finally block to guarantee ROLLBACK is highly risky. If cur.execute(sql) or cur.fetchall() raises an exception, the ROLLBACK statement will be skipped, leaving the persistent connection in an aborted transaction state. This will cause all subsequent queries in the benchmark run to fail with InFailedSqlTransaction errors. Wrap the execution in a try...finally block to ensure ROLLBACK is always executed.

Suggested change
async def run_once(self, sql: str, *, max_rows: int) -> int:
conn = self._conn
start = time.perf_counter_ns()
async with conn.cursor(row_factory=dict_row) as cur:
await cur.execute("BEGIN TRANSACTION READ ONLY")
await cur.execute(sql)
rows = await cur.fetchall()
# Match run_select's max_rows truncation + dict materialization so
# the serialization work compared is like-for-like.
_ = [dict(r) for r in rows[:max_rows]]
await cur.execute("ROLLBACK")
return time.perf_counter_ns() - start
async def run_once(self, sql: str, *, max_rows: int) -> int:
conn = self._conn
start = time.perf_counter_ns()
async with conn.cursor(row_factory=dict_row) as cur:
await cur.execute("BEGIN TRANSACTION READ ONLY")
try:
await cur.execute(sql)
rows = await cur.fetchall()
# Match run_select's max_rows truncation + dict materialization so
# the serialization work compared is like-for-like.
_ = [dict(r) for r in rows[:max_rows]]
finally:
await cur.execute("ROLLBACK")
return time.perf_counter_ns() - start

Comment thread benchmarks/perf/runner.py
Comment on lines +85 to +103
database = Database(settings)
await database.connect()
native = await NativeRunner.connect(args.database_url)
results: list[ResultRow] = []
try:
pg = await database.driver().execute_query(
"SELECT current_setting('server_version') AS v, current_setting('server_version_num')::int AS num",
force_readonly=True,
)
pg_meta = {"version_string": pg[0].cells["v"], "server_version_num": pg[0].cells["num"]} if pg else {}

for query in all_queries():
for label, runner in (("native", native), ("server_side", ServerSideRunner(database))):
warm, cold = await _sample_path(runner, query, args.iterations)
results.append(_row(label, query, "warm", warm))
results.append(_row(label, query, "cold", [cold]))
finally:
await native.close()
await database.close()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

If NativeRunner.connect raises an exception, the Database connection pool (which has already successfully connected) will never be closed, leaking active database connections. To prevent resource leaks, wrap the runner initialization and benchmark execution in nested try...finally blocks to guarantee that both the database pool and the native connection are always closed.

    database = Database(settings)
    await database.connect()
    try:
        native = await NativeRunner.connect(args.database_url)
        try:
            results: list[ResultRow] = []
            pg = await database.driver().execute_query(
                "SELECT current_setting('server_version') AS v, current_setting('server_version_num')::int AS num",
                force_readonly=True,
            )
            pg_meta = {"version_string": pg[0].cells["v"], "server_version_num": pg[0].cells["num"]} if pg else {}

            for query in all_queries():
                for label, runner in (("native", native), ("server_side", ServerSideRunner(database))):
                    warm, cold = await _sample_path(runner, query, args.iterations)
                    results.append(_row(label, query, "warm", warm))
                    results.append(_row(label, query, "cold", [cold]))
        finally:
            await native.close()
    finally:
        await database.close()

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 security issues, 3 other issues, and left some high level feedback:

Security issues:

  • Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option. (link)
  • Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option. (link)
  • Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option. (link)

General comments:

  • In bootstrap_median_ci you resample 10,000 times on every summarize call; given this runs for each (path, query, temperature) combination, consider making the resample count configurable or lowering the default to avoid the CI computation dominating runtime for larger suites.
  • In _sample_path you disable GC around warm iterations but not for the initial cold measurement; if you care about isolating cold-path DB effects from GC pauses, consider applying the same GC disable/enable pattern to the cold sample or explicitly documenting that GC noise is part of the cold path.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `bootstrap_median_ci` you resample 10,000 times on every summarize call; given this runs for each (path, query, temperature) combination, consider making the resample count configurable or lowering the default to avoid the CI computation dominating runtime for larger suites.
- In `_sample_path` you disable GC around warm iterations but not for the initial cold measurement; if you care about isolating cold-path DB effects from GC pauses, consider applying the same GC disable/enable pattern to the cold sample or explicitly documenting that GC noise is part of the cold path.

## Individual Comments

### Comment 1
<location path="benchmarks/perf/runner.py" line_range="87-55" />
<code_context>
+    )
+    database = Database(settings)
+    await database.connect()
+    native = await NativeRunner.connect(args.database_url)
+    results: list[ResultRow] = []
+    try:
+        pg = await database.driver().execute_query(
+            "SELECT current_setting('server_version') AS v, current_setting('server_version_num')::int AS num",
+            force_readonly=True,
+        )
+        pg_meta = {"version_string": pg[0].cells["v"], "server_version_num": pg[0].cells["num"]} if pg else {}
+
+        for query in all_queries():
+            for label, runner in (("native", native), ("server_side", ServerSideRunner(database))):
+                warm, cold = await _sample_path(runner, query, args.iterations)
+                results.append(_row(label, query, "warm", warm))
+                results.append(_row(label, query, "cold", [cold]))
+    finally:
+        await native.close()
+        await database.close()
</code_context>
<issue_to_address>
**issue (bug_risk):** Initialize `pg_meta` before the `try` block to avoid masking errors with `UnboundLocalError` if the metadata query fails.

Because `pg` is fetched inside the `try`, any exception before `pg_meta` is assigned will still run the `finally` and then continue to construct `metadata`. At that point `pg_meta` is undefined and raises `UnboundLocalError`, obscuring the original exception. Pre-initializing `pg_meta: dict[str, Any] = {}` before the `try:` prevents this masking while keeping later access safe.
</issue_to_address>

### Comment 2
<location path="benchmarks/perf/runner.py" line_range="153" />
<code_context>
+def main(argv: list[str] | None = None) -> int:
+    parser = argparse.ArgumentParser(description="MCPg performance benchmark (native vs server-side).")
+    parser.add_argument("--database-url", required=True, help="PostgreSQL DSN (a TPC-H-loaded database).")
+    parser.add_argument("--iterations", type=int, default=50, help="Warm iterations per pathxquery (>= 20).")
+    parser.add_argument("--scale-factor", type=int, default=1, help="TPC-H scale factor the DB was loaded at.")
+    parser.add_argument("--output", type=Path, required=True, help="Path to write the result JSON.")
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Validate a minimum `--iterations` value so warm measurements are non-empty and match the CLI contract.

The help text promises `>= 20`, but the code doesn’t enforce any minimum. With `_WARMUP = 5`, values like `--iterations 0` or other small values yield an empty warm array, so `summarize()` returns all-zero stats, which is misleading but looks valid. Consider validating `iterations >= _WARMUP + 1` (or at least `>= 1`) at argument parsing or at the start of `_run`, and failing fast with a clear error message.

Suggested implementation:

```python
    parser.add_argument(
        "--iterations",
        type=int,
        default=50,
        help=f"Warm iterations per pathxquery (>= {_WARMUP + 1}).",
    )

```

```python
    args = parser.parse_args(argv)

    if args.iterations < _WARMUP + 1:
        parser.error(f"--iterations must be >= {_WARMUP + 1} (warm measurements would be empty).")

    run = asyncio.run(_run(args))

```
</issue_to_address>

### Comment 3
<location path="docs/plans/benchmark-suite.md" line_range="19" />
<code_context>
+
+| Clause | Evidence |
+|---|---|
+| *dramatically more token-efficient* | 40–70 %+ fewer tokens per database task vs a bare `run_select` tool (the headline) |
+| *more reliable* | fewer error→retry loops, higher task-completion rate |
+| *negligible performance cost* | sub-millisecond, decomposed overhead; ~0 % on heavy queries; cache **beats** native on repeat reads |
</code_context>
<issue_to_address>
**issue (typo):** The `40–70 %+` sequence looks like a typo in the percentage notation.

Please rephrase `40–70 %+` to something like `40–70%+` or `40–70%` so the percentage notation is unambiguous.

```suggestion
| *dramatically more token-efficient* | 40–70% fewer tokens per database task vs a bare `run_select` tool (the headline) |
```
</issue_to_address>

### Comment 4
<location path="benchmarks/datasets/load_tpch.py" line_range="51" />
<code_context>
            duck.execute(f"CALL dbgen(sf={scale_factor})")
</code_context>
<issue_to_address>
**security (python.sqlalchemy.security.sqlalchemy-execute-raw-query):** Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option.

*Source: opengrep*
</issue_to_address>

### Comment 5
<location path="benchmarks/datasets/load_tpch.py" line_range="54" />
<code_context>
                duck.execute(f"COPY {table} TO '{csv_path}' (FORMAT csv, HEADER false)")
</code_context>
<issue_to_address>
**security (python.sqlalchemy.security.sqlalchemy-execute-raw-query):** Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option.

*Source: opengrep*
</issue_to_address>

### Comment 6
<location path="benchmarks/datasets/load_tpch.py" line_range="68" />
<code_context>
            await conn.execute(f"VACUUM (ANALYZE) {table}")
</code_context>
<issue_to_address>
**security (python.sqlalchemy.security.sqlalchemy-execute-raw-query):** Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread benchmarks/perf/runner.py
Comment thread benchmarks/perf/runner.py Outdated
Comment thread docs/plans/benchmark-suite.md Outdated
duck = duckdb.connect()
duck.execute("INSTALL tpch; LOAD tpch;")
print(f"generating TPC-H SF{scale_factor} (this can take a while) ...")
duck.execute(f"CALL dbgen(sf={scale_factor})")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.sqlalchemy.security.sqlalchemy-execute-raw-query): Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option.

Source: opengrep

duck.execute(f"CALL dbgen(sf={scale_factor})")
for table in _TABLES:
csv_path = Path(tmp) / f"{table}.csv"
duck.execute(f"COPY {table} TO '{csv_path}' (FORMAT csv, HEADER false)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.sqlalchemy.security.sqlalchemy-execute-raw-query): Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option.

Source: opengrep

await conn.execute(index_sql)
print("VACUUM ANALYZE ...")
for table in _TABLES:
await conn.execute(f"VACUUM (ANALYZE) {table}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.sqlalchemy.security.sqlalchemy-execute-raw-query): Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option.

Source: opengrep

devopam and others added 2 commits July 18, 2026 09:41
Address robustness feedback on the Phase 1 harness before external review:

- NativeRunner.run_once now wraps the query in try/finally so ROLLBACK
  always runs. A failing query on the persistent connection would otherwise
  leave it in an aborted-transaction state and poison every later
  measurement.
- runner._run pre-initialises pg_meta = {} before the try, so a failure in
  the server-version metadata query surfaces the real exception instead of
  masking it with UnboundLocalError when building metadata.
- runner.main validates --iterations >= _WARMUP + 1 and fails fast; small
  values previously yielded an empty warm bucket and misleading all-zero
  stats that looked valid. Added a DB-free unit test for the guard.
- load_tpch: document why the f-string SQL is safe (int scale factor +
  frozen table-name literals; identifiers/DDL can't be bound params).
- Fix a percentage-notation typo in the benchmark plan.

Full gate green: 9 bench tests pass, ruff/format/mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
@devopam
devopam merged commit 39dd23b into main Jul 18, 2026
18 of 19 checks passed
devopam added a commit that referenced this pull request Jul 18, 2026
…ncy (#272)

Completes roadmap 19.1's core on top of the Phase 1 harness (#270):

- Overhead decomposition (perf/decompose.py): times the server path
  segment-by-segment (t_parse -> t_pool -> t_txn -> t_db -> t_serialize),
  mirroring the real run_select path, and records the machine-checkable
  t_db == native assertion (native anchored by NativeRunner.db_segment_once).
- End-to-end MCP paths (perf/e2e.py, --e2e): in-memory, stdio subprocess, and
  operator-started streamable-HTTP, driven through a real ClientSession — the
  t_protocol band.
- Throughput-under-concurrency sweep (perf/concurrency.py, --concurrency):
  native + server-side at 1/4/16/64 clients, pool sized to the ceiling for a
  fair comparison.

Hardened after review: _assertions keys the baseline on concurrency == 1 (so
sweep rows don't overwrite it), e2e runners register for teardown before
start(), and the decomposition timed region rolls back on failure. Pure
helpers unit-tested (24 in the module); full unit suite green; ruff/format/
mypy clean.

Advances roadmap row: 19.1
devopam pushed a commit that referenced this pull request Jul 18, 2026
The v1 (performance) writeup — docs/performance-benchmark.md, linked from the
docs index and marked shipped in the roadmap.

- Leads with the framing rule (MCPg is not faster than Postgres; the benchmark
  proves the overhead is small and decomposes where it goes), then the
  methodology (three paths, the t_parse..t_serialize decomposition, the
  two-axis query taxonomy, TPC-H SF1/SF10, the statistical treatment), the
  load-bearing t_db == native result, where the overhead goes (the honest
  relative-vs-absolute point), the concurrency sweep, a run-it-yourself section
  with the exact commands, and a "what a skeptic attacks" table.
- The Results section is populated from a real SF10 run (committed JSON +
  dashboard), never asserted from memory — in keeping with the project's
  verify-before-you-write rule. All harness/CLI claims in the doc were verified
  against the actual runner, loader, dashboard, and query set.
- Marks §19 rows 19.1/19.2 shipped (#270/#272/#273) and 19.3 shipped (this PR).

Advances roadmap row: 19.3

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122yLZLJ8t4W43sdN6BmTZc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants