Skip to content

feat: rewrite simple to prepared - #1346

Draft
levkk wants to merge 9 commits into
mainfrom
levkk-simple-to-prepared
Draft

feat: rewrite simple to prepared#1346
levkk wants to merge 9 commits into
mainfrom
levkk-simple-to-prepared

Conversation

@levkk

@levkk levkk commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

closes #1313

@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

if let Some(ref step_two) = self.step_two {
request.simple_to_prepared_rewrite = true;
request.clear();
request.push(ProtocolMessage::Parse(step_two.parse.clone()));

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.

is this rewrite will later lead to prepare_statement consider it's like usual Parse and cause the same global cache check later? I guess it'll be the same name anyway if we get the same query transform anyway..

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, so Server will see this, check its cache, if it's already prepared, it will skip (drop, add_ignore('1')). If not, it will add to its local cache and send the Parse to pg.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Strangely I'm seeing no performance improvements from this...in fact, this branch is a bit slower than main...

@levkk

levkk commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

I might kick this one down the road. It's kinda too complicated and introduces side effects:

  1. We are guessing parameter data types
  2. We don't support rewriting updates, deletes or inserts because it may conflict with insert splits or sharding key updates
  3. the prepared statements cache story sucks: right now, this would increase the memory requirements by number of clients x number of unique statements (huge). I think that's just because my implementation sucks here (this PR specifically) and that part can be optimized, but I'm kind of losing interest in this issue for now.

Copy link
Copy Markdown
Contributor

I put this through a proper Forge run to understand the “no improvement / slightly slower” result. This was not a quick local pgbench sample.

Method

  • Provisioned an otherwise-idle Hetzner CCX23: 4 dedicated vCPUs, 16 GiB, Ubuntu; PgDog ran natively and PostgreSQL 18.4 ran in a host-networked container.
  • Pinned and built both binaries from source with the locked release profile (LTO, codegen-units=1):
    • base: 4f6faef0ab36cab0eb80ef385a7eb50c9ea33147
    • historical PR head: 05d117d3124dfce4ac4391857b8a19e172d6c6ec
  • I used 05d117d because it was the last branch commit that existed when the performance comment above was posted. The PR has moved since then.
  • Used the exact workload that revision's cli.sh bench invoked: pgdog/tests/pgbouncer/pgbench-parser.sql, simple protocol, 10 clients, one pgbench job, pgbench scale 10.
  • Compared three isolated arms: base, historical PR with the feature off, and the same PR binary with it on. The config was otherwise identical, and I explicitly enabled simple_to_prepared for the on arm.
  • Five trials per arm. Every trial started a fresh PgDog process/pool, discarded a 5-second warmup, reset pg_stat_statements, then measured for 30 seconds.
  • Captured TPS/latency plus pg_stat_statements planning counters, PgDog OpenMetrics deltas, 1-second sar/pidstat streams, binary hashes/configs/logs, and separate Linux perf profiles for off/on.

Result

arm median TPS five-trial range
base 6,167.7 6,156.5–6,188.6
PR, feature off 6,188.1 6,179.1–6,203.9
PR, feature on 6,056.6 6,022.1–6,227.8

Feature-on was 2.12% below the same binary with it off, and 1.80% below base, by medians. The on range overlaps the controls, so Forge's strict slowdown claim is inconclusive rather than proven. But the “no reliable improvement, slightly slower median” observation does reproduce.

The mechanism is much less ambiguous:

  • PostgreSQL plans/call: 1.0 → 0.0
  • PostgreSQL plan time: 0.1402 ms/call → 0
  • PgDog parse/rewrite time: 0.1182 → 0.2342 ms/query

So PostgreSQL is reusing the prepared statement successfully. The issue is that almost all of the ~0.140 ms saved in PostgreSQL is replaced by ~0.116 ms of extra measured PgDog AST/rewrite/deparse work, before counting the remaining protocol/cache/message overhead.

The perf profiles show the CPU work moving tiers:

arm PostgreSQL share of sampled cycles PgDog worker share
off 59.09% 36.76%
on 23.71% 69.05%

Feature-on-specific hot symbols include literal replacement, deparse routines, AST allocation, vector/message cloning, and hashing. This lines up with the implementation: simple queries deliberately bypass the AST cache; each request transforms literals and deparses the statement; the historical implementation takes a global prepared-cache write; and it builds a Parse/Describe/Bind/Execute/Sync sequence on every request.

One useful control: a later branch commit (427d9f7) on pgbench's cheap built-in -S point lookup was 32.25% faster with the feature enabled. In that workload, the added PgDog parse/rewrite cost was only ~0.0038 ms/query while saved PostgreSQL planning was ~0.0222 ms/call. So this is workload- and revision-sensitive, not “prepared statements never help.”

My read: the idea works and the server-side mechanism is proven, but for the historical parser-stress workload the spike mostly moves planning CPU from PostgreSQL into PgDog and adds enough cache/protocol work to erase the gain.

I kept the complete Forge run artifacts, raw measurements, system streams, and perf data locally if we want to turn this into a committed regression case.

@levkk

levkk commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Cool. So I would maybe use this as a "we can't enable prepared statements" but we need to because of X. Not sure what X is though.

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.

Translate simple queries into prepared statements automatically

3 participants