MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-3902

(motir-core) The Vitest lane is CI's critical path at 24.8 min — 1.6x the next-longest job — and the runners it would need to halve that are sitting idle (queue time is zero)

Done
Description

Type · chore (CI configuration) · Parent · MOTIR-1464, the epic holding motir-core's CI-infra class — MOTIR-2970, MOTIR-3569, MOTIR-3768 · Repo · motir-core · Measured · 2026-08-29, on runs 33245258081 (PR #2451), 33244655199 and 33225664810

The gap, measured

Per-job run time on run 33245258081, from gh api repos/moooon-B-V/motir-core/actions/runs/<id>/jobs:

jobrun
Vitest (3/3)24.8 min
Vitest (2/3)23.8 min
Vitest (1/3)23.4 min
Playwright E2E (bulk-3)11.3 min
Next.js build4.3 min
Lint + Prettier4.1 min
TypeScript2.6 min
Vitest coverage (merge)1.4 min

The critical path is changes → test → coverage26.2 min. The E2E path is 16.1 min. So the Vitest lane alone sets CI's wall clock, by 1.6x over the job behind it.

The cost is inside vitest run, not around it. Checkout + Postgres + install + migrate deploy is ~75 s; the pnpm vitest run … --shard step is 1320–1423 s across the three shards on all three runs read.

There is no long pole to fix and no imbalance to correct. Shard 3's own summary:

Test Files  454 passed (454)   Tests  6972 passed (6972)
Duration 1399.94s (transform 46.42s, setup 14.96s,
                   import 839.64s, tests 3963.61s, environment 58.74s)

4804 s of worker-CPU over 4 workers against 1400 s of wall = 86% worker utilisation. The slowest single file is tests/integration/plans/plansService.test.ts at 86 s against a mean of 8.7 s over 454 files, and the three shards ran 1196/1329/1380 s — a 19% spread. This is a volume problem, not a balance or tail problem.

The parallelism is capped at 4 and cannot be raised inside a job. TEST_DB_WORKERS is min(availableParallelism(), 8) (tests/helpers/parallelDb.ts) and availableParallelism() on ubuntu-latest is 4, so maxWorkers is 4 per shard. The only lever is more runners.

And the runners are free. Every job on run 33245258081 shows 0.0–0.1 min of queue time (started_at − created_at). Nothing is waiting for a runner, so shard count converts to wall clock almost linearly. Per-shard fixed cost is 75 s, so 3 → 8 shards costs ~6 extra runner-minutes on a 74-runner-minute lane while cutting it to ~10 min.

Acceptance criteria

  1. The test job's matrix runs 8 shards. Vitest (n/8) completes in ≲12 min on a green run, and the coverage merge job still gates the merged report at the same ≥90% per-file thresholds — the shards keep writing blob reports and coverage keeps enforcing on the merged result. No test is skipped, dropped or re-scoped: Tests N passed summed across the 8 shards equals the count the 3 shards report on the same SHA.
  2. The timeout-minutes comment on the test job is re-derived rather than carried over. Its current text justifies 75 from "20.1–24.9 min across three shards" and a 47.5-min outlier on run 32959226187 — both readings are of a 3-shard membership and say nothing about an 8-shard one. State the new observed range and the multiple applied, per the bar MOTIR-3569 set; the ceiling must not be tight enough to fail a slow-but-healthy run.
  3. tests/ci-job-timeouts.test.ts stays green — every runs-on job in the file still declares a timeout-minutes (MOTIR-3768).
  4. coverage.all is set to false on vitest.collect.config.ts (the shards' config) only if the merge job is first shown to re-derive untested files itself. Verify by running a shard with --coverage.all=false --reporter=blob and then vitest --mergeReports --coverage under the base config, and comparing the merged file list against today's. If the merged report loses files, DROP this criterion — a file that falls out of the report takes its thresholds key with it and the gate passes silently on 0% coverage, which is exactly the failure mode MOTIR-2449 left behind and tests/coverage-gate-globs.test.ts exists to catch. Record which way it went in the PR body.

Out of scope — each is separately measurable and none is a prerequisite

  • --no-isolate. Measured −20% on an 11-file DB-backed slice (47.7 s → 38.2 s, import 39.9 s → 21.5 s) with no new failures. But that slice is 11 files against ~57 per worker at 8 shards, and this suite leans on vi.mock and module-level singletons throughout — sharing a module registry across a worker's files is a real state-leak surface and needs its own spike.
  • Cost-based shard splitting. --shard=i/n partitions by Vitest's file ordering, not by measured cost. At 3 shards the spread is 19% and tolerable; at 8 it will widen. tests/e2e/shard-plan.ts (MOTIR-2617) is the pattern to copy when it does.
  • Postgres durability. .github/actions/postgres runs stock pgvector/pgvector:pg16; only synchronous_commit = off is set, per-database, in tests/setup/globalDb.ts. fsync=off / full_page_writes=off and a tmpfs data dir are free for a container destroyed at job end, and every DB-backed test pays a TRUNCATE … CASCADE in beforeEach — 6972 of them per shard. Unmeasured.
  • Making the tests themselves faster. 86% worker utilisation says the lane is not wasting the parallelism it has.

Context refs

  • .github/workflows/ci.yml — the test job (matrix, timeout-minutes, the vitest run … --shard step) and the coverage merge job below it.
  • vitest.collect.config.ts — the shards' config; already strips thresholds, which the merge job enforces on the merged result under vitest.config.ts.
  • tests/helpers/parallelDb.tsTEST_DB_WORKERS, the 4-worker cap.
  • MOTIR-1705 / MOTIR-1711 — the 3-shard split and the blob-merge coverage job this card re-sizes. MOTIR-1705 sized the matrix at 3 when the suite was ~235 files; it is 1360.

Resolution: open.