(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)
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:
| job | run |
|---|---|
| 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 build | 4.3 min |
| Lint + Prettier | 4.1 min |
| TypeScript | 2.6 min |
| Vitest coverage (merge) | 1.4 min |
The critical path is changes → test → coverage ≈ 26.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
- The
testjob's matrix runs 8 shards.Vitest (n/8)completes in ≲12 min on a green run, and thecoveragemerge job still gates the merged report at the same ≥90% per-file thresholds — the shards keep writing blob reports andcoveragekeeps enforcing on the merged result. No test is skipped, dropped or re-scoped:Tests N passedsummed across the 8 shards equals the count the 3 shards report on the same SHA. - The
timeout-minutescomment on thetestjob 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 run32959226187— 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. tests/ci-job-timeouts.test.tsstays green — everyruns-onjob in the file still declares atimeout-minutes(MOTIR-3768).coverage.allis set tofalseonvitest.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=bloband thenvitest --mergeReports --coverageunder 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 itsthresholdskey with it and the gate passes silently on 0% coverage, which is exactly the failure mode MOTIR-2449 left behind andtests/coverage-gate-globs.test.tsexists 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,import39.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 onvi.mockand 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/npartitions 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/postgresruns stockpgvector/pgvector:pg16; onlysynchronous_commit = offis set, per-database, intests/setup/globalDb.ts.fsync=off/full_page_writes=offand a tmpfs data dir are free for a container destroyed at job end, and every DB-backed test pays aTRUNCATE … CASCADEinbeforeEach— 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— thetestjob (matrix,timeout-minutes, thevitest run … --shardstep) and thecoveragemerge job below it.vitest.collect.config.ts— the shards' config; already stripsthresholds, which the merge job enforces on the merged result undervitest.config.ts.tests/helpers/parallelDb.ts—TEST_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.
Comments (0)
No comments yet — be the first to weigh in.