scripts/bench_manifest_version_pk.ts
Rama main · réplica de lectura del repositorio original.
/**
* Primary-key version lookup vs scanning every version of the app.
*
* Uses the local bench Postgres already on 127.0.0.1:55433.
* Creates schema pk_bench only. Does not touch public.
*
* bun scripts/bench_manifest_version_pk.ts
*/
import { writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { performance } from 'node:perf_hooks'
import { Client } from 'pg'
import { buildManifestSizeLookupQuery } from '../supabase/functions/_backend/utils/manifest_size.ts'
const DATABASE_URL = 'postgres://postgres:postgres@127.0.0.1:55433/manifest_size_bench'
const APP_ID = 'com.bench.pk'
const LIVE_VERSION_ID = 1
const DELETED_VERSIONS = 2000
const LIVE_VERSIONS = 150
const FILES = 400
const RUNS = 5
const SCAN_SQL = `
WITH requested AS MATERIALIZED (
SELECT DISTINCT file_hash, version_id
FROM jsonb_to_recordset($1::jsonb) AS request_files(file_hash text, version_id bigint)
WHERE file_hash IS NOT NULL
)
SELECT r.file_hash, av.id AS version_id, MAX(m.file_size) AS file_size
FROM requested r
INNER JOIN public.app_versions av
ON av.id = r.version_id
AND av.app_id = $2
AND av.deleted = false
INNER JOIN public.manifest m
ON m.app_version_id = av.id
AND m.file_hash = r.file_hash
WHERE r.version_id IS NOT NULL
GROUP BY r.file_hash, av.id
`
function assertSafeUrl(databaseUrl: string) {
const parsed = new URL(databaseUrl)
const dbName = decodeURIComponent(parsed.pathname.replace(/^\/+/, ''))
if (parsed.hostname !== '127.0.0.1' || parsed.port !== '55433' || dbName !== 'manifest_size_bench')
throw new Error(`Refusing bench against ${parsed.hostname}:${parsed.port}/${dbName}`)
}
function median(values: number[]): number {
const sorted = [...values].sort((a, b) => a - b)
return sorted[Math.floor(sorted.length / 2)] ?? 0
}
async function timeQuery(client: Client, sql: string, params: unknown[]) {
await client.query(`EXPLAIN (ANALYZE, BUFFERS) ${sql}`, params)
const timesMs: number[] = []
let rows = 0
for (let i = 0; i < RUNS; i++) {
const started = performance.now()
const result = await client.query(sql, params)
timesMs.push(performance.now() - started)
rows = result.rowCount ?? 0
}
return {
rows,
medianMs: Math.round(median(timesMs) * 10) / 10,
timesMs: timesMs.map(value => Math.round(value * 10) / 10),
}
}
async function explain(client: Client, sql: string, params: unknown[]) {
const result = await client.query(`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${sql}`, params)
return result.rows.map((row: { 'QUERY PLAN': string }) => row['QUERY PLAN']).join('\n')
}
function planLine(explainText: string, pattern: RegExp) {
return explainText.split('\n').map(line => line.trim()).find(line => pattern.test(line)) ?? ''
}
async function main() {
assertSafeUrl(DATABASE_URL)
const client = new Client({ connectionString: DATABASE_URL, statement_timeout: 120_000 })
await client.connect()
await client.query('SET jit = off')
await client.query('DROP SCHEMA IF EXISTS pk_bench CASCADE')
await client.query('CREATE SCHEMA pk_bench')
await client.query('SET search_path = pk_bench, public')
await client.query(`
CREATE TABLE pk_bench.app_versions (
id bigint PRIMARY KEY,
app_id text NOT NULL,
name text NOT NULL,
deleted boolean NOT NULL DEFAULT false
);
CREATE TABLE pk_bench.manifest (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
app_version_id bigint NOT NULL,
file_hash text NOT NULL,
file_size bigint NOT NULL
);
CREATE INDEX idx_app_id_app_versions ON pk_bench.app_versions (app_id);
`)
await client.query(`
INSERT INTO pk_bench.app_versions (id, app_id, name, deleted)
SELECT v, 'com.bench.other.' || v::text, '1.0.0', false
FROM generate_series(100000, 100499) AS v
`)
await client.query('ANALYZE pk_bench.app_versions')
await client.query(`
INSERT INTO pk_bench.app_versions (id, app_id, name, deleted)
SELECT v, $1, '1.0.0', v > $2
FROM generate_series(1, $2::int + $3::int) AS v
`, [APP_ID, LIVE_VERSIONS, DELETED_VERSIONS])
await client.query(`
INSERT INTO pk_bench.manifest (app_version_id, file_hash, file_size)
SELECT 1, 'hash-' || f, f
FROM generate_series(1, $1::int) AS f
`, [FILES])
await client.query(`
CREATE INDEX idx_manifest_app_version_id_file_hash
ON pk_bench.manifest (app_version_id, file_hash) INCLUDE (file_size);
ANALYZE pk_bench.manifest;
`)
const files = Array.from({ length: FILES }, (_, index) => ({
file_hash: `hash-${index + 1}`,
version_id: LIVE_VERSION_ID,
}))
const scanSql = SCAN_SQL.replaceAll('public.', 'pk_bench.')
const lookup = buildManifestSizeLookupQuery(APP_ID, undefined, undefined, files.map(file => ({
file_name: file.file_hash,
file_hash: file.file_hash,
download_url: null,
version_id: file.version_id,
})))
if (!lookup)
throw new Error('lookup builder returned null')
const pkSql = lookup.text.replaceAll('public.', 'pk_bench.')
const before = await timeQuery(client, scanSql, lookup.values)
const beforePlan = await explain(client, scanSql, lookup.values)
const after = await timeQuery(client, pkSql, lookup.values)
const afterPlan = await explain(client, pkSql, lookup.values)
await client.query('DROP SCHEMA IF EXISTS pk_bench CASCADE')
await client.end()
const summary = [
'# Version primary-key lookup',
'',
`Local Postgres on 127.0.0.1:55433, schema pk_bench, then dropped. Not production.`,
'',
`Seed: ${DELETED_VERSIONS} deleted versions and ${LIVE_VERSIONS} live versions of \`${APP_ID}\`, ${FILES} files on version ${LIVE_VERSION_ID}.`,
'',
'Before joins `app_versions` on `app_id` plus `id`. Stats are frozen before the fat app is inserted, matching the production misestimate. After is the `LATERAL` primary-key lookup from `buildManifestSizeLookupQuery`.',
'',
`Median of ${RUNS} hot runs after one warmup. jit off.`,
'',
'| | Median | Rows |',
'| --- | ---: | ---: |',
`| Scan every version | ${before.medianMs} ms | ${before.rows} |`,
`| Primary-key lookup | ${after.medianMs} ms | ${after.rows} |`,
'',
'## Scan plan',
'',
planLine(beforePlan, /Bitmap Heap Scan on app_versions|Execution Time/),
'',
'```',
beforePlan,
'```',
'',
'## Primary-key plan',
'',
planLine(afterPlan, /Index Scan using app_versions_pkey|Index Only Scan using idx_manifest/),
'',
'```',
afterPlan,
'```',
'',
].join('\n')
const summaryPath = resolve(import.meta.dirname, 'bench/manifest_version_pk_summary.md')
writeFileSync(summaryPath, summary)
console.log(summary)
console.log(`Wrote ${summaryPath}`)
}
main().catch((error) => {
console.error(error)
process.exit(1)
})