perf(github): reuse the inherited review wait query template (#18965)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - External review waits check current GitHub review and publication
bindings.
> - The GitHub service rebuilds the same seven-table Drizzle query on
each check.
> - A sampled production window attributed 6.3% of SQL serialization CPU
to this query shape.
> - This pull request prepares that query once per exact database or
transaction object.
> - Each call still reads current provenance, review, connection and
publication state.
> - The local service benchmark reduces median client CPU per read by
24.35%.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Repeated `readInheritedGitHubReviewWait` calls from external review wait
reads and attention retention.

**Subsystem affected**

Server GitHub chat review waits.

**Current behavior**

Every call constructs and serializes the same joined SELECT. The
database driver already supports prepared statements. That setting does
not reuse the JavaScript query template.

**Proposed behavior**

Compile the existing selector once for each supplied database,
transaction or savepoint object. Bind company, issue, agent and comment
IDs on each execution. Keep every current database read and check.

**Reason and benefit**

Remove repeated query construction and serialization CPU. Do not cache
rows or authorization decisions. Exact open-PR searches found no
duplicate query-template change.

## What Changed

- Add an exact-handle `WeakMap` for the GitHub review selector and
execute it with fresh placeholders.
- Preserve the SQL projection, joins, JSON types, latest-review order
and all current authority checks.
- Extend real PostgreSQL tests for new bindings, warm-cache revocation,
latest heads and transaction/savepoint rollback.
- Capture prepared executions in the existing test helper.
- Add an actual-service benchmark and a report with every timing sample
and setup failure.

## Verification

- `github-review-wait.test.ts`: 16 tests passed on real PostgreSQL.
- `external-chat-wait.integration.test.ts -t 'reads small current
external review waits without saved prompts'`: four affected cases
passed; 269 unrelated cases skipped.
- Pinned baseline: `e54f9fbf828e06e385e0c62dc381ccb46190aa30`. Final
source SHA256:
`fe647e7fe2aed82609e8f1d66a0109e7a10ee28313ae2ef218ba3ab39b5cd105`.
- Exclusive-window benchmark: 500 warmups per version, nine alternating
pairs of 100 complete service reads on disposable PostgreSQL. Median
client CPU: 1.00336 → 0.75908 ms/read (24.35% less). Median wall time:
2.87089 → 2.38731 ms/read (16.84% less). CPU excludes the PostgreSQL
process.
- Every generated SQL statement, parameter, query count and complete
result matched. Warm joined-selector compilations per ten service reads:
10 → 0.
- See `doc/performance/2026-11-04-prepared-github-review-wait.md` for
reproduction, all samples and measurement limits.
- Setup record: loopback required execution outside the sandbox. The
fresh embedded-Postgres dependency lacked macOS dylib aliases; aliases
to existing identical bytes restored the fixture. The SDK import needed
a current workspace SDK/shared build. A broad external-chat suite run
was interrupted and replaced by the affected test filter. Those attempts
are not counted as passing suites.
- Greptile: 5/5 on current head
`d2e6ae8814c3a56c380bd5d60c47e6f98642f085`, with no actionable issues or
inline comments.
- Current-head CI is green at
`d2e6ae8814c3a56c380bd5d60c47e6f98642f085`: 52 successful checks, three
intentional skips and the successful Snyk status. This includes
typecheck, build, the full test matrix, end-to-end shards and the
aggregate gate.
- Combined local validation at `4ab58ff70b` includes both measured
production templates and final test fixes. Full recursive `pnpm -r
typecheck` passed in 39.864 seconds. Three real PostgreSQL files passed
35/35 tests (five chat cases, 16 GitHub review cases and 14 shared
prepared-query cases). The combined build passed earlier with the same
production template bytes.
- The initial combined typecheck failed on a separate chat test typing
error and build setup. The later passing run resolves that attempt. The
original full local `pnpm test:run` was deliberately interrupted after
six chat shards passed and partial general tests ran; it is not a
passing full local suite. Each PR has independent current-head CI for
broad coverage. The initial loopback sandbox restriction was resolved by
an escalated run of the same source.
- Local benchmark/test dependencies were locked at the baseline; the
rebase adds the upstream lockfile refresh and UI paste fix only.

## Risks

- A prepared query captures its session. The exact supplied handle is
the cache key so transactions and savepoints do not borrow the root
session.
- Placeholder mistakes could cross bindings. Tests alternate independent
company/issue/agent/comment fixtures on the same handle.
- The benchmark has one synthetic eligible fixture with warm sequential
reads. It does not predict production endpoint latency or whole-server
CPU savings.
- No schema, index, database driver setting or public API changes.

## Model Used

OpenAI `gpt-6.1-sol` through Codex. The context-window size is not
exposed by the session. Used reasoning, tool calls, source editing and
code execution. Independent Codex review found no correctness issues.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-11-04 08:29:59 -06:00
1 parent 76e43852ac
commit 2cbfc35a07
5 files changed
+311 -27

No files matched your search

@@ -0,0 +1,114 @@
import assert from "node:assert/strict";
import { SQL } from "drizzle-orm";
import { execFileSync } from "node:child_process";
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { applyPendingMigrations, createDb, startEmbeddedPostgresTestDatabase } from "../../packages/db/src/index.js";
import { readInheritedGitHubReviewWait } from "../../server/src/services/chat-channels/github/review-wait.js";
import { captureReviewWaitQueries, seedGitHubReviewWait,
revokeReviewWaitConnection, type CapturedReviewWaitQuery } from "../../server/src/__tests__/helpers/github-review-wait.js";
const baselineRef = process.argv.find(arg => arg.startsWith("--baseline-ref="))?.slice(15);
assert.ok(baselineRef, "Pass an explicit pre-fix --baseline-ref=<commit>");
const baselineRevision = execFileSync("git", ["rev-parse", "--verify", `${baselineRef}^{commit}`], { encoding: "utf8" }).trim();
const treatmentRevision = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
const modulePath = "server/src/services/chat-channels/github/review-wait.ts";
const helperPath = "server/src/services/chat-channels/run-origin.ts";
const root = fileURLToPath(new URL("../../", import.meta.url));
assert.equal(execFileSync("git", ["show", `${baselineRevision}:${helperPath}`], { encoding: "utf8" }),
await fs.readFile(path.join(root, helperPath), "utf8"), "Baseline provenance helper must be unchanged");
const keepAlive = setInterval(() => {}, 1_000);
let temporary: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | undefined;
let db: ReturnType<typeof createDb> | undefined;
let baselineDir: string | undefined;
try {
const oldSource = execFileSync("git", ["show", `${baselineRevision}:${modulePath}`], { encoding: "utf8" });
const moduleUrl = new URL(`../../${modulePath}`, import.meta.url);
baselineDir = await fs.mkdtemp(path.join(root, "server/scripts/.review-wait-baseline-"));
const baselineFile = path.join(baselineDir, "review-wait.ts");
await fs.writeFile(baselineFile, oldSource.replace(/from "(\.{1,2}\/[^\"]+)"/g,
(_match, specifier: string) => `from ${JSON.stringify(fileURLToPath(new URL(specifier, moduleUrl)))}`));
const baseline = await import(pathToFileURL(baselineFile).href) as typeof import("../../server/src/services/chat-channels/github/review-wait.js");
const externalUrl = process.env.PAPERCLIP_TEST_DATABASE_URL;
if (externalUrl) {
const parsed = new URL(externalUrl);
assert.ok(["127.0.0.1", "localhost"].includes(parsed.hostname) && parsed.pathname.startsWith("/review_wait_benchmark"),
"External benchmark database must be a disposable local review_wait_benchmark database");
await applyPendingMigrations(externalUrl);
db = createDb(externalUrl);
} else {
temporary = await startEmbeddedPostgresTestDatabase("github-review-wait-benchmark-");
db = createDb(temporary.connectionString);
}
const f = await seedGitHubReviewWait(db);
async function capture(reader: typeof readInheritedGitHubReviewWait) {
const queries: CapturedReviewWaitQuery[] = [];
const result = await reader(captureReviewWaitQueries(db!, queries), f.run);
return { result, queries };
}
const before = await capture(baseline.readInheritedGitHubReviewWait);
const after = await capture(readInheritedGitHubReviewWait);
assert.deepEqual(after, before, "Complete service output, SQL, bound parameters and query count must match");
assert.equal(before.result?.kind, "published");
async function compilations(reader: typeof readInheritedGitHubReviewWait) {
const original = SQL.prototype.toQuery;
let joined = 0;
SQL.prototype.toQuery = function (...args: Parameters<typeof original>) {
const query = original.apply(this, args);
if (query.sql.includes('join "chat_github_reviews"')) joined++;
return query;
};
try {
for (let i = 0; i < 10; i++) await reader(db!, f.run);
} finally { SQL.prototype.toQuery = original; }
return joined;
}
const buildCounts = { baseline: await compilations(baseline.readInheritedGitHubReviewWait),
treatment: await compilations(readInheritedGitHubReviewWait) };
buildCounts.treatment = await compilations(readInheritedGitHubReviewWait);
assert.equal(buildCounts.baseline, 10);
assert.equal(buildCounts.treatment, 0);
await revokeReviewWaitConnection(db, f);
assert.deepEqual(await readInheritedGitHubReviewWait(db, f.run), { kind: "revoked" });
assert.deepEqual(await baseline.readInheritedGitHubReviewWait(db, f.run), { kind: "revoked" });
await db.$client.unsafe("UPDATE tool_connections SET enabled = true WHERE id = $1::uuid", [f.connectionId]);
const iterations = Number(process.env.REVIEW_WAIT_BENCH_ITERATIONS ?? 100);
assert.ok(Number.isInteger(iterations) && iterations > 0);
const readers = { baseline: baseline.readInheritedGitHubReviewWait, treatment: readInheritedGitHubReviewWait };
const samples = { baseline: [] as { cpuMs: number; wallMs: number }[], treatment: [] as { cpuMs: number; wallMs: number }[] };
for (const reader of Object.values(readers)) for (let i = 0; i < 500; i++) await reader(db, f.run);
for (let pair = 0; pair < 9; pair++) {
for (const version of pair % 2 ? ["treatment", "baseline"] as const : ["baseline", "treatment"] as const) {
const cpu = process.cpuUsage(), wall = performance.now();
for (let i = 0; i < iterations; i++) await readers[version](db, f.run);
const elapsed = process.cpuUsage(cpu);
samples[version].push({ cpuMs: (elapsed.user + elapsed.system) / 1_000, wallMs: performance.now() - wall });
}
}
const stats = (values: typeof samples.baseline) => {
const cpu = values.map(value => value.cpuMs / iterations).sort((a, b) => a - b);
const wall = values.map(value => value.wallMs / iterations).sort((a, b) => a - b);
return { samples: values, medianCpuMsPerRead: cpu[Math.floor(cpu.length / 2)],
minCpuMsPerRead: cpu[0], maxCpuMsPerRead: cpu.at(-1), medianWallMsPerRead: wall[Math.floor(wall.length / 2)] };
};
console.log(JSON.stringify({ baselineRevision, treatmentRevision,
treatmentDiff: execFileSync("git", ["diff", "HEAD", "--", modulePath], { cwd: root, encoding: "utf8" }),
runtime: process.version, platform: process.platform, arch: process.arch,
postgresVersion: (await db.$client.unsafe("SELECT version()"))[0]!.version,
fixture: { reviews: 1, synthetic: true }, correctness: { completeOutputEqual: true,
everyGeneratedQueryAndParameterEqual: true, currentConnectionRevocationEqual: true },
generatedQueries: before.queries, compilationCountsPerTenWarmReads: buildCounts,
measurement: "Complete actual service, sequential real PostgreSQL reads; process CPU includes client work, excludes database process CPU",
timings: { iterations, warmups: 500, pairs: 9, order: "alternating", baseline: stats(samples.baseline), treatment: stats(samples.treatment) },
}, null, 2));
} finally {
try {
await db?.$client.end({ timeout: 0 });
await temporary?.cleanup();
if (baselineDir) await fs.rm(baselineDir, { recursive: true, force: true });
} finally { clearInterval(keepAlive); }
}