core/docs/ci-optimization.md
Shay f93f756e44
Some checks failed
smoke / smoke (-m "not quarantine") (pull_request) Successful in 5m35s
lane-shas / verify pinned lane SHAs (pull_request) Failing after 28m37s
ci: order-of-magnitude CI optimization for single 2-vCPU Act runner
Root cause of PR checks stuck "Waiting to run": full-pytest on main ran
~12k tests including the slow registry on a 1.5-CPU / 1.2 GiB job cgroup
for hours, monopolizing the only runner.

Workflows:
- full-pytest.yml: fast lane only (`not quarantine and not slow`)
- nightly-full-pytest.yml: full suite including slow (schedule + manual)
- lane-shas.yml: PR path filters so pin verification is skipped when
  the change cannot affect lane report bytes

Docs: docs/ci-optimization.md + testing-lanes CI mapping.

Infra (VM, same change window): job cgroup 2 CPU / 1800 MiB; hung
full-pytest task stopped so the queue can drain PR smoke/lane-shas.
2026-07-14 20:40:52 -07:00

63 lines
3 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CI optimization (single self-hosted Act runner)
## Problem (measured 2026-07-15)
| Fact | Value |
|------|--------|
| Host | 2 vCPU, ~3.8GiB RAM, shared Forgejo + Postgres + Traefik |
| Job cgroup (was) | `--cpus=1.5 --memory=1200m` |
| Concurrent capacity | 1 job |
| Suite size | ~12.7k tests (`not quarantine`) |
| Post-merge job | `full-pytest` with **slow included** + `-n 2` |
| Observed | One `full-pytest` ran **2+ hours** at ~25% progress; PR **smoke / lane-shas** stayed **Waiting to run** |
That is not “slow tests” alone — it is **queue starvation**: one oversized job owns the only runner.
## Order-of-magnitude strategy
### 1. Right-size the default CI lane (workflow) — largest lever
| Lane | When | Marker expression | Intent |
|------|------|-------------------|--------|
| **smoke** | every PR | fixed file list | minutes; merge gate |
| **lane-shas** | PR (path-filtered) + main | pin script | only when pin surface can change |
| **full-pytest (main)** | every push to main | `not quarantine and not slow` | fast post-merge ratchet |
| **nightly-full** | schedule + manual | `not quarantine` (includes slow) | soak / proof / eval-matrix |
Local doc already measured **~7.7×** from “full serial” → “fast + parallel” on a laptop (`docs/testing-lanes.md`). On a 2vCPU VM the absolute times are worse, but **excluding slow** removes the 16minute parallel floor and hundreds of soak tests — typically multi× wall clock and frees the runner for PR gates.
### 2. Path-filter expensive PR jobs
`lane-shas` is multiminute (demos + showcase). Skip on PR when the diff cannot change pin bytes (docs-only, pure tooling, etc.). Main still always verifies after merge.
### 3. Runner cgroup (infra)
`infra/runner/config.yaml` job options should match host reality:
- Prefer **2 CPUs** for the job when the host is 2vCPU (avoid 1.5 + xdist thrash).
- Memory **≤ available** after Forgejo (~800MiB limit on host stack).
- Keep **capacity 1** until a second runner exists — concurrency >1 on 2 cores makes two jobs slower than one.
### 4. What will *not* give 100× on this box
- Throwing more xdist workers at a 2vCPU host.
- Running full+slow on every main push.
- Expecting MLX/Rust to speed pytest collection/runtime of the whole suite without suite redesign.
True second order of magnitude needs either **more hardware** (second runner / larger VM) or **further suite partitioning** (shard by package, change-based test selection).
## Local policy (solo branch work)
1. Targeted tests for files you touched.
2. Dual-run only if pin surface changed.
3. Full smoke before merge (or trust green smoke if unchanged smoke paths).
4. Never rely on remote `full-pytest` for PR feedback — it is post-merge / nightly.
## Related
- `docs/testing-lanes.md` — fast / slow / full classification
- `conftest.py``SLOW_FILES` / `SLOW_TESTS` / `QUARANTINE`
- `.github/workflows/full-pytest.yml`
- `.github/workflows/nightly-full-pytest.yml`
- `.github/workflows/smoke.yml`
- `.github/workflows/lane-shas.yml`