Files
windmill/benchmarks/model_batch_vs_sql.ts
HugoCasa 8c3ac22d8d feat: add as_worker_tag() helper, benchmark results and model
- Extract bunnative→nativets tag logic into ScriptLang::as_worker_tag()
- Add benchmark results for batch pull vs direct SQL (1W and 3W)
- Add throughput model script comparing batch vs SQL at scale
- Add nativets_sleep benchmark script support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:05:15 +01:00

166 lines
5.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Model: Batch Pull vs Direct SQL throughput
*
* Calibrated from real benchmarks (3 native workers = 24 subworkers, local PG):
* - nativets (fast): batch 291 j/s, SQL 253 j/s at N=24; batch 108, SQL 88 at N=8
* - nativets_sleep: both ~43.8 j/s at N=24 (bottlenecked by 500ms avg exec time)
*
* Per-worker job time model:
* T_pw = T_base + T_exec + T_contention(N)
* throughput = N / T_pw
*
* Batch: T_contention grows linearly with N (HTTP server load)
* T_pw_batch(N) = BASE_BATCH + T_exec + SCALE_BATCH × N
*
* SQL: T_contention grows quadratically with N (SKIP LOCKED scanning past locked rows)
* T_pw_sql(N) = BASE_SQL + T_exec + SCALE_SQL ×
*
* Parameters fitted from 2 data points each (N=8, N=24):
* Batch: BASE=69.9ms, SCALE=0.525ms/worker
* SQL: BASE=90.4ms, SCALE=0.0078ms/worker²
* (SQL quadratic overtakes batch linear around N~40)
*/
// --- Model parameters (fitted from benchmarks) ---
// Batch: per-worker time = BASE + SCALE_LINEAR * N + T_exec
const BASE_BATCH = 69.9; // ms — base overhead (worker loop, HTTP roundtrip, job completion writes)
const SCALE_BATCH = 0.525; // ms per subworker — linear growth from server load
// SQL: per-worker time = BASE + SCALE_QUAD * N² + T_exec
const BASE_SQL = 90.4; // ms — base overhead (worker loop, poll interval wait, job completion writes)
const SCALE_SQL = 0.0078; // ms per subworker² — quadratic growth from SKIP LOCKED contention
// --- Throughput functions ---
function throughputBatch(subworkers: number, execMs: number): number {
const tPerWorker = BASE_BATCH + execMs + SCALE_BATCH * subworkers;
return (subworkers / tPerWorker) * 1000; // jobs/s
}
function throughputSql(subworkers: number, execMs: number): number {
const tPerWorker = BASE_SQL + execMs + SCALE_SQL * subworkers * subworkers;
return (subworkers / tPerWorker) * 1000; // jobs/s
}
function pct(batch: number, sql: number): string {
const diff = ((batch - sql) / sql) * 100;
return `${diff >= 0 ? "+" : ""}${diff.toFixed(0)}%`;
}
// --- Validation against real data ---
console.log("=== Model Validation (vs real benchmarks) ===\n");
console.log(
" Setup | Model Batch | Real Batch | Model SQL | Real SQL",
);
console.log(
" ---------------------|-------------|------------|-----------|--------",
);
const cases = [
{ n: 8, exec: 0, label: "1W nativets", realBatch: 108, realSql: 88 },
{ n: 24, exec: 0, label: "3W nativets", realBatch: 291, realSql: 253 },
{
n: 24,
exec: 500,
label: "3W sleep(500ms)",
realBatch: 43.8,
realSql: 43.8,
},
];
for (const c of cases) {
const mb = throughputBatch(c.n, c.exec);
const ms = throughputSql(c.n, c.exec);
console.log(
` ${c.label.padEnd(21)}| ${mb.toFixed(0).padStart(7)} j/s | ${c.realBatch.toFixed(0).padStart(6)} j/s | ${ms.toFixed(0).padStart(5)} j/s | ${c.realSql.toFixed(0).padStart(4)} j/s`,
);
}
// --- Projections ---
const workerCounts = [1, 2, 3, 5, 8, 10, 15, 20]; // native workers (×8 subworkers each)
const execTimes = [
{ ms: 0, label: "~0ms (identity)" },
{ ms: 5, label: "5ms" },
{ ms: 20, label: "20ms" },
{ ms: 50, label: "50ms" },
{ ms: 200, label: "200ms" },
{ ms: 500, label: "500ms" },
];
console.log("\n\n=== Projected Throughput (jobs/s) ===\n");
for (const exec of execTimes) {
console.log(`--- Job duration: ${exec.label} ---\n`);
console.log(
" Native workers (subw) | Batch | SQL | Advantage | Batch wins?",
);
console.log(
" ----------------------|-----------|----------|-------------|------------",
);
for (const w of workerCounts) {
const n = w * 8;
const b = throughputBatch(n, exec.ms);
const s = throughputSql(n, exec.ms);
const advantage = pct(b, s);
const wins = b > s * 1.05 ? " YES" : b > s * 1.01 ? " marginal" : " no";
console.log(
` ${String(w).padStart(2)}W (${String(n).padStart(3)}) | ${b.toFixed(0).padStart(5)} j/s | ${s.toFixed(0).padStart(5)} j/s | ${advantage.padStart(8)} | ${wins}`,
);
}
console.log();
}
// --- Crossover analysis ---
console.log("=== Crossover: min workers where batch is >10% faster ===\n");
console.log(" Job duration | Min workers | Subworkers | Batch j/s | SQL j/s");
console.log(" -------------|-------------|------------|-----------|--------");
for (const exec of execTimes) {
let found = false;
for (let w = 1; w <= 50; w++) {
const n = w * 8;
const b = throughputBatch(n, exec.ms);
const s = throughputSql(n, exec.ms);
if (b > s * 1.1) {
console.log(
` ${exec.label.padEnd(13)}| ${String(w).padStart(5)}W | ${String(n).padStart(5)} | ${b.toFixed(0).padStart(5)} j/s | ${s.toFixed(0).padStart(5)} j/s`,
);
found = true;
break;
}
}
if (!found) {
console.log(
` ${exec.label.padEnd(13)}| >50W (never significant at this job duration)`,
);
}
}
console.log("\n\n=== Key Takeaways ===\n");
console.log(
"1. For fast jobs (~0ms): batch pull is always faster, advantage grows with scale",
);
console.log(" - 5 native workers (40 subworkers): ~13% faster");
console.log(" - 10 native workers (80 subworkers): ~25% faster");
console.log(" - 20 native workers (160 subworkers): ~88% faster");
console.log(
"2. For medium jobs (50ms): batch advantage meaningful from ~5 native workers",
);
console.log(
"3. For slow jobs (500ms+): only matters at 15+ native workers (120+ subworkers)",
);
console.log(
" (but still reduces DB load — fewer pull queries, less index scanning)",
);
console.log(
"4. The SQL quadratic contention (SKIP LOCKED scanning) is the dominant factor",
);
console.log(
" — SQL throughput plateaus around 15-20 native workers while batch keeps scaling",
);