mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 00:04:10 +00:00
perf: add inline-persist fast path for WAC v2 step() (#8807)
This commit is contained in:
@@ -1494,6 +1494,16 @@ export class WorkflowCtx {
|
||||
private _suspended = false;
|
||||
/** When set, the task matching this key executes its inner function directly */
|
||||
_executingKey: string | null;
|
||||
/** Serializes fast-path POSTs across concurrent step() calls within one
|
||||
* workflow invocation. Wraps only the HTTP call, not fn() — so
|
||||
* `Promise.all([step("a", fn_a), step("b", fn_b)])` still runs the two
|
||||
* fn() bodies in parallel, only the API requests are ordered. This
|
||||
* closes the first-write race window against `SELECT FOR UPDATE` on a
|
||||
* not-yet-created `v2_job_status` row: two concurrent POSTs would both
|
||||
* see None and overwrite each other's checkpoint because the helper
|
||||
* writes the whole serialized `_checkpoint` object, not a single
|
||||
* `completed_steps[key]`. Initialized to a resolved promise. */
|
||||
private _inlineChain: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(checkpoint: Record<string, any> = {}) {
|
||||
this.completed = checkpoint?.completed_steps ?? {};
|
||||
@@ -1652,6 +1662,72 @@ export class WorkflowCtx {
|
||||
const t0 = Date.now();
|
||||
const result = await fn();
|
||||
const durationMs = Date.now() - t0;
|
||||
|
||||
// Fast path: POST the delta to the new per-job API endpoint and return the
|
||||
// result directly so the workflow subprocess continues into the next step()
|
||||
// without unwinding. Concurrent step() calls (e.g. inside Promise.all) run
|
||||
// their fn() bodies in parallel, then serialize the API POSTs via a
|
||||
// per-ctx promise chain (`this._inlineChain`). Serializing the POSTs is
|
||||
// required because the backend helper writes the whole serialized
|
||||
// `_checkpoint` object per call, and two concurrent writes against a
|
||||
// not-yet-created `v2_job_status` row would both see None under
|
||||
// `SELECT FOR UPDATE` and overwrite each other.
|
||||
//
|
||||
// On any failure — network, auth, timeout, source-hash mismatch, old
|
||||
// backend without the endpoint — fall through to throwing StepSuspend so
|
||||
// the worker takes the legacy suspend-and-replay path. Gated by
|
||||
// WM_WAC_INLINE_FAST_PATH (default on) so the old behavior stays
|
||||
// reachable for A/B testing and rollback.
|
||||
const fastPathFlagRaw = (getEnv("WM_WAC_INLINE_FAST_PATH") ?? "1").trim().toLowerCase();
|
||||
const fastPathEnabled =
|
||||
fastPathFlagRaw !== "0" &&
|
||||
fastPathFlagRaw !== "false" &&
|
||||
fastPathFlagRaw !== "off" &&
|
||||
fastPathFlagRaw !== "no";
|
||||
const jobId = getEnv("WM_JOB_ID");
|
||||
const workspace = getEnv("WM_WORKSPACE");
|
||||
if (fastPathEnabled && jobId && workspace && OpenAPI.BASE && OpenAPI.TOKEN) {
|
||||
const chainTail = this._inlineChain.then(async () => {
|
||||
const ctrl = new AbortController();
|
||||
const t = setTimeout(() => ctrl.abort(), 10_000);
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${OpenAPI.BASE}/w/${workspace}/jobs/wac/inline_checkpoint/${jobId}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${OpenAPI.TOKEN}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
key,
|
||||
result,
|
||||
started_at: startedAt,
|
||||
duration_ms: durationMs,
|
||||
}),
|
||||
signal: ctrl.signal,
|
||||
},
|
||||
);
|
||||
if (!resp.ok) {
|
||||
throw new Error(`inline_checkpoint API ${resp.status}`);
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(t);
|
||||
}
|
||||
});
|
||||
// Swallow chain errors so a past failure does not poison future awaits.
|
||||
this._inlineChain = chainTail.catch(() => {});
|
||||
try {
|
||||
await chainTail;
|
||||
return result as T;
|
||||
} catch (e) {
|
||||
console.log(
|
||||
`WAC v2 inline fast path failed for key ${key}, falling back to suspend: ${e}`,
|
||||
);
|
||||
// fall through to the legacy suspend path below
|
||||
}
|
||||
}
|
||||
|
||||
throw new StepSuspend({ mode: "inline_checkpoint", steps: [], key, result, started_at: startedAt, duration_ms: durationMs });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user