Skip to content

F21: bound and serialize Graphile builds with watchdog and drain - #1855

Open
Zetazzz wants to merge 5 commits into
mainfrom
graphile-build-coordinator-c8f5bfd9
Open

Zetazzz wants to merge 5 commits into
mainfrom
graphile-build-coordinator-c8f5bfd9

Conversation

@Zetazzz

@Zetazzz Zetazzz commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Distinct concurrent cold schema misses now enter one process-wide FIFO coordinator for cached Server and Explorer producers. One active permit covers admission, eviction/disposal waits, allocation, readiness, publication and cleanup; the waiting queue defaults to 16. Exact-key calls share a flight.

Rebased onto main 299b3920; tested head ab590b19. This main-based PR includes F04/F17/F19/F20 prerequisites, including #1853's commits. Both PRs target main, but their diffs overlap.

Measured conclusion: serialization and queueing work. This workload shows no stable general speedup or meaningful RSS reduction; the demonstrated value is controlling concurrent cold builds and accepting a burst through a bounded queue.

The watchdog (default 300000 ms) fences late publication without pretending to cancel JavaScript or releasing the permit before actual work settles. Watchdog expiry, disposal failure and drain timeout permanently latch the coordinator unavailable until restart. Shutdown fences before HTTP close and uses one drain deadline (default 30000 ms); pools stay open while actual build work remains. Main's existing measured heap budget remains enforced by the admitted path. The uncached single-instance dev server and full request/WebSocket retirement are outside this change.

Measured medians, three runs per row; elapsed time completes all requested builds and validation reads:

Scenario main time (ms) F19 time (ms) F21 time (ms) main / F19 / F21 peak RSS (MiB)
Loose, 1 cold key 1504 1558 1416 176.12 / 177.39 / 177.55
Loose, 4 cold keys 3498 3333 4042 243.73 / 250.80 / 244.53
Loose, 8 cold keys 7686 6660 7126 285.27 / 291.04 / 293.18
Tight, 8 cold keys, all completed 7130 6313 7197 292.30 / 294.87 / 291.07

With 8 cold keys, main/F19/F21 peak concurrent creates were 8/8/1 at loose capacity and 8/4/1 at tight capacity. F21 queued up to 7 and completed all 8 initial calls without capacity rejection. F19 at tight capacity completed 4 initial calls and rejected 4 before allocation; one immediate in-process retry wave after the initial wave settled completed the remainder.

All-completed timing includes initial rejection time, waiting for that wave, retries and scheduling, starting before the initial 8 calls. No client/network/backoff delay is added; real retry latency may be higher. RSS includes every retry wave.

F21's loose-8 median was 7.3% lower than main, but loose-4 was 15.5% higher. Loose-8 ranges overlap: main 6580–7810 ms, F19 6100–7496 ms, F21 6240–7610 ms. These samples do not justify a general speedup claim; RSS was essentially unchanged or higher.

Measured on 2026-11-04: Node 24.20.0, PostgreSQL 18.6, 3 available CPUs, approximately 4 GiB RAM, --expose-gc --max-old-space-size=768. Each fresh worker builds a 24-table schema and validates a seeded GraphQL read. Three repetitions per arm/scenario, randomized sequential order; 36/36 runs passed across main/F19/F21. Every successful build produced the same schema hash and query result.

Loose capacity is 50 residents; tight capacity is 4. Both measured resident heap budget and process heap watermark are set to 4 GiB, with a 64 MiB build reserve, to isolate count/coordinator behavior. This does not test default heap-watermark tuning or OOM prevention. No subscriptions are used.

The validation read runs inside the create callback before publication to prevent eviction racing validation. Consequently, permit occupancy includes query time: this is a cache-layer cold build-and-query microbenchmark, not production HTTP latency/throughput or a bound on all SQL concurrency. RSS peaks cover the entire run, including retry waves.

Validation at the rebased head:

  • CI: 17/17 jobs passed, manually dispatched on the exact head after retargeting.
  • Cache: 100 tests; graphql-env: 94 tests plus snapshot; errors: 56 tests; focused server: 38 tests. Unchanged express-context source also passed 135 tests during F19 validation.
  • Real PostgreSQL/scoped-routing HTTP scenarios: 7/7 passed.
  • Relevant CJS/ESM package builds, frozen-lockfile install and diff checks passed.

Raw measurements, samples and runtime/source provenance are retained in the task workspace at /workspace/scratch/admission-rebase/bench/results-24.json; grouped medians/ranges are in results-24-summary.json, alongside the worker, runner and analysis scripts.

@tenki-reviewer

tenki-reviewer Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Review complete. 🟡 1 medium

💬 Inline comments (1)


The PR adds a build lifecycle coordinator that permits, queues, watchdogs, and drains Graphile schema builds, then threads the shared coordinator through the cache, middleware, and server so builds are fenced before shutdown and bounded under load. A single high-confidence issue surfaced: a routine disposal failure now latches the coordinator into a permanent stuck state, bricking all future builds for the process lifetime. The coordinator core (permit/queue/watchdog/drain) is otherwise careful and well-tested; two lower-confidence shutdown-path concerns (deadline throw skipping pool close, shared-coordinator reconfigure) were reviewed but not retained.

Files Change
graphile/graphile-cache/src/build-coordinator.ts, build-flights.ts, admitted-build.ts, graphile-cache.ts, index.ts Add the coordinator state machine and wire it into build admission, disposal, and cache lifecycle.
graphile/graphile-cache/src/__tests__/* Add concurrency and fake-timer tests for coordinated build/admission/drain paths.
graphql/env/src/*, graphql/types/src/* Extend env/config merge and validation for the new build-coordination settings.
graphql/server/src/server.ts, middleware/graphile.ts, graphql/explorer/src/server.ts Fence builds before shutdown and configure the shared coordinator from server options.
packages/errors/src/registry.ts Register new lifecycle error codes (SCHEMA_BUILD_STUCK, QUEUE_FULL, DRAIN_TIMEOUT) and test coverage.

Reviewed commit: 51f923f

@tenki-reviewer tenki-reviewer Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces a GraphileBuildCoordinator that serializes, bounds, and drains Graphile schema builds across the graphile-cache, GraphQL server, and explorer, with a new error-code registry and env config plumbing.

Key findings

@Zetazzz
Zetazzz force-pushed the graphile-build-coordinator-c8f5bfd9 branch from 031a1b0 to ab590b1 Compare November 4, 2026 05:24
@Zetazzz
Zetazzz changed the base branch from graphile-build-coalescing-c8f5bfd9 to main November 4, 2026 05:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant