Jobs à 4 Go de RAM : le lint typé d'ATLAS meurt à 3 Go (SIGABRT), et check bloque tout le reste #1

Closed
opened 2026-10-15 12:10:42 +00:00 by nicolas-fryder · 2 comments

Demande

Passer la limite des jobs de 3 Go à 4 Go, sans swap comme aujourd'hui :

  • config.yml : options: "--memory=4g --memory-swap=4g --cpus=2.5 --pids-limit=512"
  • docker-compose.yaml, service dind : memory: 5g et memswap_limit: 5g. C'est la règle déjà écrite au-dessus : un job, plus dockerd, plus les conteneurs de service (Postgres d'integration).

Si l'hôte ne peut pas les donner à tous les jobs, une seconde étiquette suffit, par exemple node-22-4g à 4 Go, que seul check demanderait. check.yml d'eqs-workflows prendrait alors une entrée runs-on.

Pourquoi

ATLAS, check.yml@v1, run du 2026-10-15 sur 542d9ec : pnpm check --sequential → lint tuée par le système (SIGABRT), sans avoir pu rien dire. Le job tournait déjà avec les 3 Go du 2026-10-14. Ce n'est pas le noyau (ce serait SIGKILL) : c'est V8 qui abandonne, tas plein.

Mesuré en local, même dépôt, ESLint 9.39, sans cache :

node --max-old-space-size=1024 node_modules/eslint/bin/eslint.js .   → exit 134
node --max-old-space-size=1536 …                                     → exit 134
node --max-old-space-size=1792 …                                     → exit 134
node --max-old-space-size=3072 …                                     → exit 0

Le profil ESLint du socle (projectService) charge un programme TypeScript complet par tsconfig.json : chez ATLAS, trois (racine, tests/, worker/), chacun avec les types de Next 16 et de Prisma. Le tas seul dépasse 1,8 Go. À 3 Go de RAM au total, il ne reste pas la place pour le tas, le code de Node et le reste du processus.

Conséquence aujourd'hui

check est rouge à chaque push sur ATLAS. integration, contract et audit en dépendent (needs) et ne tournent jamais. Le pre-push local, sans plafond, passe.

Lié

  • Infrastructure/eqs-workflows#4 : le même constat côté workflow, avec les pistes logicielles (lint découpé par tsconfig), qui ne dispensent pas d'avoir la mémoire tant qu'elles n'existent pas.
  • Le commentaire de check.yml (« le runner donne 2 Go et 1,5 CPU ») est périmé depuis le 2026-10-14.

Ouvert depuis une session de travail sur ATLAS.

## Demande Passer la limite des jobs de **3 Go à 4 Go**, sans swap comme aujourd'hui : - `config.yml` : `options: "--memory=4g --memory-swap=4g --cpus=2.5 --pids-limit=512"` - `docker-compose.yaml`, service `dind` : `memory: 5g` et `memswap_limit: 5g`. C'est la règle déjà écrite au-dessus : un job, plus dockerd, plus les conteneurs de service (Postgres d'`integration`). Si l'hôte ne peut pas les donner à tous les jobs, une **seconde étiquette** suffit, par exemple `node-22-4g` à 4 Go, que seul `check` demanderait. `check.yml` d'eqs-workflows prendrait alors une entrée `runs-on`. ## Pourquoi ATLAS, `check.yml@v1`, run du 2026-10-15 sur `542d9ec` : `pnpm check --sequential` → **`lint tuée par le système (SIGABRT), sans avoir pu rien dire`**. Le job tournait déjà avec les 3 Go du 2026-10-14. Ce n'est pas le noyau (ce serait SIGKILL) : c'est V8 qui abandonne, tas plein. Mesuré en local, même dépôt, ESLint 9.39, sans cache : ``` node --max-old-space-size=1024 node_modules/eslint/bin/eslint.js . → exit 134 node --max-old-space-size=1536 … → exit 134 node --max-old-space-size=1792 … → exit 134 node --max-old-space-size=3072 … → exit 0 ``` Le profil ESLint du socle (`projectService`) charge un programme TypeScript complet par `tsconfig.json` : chez ATLAS, trois (racine, `tests/`, `worker/`), chacun avec les types de Next 16 et de Prisma. Le tas seul dépasse 1,8 Go. À 3 Go de RAM au total, il ne reste pas la place pour le tas, le code de Node et le reste du processus. ## Conséquence aujourd'hui `check` est rouge à chaque push sur ATLAS. `integration`, `contract` et `audit` en dépendent (`needs`) et **ne tournent jamais**. Le pre-push local, sans plafond, passe. ## Lié - Infrastructure/eqs-workflows#4 : le même constat côté workflow, avec les pistes logicielles (lint découpé par `tsconfig`), qui ne dispensent pas d'avoir la mémoire tant qu'elles n'existent pas. - Le commentaire de `check.yml` (« le runner donne 2 Go et 1,5 CPU ») est périmé depuis le 2026-10-14. *Ouvert depuis une session de travail sur ATLAS.*
Author
Owner

Mesuré : ce n'est pas la RAM, c'est le plafond de tas de V8

Le job check d'ATLAS a été rejoué en conteneur aux limites exactes du runner : node:22-bookworm, --memory=3g --memory-swap=3g --cpus=2.5 --pids-limit=512, clone propre de a53d3ad, chaque tâche de eqs-check lancée seule avec --sans-cache. Le pic de mémoire anonyme du cgroup (memory.stat) est échantillonné chaque seconde : contrairement à memory.peak, il ne compte pas le cache disque.

limite plafond de tas V8 lint pic anon du lint oom_kill
3 Go, défaut 1 584 Mo ✗ SIGABRT 1 788 Mo 0
3 Go, NODE_OPTIONS=--max-old-space-size=2560 2 608 Mo ✓ 2 519 Mo 0
4 Go, défaut ~2,1 Go ✓ 2 294 Mo 0

Les autres tâches à 3 Go, réglage par défaut : types 1 386 Mo, types-tests 1 482 Mo, doctor 672 Mo, unit 466 Mo, format/knip/types-worker < 260 Mo. Toutes vertes.

Lecture. Node 22 dimensionne son tas à environ la moitié de la mémoire du conteneur : 1,58 Go pour 3 Go. Le lint en demande ~1,8 Go et V8 abandonne (SIGABRT), alors que le cgroup a encore plus d'un gigaoctet libre. Le noyau n'a jamais tué personne (oom_kill 0).

Ce que je propose à la place des 4 Go

Garder --memory=3g et donner aux jobs un tas à la mesure du conteneur, dans config.yml à côté de options, pour que les deux valeurs bougent ensemble :

runner:
  envs:
    NODE_OPTIONS: --max-old-space-size=2560

Il reste ~550 Mo de marge au pic du lint. Le jour où le lint ou un build la mangera, passer à 4 Go et 3 584 Mo de tas, ensemble.

À noter : l'hôte Forge n'avait aucun swap (7,6 Go, 4,1 Go disponibles). Un fichier d'échange de 4 Go et vm.swappiness=10 y ont été ajoutés le 2026-10-15. Les jobs gardent zéro swap ; ce swap protège Forgejo et Coolify d'un pic de dind. Un redémarrage de l'hôte est en attente (mises à jour), sans lien avec ce réglage.

## Mesuré : ce n'est pas la RAM, c'est le plafond de tas de V8 Le job `check` d'ATLAS a été rejoué en conteneur aux limites exactes du runner : `node:22-bookworm`, `--memory=3g --memory-swap=3g --cpus=2.5 --pids-limit=512`, clone propre de `a53d3ad`, chaque tâche de `eqs-check` lancée seule avec `--sans-cache`. Le pic de mémoire **anonyme** du cgroup (`memory.stat`) est échantillonné chaque seconde : contrairement à `memory.peak`, il ne compte pas le cache disque. | limite | plafond de tas V8 | lint | pic anon du lint | `oom_kill` | |---|---|---|---|---| | 3 Go, défaut | **1 584 Mo** | ✗ SIGABRT | 1 788 Mo | 0 | | 3 Go, `NODE_OPTIONS=--max-old-space-size=2560` | 2 608 Mo | ✓ | 2 519 Mo | 0 | | 4 Go, défaut | ~2,1 Go | ✓ | 2 294 Mo | 0 | Les autres tâches à 3 Go, réglage par défaut : types 1 386 Mo, types-tests 1 482 Mo, doctor 672 Mo, unit 466 Mo, format/knip/types-worker < 260 Mo. Toutes vertes. **Lecture.** Node 22 dimensionne son tas à environ la moitié de la mémoire du conteneur : 1,58 Go pour 3 Go. Le lint en demande ~1,8 Go et V8 abandonne (SIGABRT), alors que le cgroup a encore plus d'un gigaoctet libre. Le noyau n'a jamais tué personne (`oom_kill 0`). ## Ce que je propose à la place des 4 Go Garder `--memory=3g` et donner aux jobs un tas à la mesure du conteneur, dans `config.yml` à côté de `options`, pour que les deux valeurs bougent ensemble : ```yaml runner: envs: NODE_OPTIONS: --max-old-space-size=2560 ``` Il reste ~550 Mo de marge au pic du lint. Le jour où le lint ou un build la mangera, passer à 4 Go **et** 3 584 Mo de tas, ensemble. À noter : **l'hôte Forge n'avait aucun swap** (7,6 Go, 4,1 Go disponibles). Un fichier d'échange de 4 Go et `vm.swappiness=10` y ont été ajoutés le 2026-10-15. Les jobs gardent zéro swap ; ce swap protège Forgejo et Coolify d'un pic de `dind`. Un redémarrage de l'hôte est en attente (mises à jour), sans lien avec ce réglage.
Author
Owner

Suite : le tas suffit au lint, mais les jobs qui construisent manquent de RAM à 3 Go

Run 14 d'ATLAS (après 376e8ad, NODE_OPTIONS=--max-old-space-size=2560) : check est vert. En revanche :

  • integration : next build, lancé par le test de conformité, meurt avec un code null (signal) pendant « Running TypeScript » ;
  • fuzzing (contract.yml) : le journal s'arrête net sur « Creating an optimized production build… ». Le job est resté affiché « running » alors que le run était terminé : le runner a perdu le job, sans rapporter sa fin.

Mesuré en conteneur aux limites du runner (--memory=3g --memory-swap=3g --cpus=2.5, clone propre, experimental.cpus: 3 pour reproduire les 3 workers du VPS), pic de mémoire anonyme du cgroup :

next build à 3 Go, 3 workers résultat pic anon marge
tas par défaut ✓ 2 764 Mo ~300 Mo
tas 2 560 Mo ✓ 2 652 Mo ~420 Mo

Seul, le build passe de justesse. Dans le job integration, il tourne en plus du processus vitest qui l'a lancé (Prisma, harnais) : la marge disparaît, et le noyau tue le build. Le NODE_OPTIONS n'y est pour rien : sans lui, la marge est même plus faible.

Demande révisée

4 Go pour les jobs, comme demandé au départ, dind à 5 Go, et zéro swap pour les jobs. Le NODE_OPTIONS reste utile au lint. Avec 4 Go, le monter à --max-old-space-size=3072 laisserait la même marge au lint qu'aujourd'hui.

L'hôte a désormais 4 Go de swap (2026-10-15) : un dind à 5 Go ne mettrait plus Forgejo à la merci d'un pic.

## Suite : le tas suffit au lint, mais les jobs qui construisent manquent de RAM à 3 Go Run 14 d'ATLAS (après `376e8ad`, `NODE_OPTIONS=--max-old-space-size=2560`) : `check` est **vert**. En revanche : - `integration` : `next build`, lancé par le test de conformité, meurt avec un code `null` (signal) pendant « Running TypeScript » ; - `fuzzing` (contract.yml) : le journal s'arrête net sur « Creating an optimized production build… ». Le job est resté affiché « running » alors que le run était terminé : **le runner a perdu le job**, sans rapporter sa fin. Mesuré en conteneur aux limites du runner (`--memory=3g --memory-swap=3g --cpus=2.5`, clone propre, `experimental.cpus: 3` pour reproduire les 3 workers du VPS), pic de mémoire **anonyme** du cgroup : | `next build` à 3 Go, 3 workers | résultat | pic anon | marge | |---|---|---|---| | tas par défaut | ✓ | 2 764 Mo | ~300 Mo | | tas 2 560 Mo | ✓ | 2 652 Mo | ~420 Mo | Seul, le build passe de justesse. Dans le job `integration`, il tourne en plus du processus vitest qui l'a lancé (Prisma, harnais) : la marge disparaît, et le noyau tue le build. Le `NODE_OPTIONS` n'y est pour rien : sans lui, la marge est même plus faible. ## Demande révisée **4 Go pour les jobs**, comme demandé au départ, `dind` à 5 Go, et zéro swap pour les jobs. Le `NODE_OPTIONS` reste utile au lint. Avec 4 Go, le monter à `--max-old-space-size=3072` laisserait la même marge au lint qu'aujourd'hui. L'hôte a désormais 4 Go de swap (2026-10-15) : un `dind` à 5 Go ne mettrait plus Forgejo à la merci d'un pic.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Infrastructure/forgejo-runner#1
No description provided.