perf: round 23 — Postgres query-shape pass: typed JSONB decode, drive-policy borrow-deserialize, user-profile join!, subject-group CTE reuse, dedup unzip

Benchmark-gated, same rule as ROUND2-22: BEFORE/AFTER with a value-equivalence
gate and rollback-on-regression. Two harnesses — bench_round23_micro (no
Postgres; deterministic allocation gate) and bench_round23_queries (live
Postgres; p50 latency + strict equivalence gate against seeded fixtures). See
benches/ROUND23.md.

- J1: contact_pg_repository::row_to_contact (+ the inlined contact_group sibling)
  decode the 3 JSONB columns via sqlx::types::Json<T> (one from_slice pass)
  instead of row.get::<serde_json::Value> + from_value (a throwaway Value DOM
  per column, walked a second time). Per contact row of every list / multiget /
  CardDAV sync. Micro 84 -> 33 allocs/op (2.15x); PG 3794 -> 2360 ns/contact
  (1.61x) on 500 real rows.
- J2: DrivePolicies::from_value deserializes straight from the borrow
  (T::deserialize(&Value)) instead of from_value(value.clone()) — dropping the
  full-DOM clone on every drive-policy read (move/copy, share, grant); one-line
  body change, all 7 callers unchanged. Micro 5 -> 0 allocs/op (11.51x).
- P1: get_user_profile overlaps the two independent caller+target reads with
  tokio::join! (self-case still a single fetch; caller-error precedence
  preserved via caller_res? first) instead of two serial round-trips. PG
  577 -> 312 us/call (1.85x).
- G1: subject_group remove_member computes the child's transitive-user recursive
  CTE once and reuses it for both the would-empty pre-check and the cache
  invalidation, instead of running the identical CTE twice (the edge delete is
  above the child, so its descendants can't change). PG 829 -> 412 us/removal
  (2.01x).
- U1: dedup_service (store_loose_chunks final registration + the ingest
  run_rollback) reshapes the owned, dead-after Vec<(String,i64)> via
  into_iter().unzip() instead of cloning every 64-byte hash for the
  sync_blobs(&[String]) + UNNEST bind. Micro 256 -> 0 hash clones.

Verified: cargo clippy --features bench --all-targets -D warnings clean, cargo
fmt --all --check clean, cargo test --lib --features bench = 529 passed / 0
failed. The PG benches run against a local PostgreSQL 16 (schema applied from
migrations/); every equivalence gate passes.

The download_zip per-item N+1 (the audit's highest raw-latency candidate) is
deferred to a dedicated pass: its fix moves the sole authorization inside the
stream call, so it needs an AuthZ-ordering + anti-enumeration proof, not a perf
banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKyQ4AnYtgp1JtjzweyMeo
This commit is contained in:
Claude
2026-07-20 15:25:42 +00:00
parent 992bdae898
commit 1ec7030cc7
10 changed files with 1107 additions and 42 deletions
+363
View File
@@ -0,0 +1,363 @@
//! Round-23 CPU/alloc micro-pack (no Postgres) — the deterministic alloc gates
//! for the decode / clone candidates. The end-to-end PostgreSQL latency +
//! equivalence evidence lives in `bench_round23_queries.rs`.
//!
//! Same rule as ROUND2–22: each section is BEFORE (verbatim replica of the
//! shipped-before shape) vs AFTER (verbatim replica of the shipped-after shape,
//! which the source is then made to match), with a byte/-value equivalence gate
//! and a `GATE FAIL … rollback` check that `std::process::exit(1)`s if the AFTER
//! arm fails to beat its BEFORE.
//!
//! [J1] `contact_pg_repository::row_to_contact` (+ the `contact_group`
//! sibling) decoded each JSONB column with `row.get::<serde_json::Value>`
//! + `serde_json::from_value::<Vec<Dto>>` — a throwaway `Value` DOM per
//! column, walked a second time. AFTER decodes straight into the typed
//! Vec via `sqlx::types::Json<T>` (one `from_slice` pass). Modeled here
//! as `from_slice::<Value>` + `from_value` vs `from_slice::<Vec<Dto>>`.
//!
//! [J2] `DrivePolicies::from_value` did `serde_json::from_value(value.clone())`
//! — cloning the ENTIRE policies DOM per drive-policy read. AFTER
//! deserializes from the borrow (`T::deserialize(&Value)`), no clone.
//!
//! [U1] `dedup_service` (`store_loose_chunks` final registration + the ingest
//! `run_rollback`) built `Vec<String>`/`Vec<i64>` by CLONING every hash
//! out of an owned, dead-after `Vec<(String,i64)>` purely to reshape for
//! `sync_blobs(&[String])` + the UNNEST bind. AFTER moves via
//! `into_iter().unzip()`.
//!
//! Run:
//! cargo run --release --features bench --example bench_round23_micro
//! Tunables (env): BENCH_ITERS (200000), J1_ROWS (3), U1_CHUNKS (256)
use std::alloc::{GlobalAlloc, Layout, System};
use std::env;
use std::hint::black_box;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use serde::{Deserialize, Serialize};
use serde_json::Value;
static ALLOC_CALLS: AtomicU64 = AtomicU64::new(0);
struct CountingAlloc;
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.realloc(ptr, layout, new_size) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc_zeroed(layout) }
}
}
#[global_allocator]
static GLOBAL: CountingAlloc = CountingAlloc;
fn env_or<T: std::str::FromStr>(key: &str, default: T) -> T {
env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
struct Measured {
wall_ns_per_op: f64,
allocs_per_op: f64,
}
fn measure<F: FnMut()>(iters: usize, mut f: F) -> Measured {
for _ in 0..(iters / 20).max(1) {
f();
}
let a0 = ALLOC_CALLS.load(Ordering::Relaxed);
let t = Instant::now();
for _ in 0..iters {
f();
}
let wall = t.elapsed().as_nanos() as f64 / iters as f64;
let allocs = (ALLOC_CALLS.load(Ordering::Relaxed) - a0) as f64 / iters as f64;
Measured {
wall_ns_per_op: wall,
allocs_per_op: allocs,
}
}
fn print_row(label: &str, m: &Measured) {
println!(
"| {:<52} | {:>12.1} | {:>10.2} |",
label, m.wall_ns_per_op, m.allocs_per_op
);
}
fn header_footer(name: &str, before: &Measured, after: &Measured) {
println!("| arm | ns/op | allocs/op |");
print_row(&format!("BEFORE {name}"), before);
print_row(&format!("AFTER {name}"), after);
println!(
"# {:.2}x wall, {:.2} fewer allocs/op",
before.wall_ns_per_op / after.wall_ns_per_op,
before.allocs_per_op - after.allocs_per_op
);
}
fn gate_allocs(tag: &str, before: &Measured, after: &Measured) {
if after.allocs_per_op >= before.allocs_per_op {
eprintln!("GATE FAIL [{tag}]: AFTER did not reduce allocations — rollback");
std::process::exit(1);
}
}
// ────────────────────────────────────────────────────────────────────────────
// [J1] Contact JSONB decode — Value DOM + from_value vs Json<T> from_slice.
// Verbatim replicas of the persistence DTOs (contact_persistence_dto.rs).
// ────────────────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct EmailDto {
email: String,
r#type: String,
is_primary: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct PhoneDto {
number: String,
r#type: String,
is_primary: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct AddressDto {
street: Option<String>,
city: Option<String>,
state: Option<String>,
postal_code: Option<String>,
country: Option<String>,
r#type: String,
is_primary: bool,
}
/// BEFORE: `row.get::<Value>` (sqlx JSONB→Value DOM) then `from_value::<Vec<T>>`
/// (a second walk of the DOM). Modeled with `from_slice::<Value>` (what sqlx's
/// Value decoder does) + `from_value`.
fn j1_before(
email: &[u8],
phone: &[u8],
addr: &[u8],
) -> (Vec<EmailDto>, Vec<PhoneDto>, Vec<AddressDto>) {
let ev: Value = serde_json::from_slice(email).unwrap();
let pv: Value = serde_json::from_slice(phone).unwrap();
let av: Value = serde_json::from_slice(addr).unwrap();
let emails = serde_json::from_value::<Vec<EmailDto>>(ev).unwrap_or_default();
let phones = serde_json::from_value::<Vec<PhoneDto>>(pv).unwrap_or_default();
let addrs = serde_json::from_value::<Vec<AddressDto>>(av).unwrap_or_default();
(emails, phones, addrs)
}
/// AFTER: `sqlx::types::Json<Vec<T>>` decodes the JSONB bytes straight into the
/// typed Vec (one `from_slice::<Vec<T>>`), no intermediate DOM.
fn j1_after(
email: &[u8],
phone: &[u8],
addr: &[u8],
) -> (Vec<EmailDto>, Vec<PhoneDto>, Vec<AddressDto>) {
let emails = serde_json::from_slice::<Vec<EmailDto>>(email).unwrap_or_default();
let phones = serde_json::from_slice::<Vec<PhoneDto>>(phone).unwrap_or_default();
let addrs = serde_json::from_slice::<Vec<AddressDto>>(addr).unwrap_or_default();
(emails, phones, addrs)
}
fn section_j1() {
let iters: usize = env_or("BENCH_ITERS", 200_000);
let n: usize = env_or("J1_ROWS", 3); // entries per column, realistic contact
let mk_emails = |n: usize| -> Vec<EmailDto> {
(0..n)
.map(|i| EmailDto {
email: format!("user{i}@example.com"),
r#type: if i == 0 { "home" } else { "work" }.to_string(),
is_primary: i == 0,
})
.collect()
};
let mk_phones = |n: usize| -> Vec<PhoneDto> {
(0..n)
.map(|i| PhoneDto {
number: format!("+1-555-010{i}"),
r#type: "cell".to_string(),
is_primary: i == 0,
})
.collect()
};
let mk_addrs = |n: usize| -> Vec<AddressDto> {
(0..n)
.map(|i| AddressDto {
street: Some(format!("{} Main St", 100 + i)),
city: Some("Springfield".to_string()),
state: Some("IL".to_string()),
postal_code: Some("62704".to_string()),
country: Some("US".to_string()),
r#type: "home".to_string(),
is_primary: i == 0,
})
.collect()
};
let email_b = serde_json::to_vec(&mk_emails(n)).unwrap();
let phone_b = serde_json::to_vec(&mk_phones(n)).unwrap();
let addr_b = serde_json::to_vec(&mk_addrs(n)).unwrap();
// Equivalence: identical decoded Vecs.
assert_eq!(
j1_before(&email_b, &phone_b, &addr_b),
j1_after(&email_b, &phone_b, &addr_b),
"J1 decoded contacts differ"
);
let before = measure(iters, || {
black_box(j1_before(
black_box(&email_b),
black_box(&phone_b),
black_box(&addr_b),
));
});
let after = measure(iters, || {
black_box(j1_after(
black_box(&email_b),
black_box(&phone_b),
black_box(&addr_b),
));
});
println!(
"\n## [J1] Contact JSONB decode ({n} entries/col — per contact row of every list/multiget/sync)"
);
header_footer(
"Value DOM + from_value vs Json<T> from_slice",
&before,
&after,
);
gate_allocs("J1", &before, &after);
}
// ────────────────────────────────────────────────────────────────────────────
// [J2] Drive policies decode — from_value(value.clone()) vs deserialize(&value).
// ────────────────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
#[serde(default)]
struct Policies {
forbid_public_links: bool,
read_only: bool,
}
/// BEFORE: clone the whole `Value` DOM, then `from_value`.
fn j2_before(value: &Value) -> Policies {
serde_json::from_value(value.clone()).unwrap_or_default()
}
/// AFTER: deserialize straight from the borrow — no DOM clone.
fn j2_after(value: &Value) -> Policies {
Policies::deserialize(value).unwrap_or_default()
}
fn section_j2() {
let iters: usize = env_or("BENCH_ITERS", 200_000);
// A realistic on-disk policies bag with an unknown key preserved on disk
// (the lenient contract) so the DOM isn't trivially tiny.
let value: Value = serde_json::from_str(
r#"{"forbid_public_links":true,"read_only":false,"x_future_flag":"kept-on-disk"}"#,
)
.unwrap();
assert_eq!(
j2_before(&value),
j2_after(&value),
"J2 decoded policies differ"
);
assert!(j2_after(&value).forbid_public_links);
let before = measure(iters, || {
black_box(j2_before(black_box(&value)));
});
let after = measure(iters, || {
black_box(j2_after(black_box(&value)));
});
println!("\n## [J2] Drive policies decode (per move/copy/share/grant drive-policy read)");
header_footer(
"from_value(value.clone()) vs deserialize(&value)",
&before,
&after,
);
gate_allocs("J2", &before, &after);
}
// ────────────────────────────────────────────────────────────────────────────
// [U1] dedup hash reshape — clone-collect vs into_iter().unzip().
// ────────────────────────────────────────────────────────────────────────────
fn u1_build(n: usize) -> Vec<(String, i64)> {
(0..n)
.map(|i| {
(
format!("{:064x}", i as u128 * 0x9E37_79B9_7F4A_7C15),
i as i64,
)
})
.collect()
}
/// BEFORE: clone every hash out of the owned (dead-after) Vec to reshape.
fn u1_before(rows: Vec<(String, i64)>) -> (Vec<String>, Vec<i64>) {
let hashes: Vec<String> = rows.iter().map(|(h, _)| h.clone()).collect();
let sizes: Vec<i64> = rows.iter().map(|(_, s)| *s).collect();
(hashes, sizes)
}
/// AFTER: move via unzip — no per-hash content copy.
fn u1_after(rows: Vec<(String, i64)>) -> (Vec<String>, Vec<i64>) {
rows.into_iter().unzip()
}
fn section_u1() {
let n: usize = env_or("U1_CHUNKS", 256);
let iters: usize = env_or("BENCH_ITERS", 200_000) / 20; // heavier op
// Equivalence: identical hashes + sizes.
assert_eq!(
u1_before(u1_build(n)),
u1_after(u1_build(n)),
"U1 reshape differs"
);
let before = measure(iters, || {
black_box(u1_before(black_box(u1_build(n))));
});
let after = measure(iters, || {
black_box(u1_after(black_box(u1_build(n))));
});
println!(
"\n## [U1] dedup hash reshape ({n} distinct new chunks — per delta-upload registration)"
);
header_footer("clone-collect vs into_iter().unzip()", &before, &after);
gate_allocs("U1", &before, &after);
}
fn main() {
println!("# Round-23 micro-pack — BEFORE/AFTER (counting allocator, release)");
println!("# allocs/op is the deterministic gate; a non-winning AFTER exits 1 (rollback).");
section_j1();
section_j2();
section_u1();
println!("\nAll Round-23 micro sections passed their allocation gate.");
}
+433
View File
@@ -0,0 +1,433 @@
//! Round-23 PostgreSQL query-shape pack — end-to-end latency + equivalence on
//! the live dev Postgres. The deterministic alloc gates for the decode/clone
//! candidates live in `bench_round23_micro.rs`; this harness measures the real
//! round-trip / decode wins against seeded fixtures and asserts identical
//! results (the equivalence gate — a mismatch `std::process::exit(1)`s).
//!
//! [Q1] Contact JSONB decode on REAL rows (contact_pg §J1): fetch a seeded
//! address book's contacts once, then decode the `email`/`phone`/`address`
//! JSONB columns BEFORE (`row.get::<Value>` + `from_value`) vs AFTER
//! (`row.try_get::<sqlx::types::Json<Vec<Dto>>>`). Gate: identical decode.
//!
//! [Q4] `get_user_profile` (§P1): two independent point reads of the caller +
//! target users, BEFORE serial (`await` then `await`) vs AFTER concurrent
//! (`tokio::join!`). Gate: identical rows.
//!
//! [Q6] `subject_group::remove_member` (§G1): the child group's transitive
//! user set (a recursive CTE) BEFORE computed TWICE (the shipped-before
//! pre-check + `invalidation_targets`) vs AFTER once + reused. Gate:
//! identical user set.
//!
//! Run (needs the dev Postgres up; reads DATABASE_URL from .env):
//! cargo run --release --features bench --example bench_round23_queries
//! Tunables (env): BENCH_PASSES (200), Q1_CONTACTS (500), Q1_DECODE_PASSES (4000)
use std::env;
use std::time::Instant;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sqlx::postgres::PgPoolOptions;
use sqlx::{PgPool, Row};
use uuid::Uuid;
fn env_or<T: std::str::FromStr>(key: &str, default: T) -> T {
env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
fn p50(mut samples: Vec<f64>) -> f64 {
samples.sort_by(|a, b| a.partial_cmp(b).unwrap());
samples[samples.len() / 2]
}
fn report(tag: &str, unit: &str, before: f64, after: f64) {
println!(
"| {:<44} | {:>12} | {:>12} | {:>7} |",
tag, "BEFORE", "AFTER", "speedup"
);
println!(
"| {:<44} | {:>12.1} | {:>12.1} | {:>6.2}x |",
unit,
before,
after,
before / after
);
}
// ── Verbatim replicas of contact_persistence_dto.rs ──────────────────────────
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct EmailDto {
email: String,
r#type: String,
is_primary: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct PhoneDto {
number: String,
r#type: String,
is_primary: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct AddressDto {
street: Option<String>,
city: Option<String>,
state: Option<String>,
postal_code: Option<String>,
country: Option<String>,
r#type: String,
is_primary: bool,
}
async fn cleanup(pool: &PgPool) {
// Idempotent teardown (also clears any fixtures a prior crashed run left).
// Memberships first (FK to both groups and users), targeted by the bench
// group names so it catches them whoever `added_by` is.
let _ = sqlx::query(
"DELETE FROM auth.subject_group_members WHERE group_id IN
(SELECT id FROM auth.subject_groups
WHERE name IN ('bench23parent','bench23child','bench23grand'))",
)
.execute(pool)
.await;
let _ = sqlx::query(
"DELETE FROM auth.subject_groups WHERE name IN ('bench23parent','bench23child','bench23grand')",
)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM carddav.contacts WHERE uid LIKE 'bench23-%'")
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM carddav.address_books WHERE name = 'bench23_ab'")
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM auth.users WHERE email LIKE 'bench23-%@bench.invalid'")
.execute(pool)
.await;
}
async fn seed_user(pool: &PgPool, tag: &str) -> Uuid {
sqlx::query_scalar(
"INSERT INTO auth.users (username, email, role)
VALUES ($1, $2, 'user') RETURNING id",
)
.bind(format!("bench23_{tag}"))
.bind(format!("bench23-{tag}@bench.invalid"))
.fetch_one(pool)
.await
.expect("seed user")
}
// ── [Q1] Contact JSONB decode ────────────────────────────────────────────────
async fn section_q1(pool: &PgPool) {
let n: usize = env_or("Q1_CONTACTS", 500);
let passes: usize = env_or("Q1_DECODE_PASSES", 4000);
let owner = seed_user(pool, "q1owner").await;
let ab: Uuid = sqlx::query_scalar(
"INSERT INTO carddav.address_books (id, name, owner_id)
VALUES (gen_random_uuid(), 'bench23_ab', $1) RETURNING id",
)
.bind(owner)
.fetch_one(pool)
.await
.expect("seed address book");
for i in 0..n {
let emails = serde_json::to_value(vec![
EmailDto {
email: format!("user{i}@example.com"),
r#type: "home".into(),
is_primary: true,
},
EmailDto {
email: format!("user{i}@work.example.com"),
r#type: "work".into(),
is_primary: false,
},
])
.unwrap();
let phones = serde_json::to_value(vec![PhoneDto {
number: format!("+1-555-01{i:04}"),
r#type: "cell".into(),
is_primary: true,
}])
.unwrap();
let addrs = serde_json::to_value(vec![AddressDto {
street: Some(format!("{} Main St", 100 + i)),
city: Some("Springfield".into()),
state: Some("IL".into()),
postal_code: Some("62704".into()),
country: Some("US".into()),
r#type: "home".into(),
is_primary: true,
}])
.unwrap();
sqlx::query(
"INSERT INTO carddav.contacts (id, address_book_id, uid, full_name, email, phone, address, etag)
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7)",
)
.bind(ab)
.bind(format!("bench23-{i}"))
.bind(format!("Contact {i}"))
.bind(&emails)
.bind(&phones)
.bind(&addrs)
.bind(format!("etag-{i}"))
.execute(pool)
.await
.expect("seed contact");
}
// Fetch the rows ONCE (the query round-trip is out of the measured window —
// we isolate the per-row decode, which is what §J1 changes).
let rows = sqlx::query(
"SELECT email, phone, address FROM carddav.contacts
WHERE address_book_id = $1 ORDER BY uid",
)
.bind(ab)
.fetch_all(pool)
.await
.expect("fetch contacts");
assert_eq!(rows.len(), n, "Q1 seeded row count");
// BEFORE: Value DOM + from_value per column.
let decode_before =
|rows: &[sqlx::postgres::PgRow]| -> Vec<(Vec<EmailDto>, Vec<PhoneDto>, Vec<AddressDto>)> {
rows.iter()
.map(|r| {
let ev: Value = r.get("email");
let pv: Value = r.get("phone");
let av: Value = r.get("address");
(
serde_json::from_value::<Vec<EmailDto>>(ev).unwrap_or_default(),
serde_json::from_value::<Vec<PhoneDto>>(pv).unwrap_or_default(),
serde_json::from_value::<Vec<AddressDto>>(av).unwrap_or_default(),
)
})
.collect()
};
// AFTER: typed Json<T> decode straight from the JSONB bytes.
let decode_after =
|rows: &[sqlx::postgres::PgRow]| -> Vec<(Vec<EmailDto>, Vec<PhoneDto>, Vec<AddressDto>)> {
rows.iter()
.map(|r| {
(
r.try_get::<sqlx::types::Json<Vec<EmailDto>>, _>("email")
.map(|j| j.0)
.unwrap_or_default(),
r.try_get::<sqlx::types::Json<Vec<PhoneDto>>, _>("phone")
.map(|j| j.0)
.unwrap_or_default(),
r.try_get::<sqlx::types::Json<Vec<AddressDto>>, _>("address")
.map(|j| j.0)
.unwrap_or_default(),
)
})
.collect()
};
// Equivalence gate.
if decode_before(&rows) != decode_after(&rows) {
eprintln!("GATE FAIL [Q1]: BEFORE/AFTER decode differ — rollback");
cleanup(pool).await;
std::process::exit(1);
}
let mut b = Vec::with_capacity(passes);
let mut a = Vec::with_capacity(passes);
for _ in 0..passes / 20 {
std::hint::black_box(decode_before(&rows));
std::hint::black_box(decode_after(&rows));
}
for _ in 0..passes {
let t = Instant::now();
std::hint::black_box(decode_before(&rows));
b.push(t.elapsed().as_nanos() as f64 / n as f64);
let t = Instant::now();
std::hint::black_box(decode_after(&rows));
a.push(t.elapsed().as_nanos() as f64 / n as f64);
}
println!(
"\n## [Q1] Contact JSONB decode on real rows ({n} contacts) — gate OK (identical decode)"
);
report(
"Value DOM + from_value vs Json<T>",
"p50 ns/contact",
p50(b),
p50(a),
);
}
// ── [Q4] get_user_profile: serial vs join! ──────────────────────────────────
async fn section_q4(pool: &PgPool) {
let passes: usize = env_or("BENCH_PASSES", 200);
let caller = seed_user(pool, "q4caller").await;
let target = seed_user(pool, "q4target").await;
// Capture `pool` (not take it as a param) so the returned future borrows a
// single concrete lifetime — a closure param `&PgPool` + future return hits
// the HRTB limitation.
let read = |id: Uuid| async move {
sqlx::query("SELECT id, email, role FROM auth.users WHERE id = $1")
.bind(id)
.fetch_optional(pool)
.await
.expect("read user")
.map(|r| r.get::<Uuid, _>("id"))
};
// Equivalence gate: same two ids either way.
let ser = (read(caller).await, read(target).await);
let (jc, jt) = tokio::join!(read(caller), read(target));
if ser != (jc, jt) {
eprintln!("GATE FAIL [Q4]: serial/join ids differ — rollback");
cleanup(pool).await;
std::process::exit(1);
}
let mut b = Vec::with_capacity(passes);
let mut a = Vec::with_capacity(passes);
for _ in 0..(passes / 20).max(1) {
let _ = (read(caller).await, read(target).await);
let _ = tokio::join!(read(caller), read(target));
}
for _ in 0..passes {
let t = Instant::now();
let _ = std::hint::black_box((read(caller).await, read(target).await));
b.push(t.elapsed().as_nanos() as f64);
let t = Instant::now();
let _ = std::hint::black_box(tokio::join!(read(caller), read(target)));
a.push(t.elapsed().as_nanos() as f64);
}
println!("\n## [Q4] get_user_profile caller+target reads — gate OK (identical ids)");
report(
"2 serial reads vs tokio::join!",
"p50 ns/call",
p50(b),
p50(a),
);
}
// ── [Q6] subject_group child transitive users: 2 CTEs vs 1 ───────────────────
async fn section_q6(pool: &PgPool) {
let passes: usize = env_or("BENCH_PASSES", 200);
// Tree: parent → child → {grandchild, u2}; grandchild → u3. u1 direct on parent.
let u1 = seed_user(pool, "q6u1").await;
let u2 = seed_user(pool, "q6u2").await;
let u3 = seed_user(pool, "q6u3").await;
let mk_group = |name: &'static str| async move {
sqlx::query_scalar::<_, Uuid>(
"INSERT INTO auth.subject_groups (name) VALUES ($1) RETURNING id",
)
.bind(name)
.fetch_one(pool)
.await
.expect("seed group")
};
let parent = mk_group("bench23parent").await;
let child = mk_group("bench23child").await;
let grand = mk_group("bench23grand").await;
let add_ug = |g: Uuid, u: Uuid| async move {
sqlx::query("INSERT INTO auth.subject_group_members (group_id, member_user_id, added_by) VALUES ($1, $2, $3)")
.bind(g).bind(u).bind(u1).execute(pool).await.expect("add user member");
};
let add_gg = |g: Uuid, c: Uuid| async move {
sqlx::query("INSERT INTO auth.subject_group_members (group_id, member_group_id, added_by) VALUES ($1, $2, $3)")
.bind(g).bind(c).bind(u1).execute(pool).await.expect("add group member");
};
add_ug(parent, u1).await;
add_gg(parent, child).await;
add_gg(child, grand).await;
add_ug(child, u2).await;
add_ug(grand, u3).await;
let cte = |gid: Uuid| async move {
let rows = sqlx::query(
"WITH RECURSIVE descendants AS (
SELECT $1::uuid AS g
UNION
SELECT m.member_group_id FROM auth.subject_group_members m
JOIN descendants d ON m.group_id = d.g WHERE m.member_group_id IS NOT NULL)
SELECT DISTINCT m.member_user_id AS user_id FROM auth.subject_group_members m
JOIN descendants d ON m.group_id = d.g WHERE m.member_user_id IS NOT NULL",
)
.bind(gid)
.fetch_all(pool)
.await
.expect("cte");
let mut ids: Vec<Uuid> = rows.iter().map(|r| r.get::<Uuid, _>("user_id")).collect();
ids.sort();
ids
};
// Equivalence: the child's transitive set is {u2, u3}, and it is IDENTICAL
// whether computed once or twice (the edge delete above the child cannot
// change its descendants — the §G1 correctness claim).
let once = cte(child).await;
let twice = {
let _first = cte(child).await;
cte(child).await
};
let mut expected = [u2, u3];
expected.sort();
if once != twice || once != expected {
eprintln!("GATE FAIL [Q6]: child transitive set not stable/expected — rollback");
cleanup(pool).await;
std::process::exit(1);
}
let mut b = Vec::with_capacity(passes);
let mut a = Vec::with_capacity(passes);
for _ in 0..(passes / 20).max(1) {
let _ = (cte(child).await, cte(child).await);
let _ = cte(child).await;
}
for _ in 0..passes {
// BEFORE: the child CTE runs TWICE (pre-check + invalidation_targets).
let t = Instant::now();
let _ = cte(child).await;
let _ = std::hint::black_box(cte(child).await);
b.push(t.elapsed().as_nanos() as f64);
// AFTER: once, reused.
let t = Instant::now();
let _ = std::hint::black_box(cte(child).await);
a.push(t.elapsed().as_nanos() as f64);
}
println!("\n## [Q6] subject_group child transitive users — gate OK (stable set {{u2,u3}})");
report(
"2 recursive CTEs vs 1 (reused)",
"p50 ns/removal",
p50(b),
p50(a),
);
}
#[tokio::main(flavor = "multi_thread")]
async fn main() {
dotenvy::dotenv().ok();
let url = env::var("DATABASE_URL")
.or_else(|_| env::var("OXICLOUD_DB_CONNECTION_STRING"))
.expect("set DATABASE_URL — the dev Postgres URL");
let pool = PgPoolOptions::new()
.max_connections(8)
.connect(&url)
.await
.expect("connect Postgres");
println!("# Round-23 PG query-shape pack — BEFORE/AFTER (live Postgres)");
println!("# Each section asserts an equivalence gate (mismatch → exit 1) and reports p50.");
cleanup(&pool).await;
section_q1(&pool).await;
section_q4(&pool).await;
section_q6(&pool).await;
cleanup(&pool).await;
println!("\nAll Round-23 query sections passed their equivalence gate.");
}