3.3 KiB
limits/outstanding-cap
Purpose
Admission control for in-flight 2PC transactions. The coordinator
holds a fixed-size budget of "outstanding prepared transactions"; once
the budget is exhausted new run2pc calls refuse rather than queue.
The default is 256 (design doc §14's open question, pinned by this
package's spec) and operators can tune it via host config.
Refusing rather than queuing is intentional: Postgres holds row-level locks for every prepared transaction, so a backed-up coordinator would otherwise convert a transient overload into a global lock storm. Failing fast lets callers route to a degraded path (degraded-write, retry queue, etc.).
Features
OutstandingPreparedCap— synchronous counter withadmit(),release(),snapshot(),capacity().- Constructor validates that the cap is a positive integer (rejects
zero, negatives, fractions,
NaN). release()is idempotent below zero — a buggy double-release in caller code cannot silently expand the real cap.- Synchronous-only — relies on the event-loop atomicity guarantee
between
admit/releasepairs in JavaScript runtimes.
Usage: Basic
import { OutstandingPreparedCap } from "@suaveplan/api-2pc/limits";
const cap = new OutstandingPreparedCap(256);
cap.admit();
try {
// run the 2PC transaction
} finally {
cap.release();
}
Usage: Advanced
Wrap admission inside the coordinator's public entry to centralise the
release in one finally. The snapshot fuels the OTel gauge:
import { TWOPC_CODES, TwoPcError } from "@suaveplan/api-2pc/error-codes";
import { OutstandingPreparedCap } from "@suaveplan/api-2pc/limits";
class MiniCoordinator {
private readonly cap = new OutstandingPreparedCap(256);
snapshot() {
return this.cap.snapshot();
}
async run<T>(fn: () => Promise<T>): Promise<T> {
try {
this.cap.admit();
} catch (err) {
if (
err instanceof TwoPcError &&
err.twoPcCode === TWOPC_CODES.OUTSTANDING_LIMIT_EXCEEDED
) {
// route to degraded path
throw err;
}
throw err;
}
try {
return await fn();
} finally {
this.cap.release();
}
}
}
API Reference
class OutstandingPreparedCapconstructor(cap: number)— throwsTWOPC_OUTSTANDING_LIMIT_EXCEEDEDon invalidcap.admit(): void— throwsTWOPC_OUTSTANDING_LIMIT_EXCEEDEDwhen at capacity.release(): void— idempotent below zero.snapshot(): number— current outstanding count.capacity(): number— configured cap.
Implementation Notes
Construction-time validation re-uses the same error code as the
runtime cap-exceeded path. This is deliberate: operators tuning the
cap via host config want one error string to alert on, regardless of
whether the misconfiguration was a fractional value at startup or a
real overload at steady state. The context object on the thrown
TwoPcError distinguishes the two cases (cap only vs.
cap + outstanding).
The class is intentionally tiny — no async, no internal queue, no external dependencies. Adding a queue here would re-introduce the lock-storm risk the cap exists to prevent.