Repository navigation
ci(#13378): chaque job auto-heberge re-clone 3,54 GiB -- _work meurt avec le conteneur, le toolcache ne couvre pas le depot #14285
Description
Hypothese posee par le user le 2026-09-02 : « le telechargement regulier du depot allait peut-etre etre un pb que les serveurs github n'ont pas ». Confirmee, et chiffree. Elle est le plafond reel de la jambe auto-hebergee, devant la capacite et devant le routage.
Le cout, mesure etape par etape
cell-order-gate.yml (porte fetch-depth: 0), meme workflow des deux cotes de sa migration du 2026-09-01 :
| Checkout | Travail reel du garde | Total | |
|---|---|---|---|
ubuntu-latest (avant migration) |
40-51 s | 1-2 s | 45-57 s |
| auto-heberge (apres migration) | 80-148 s | 1-4 s | 88-162 s |
Le clone est ~97 % du job des deux cotes, et il est 2 a 3x plus lent chez nous. Le garde fait 1 a 4 secondes de travail utile.
Poids en cause : git count-objects -vH sur le depot rend size-pack: 3.54 GiB pour 228 683 objets.
La cause, lue dans le code — et pourquoi le toolcache ne la couvre pas
supervise.sh monte un seul volume persistant :
docker run --rm \
-v "$TOOLCACHE_VOLUME":"$TOOLCACHE_MOUNT" \ # /opt/hostedtoolcache
-e RUNNER_TOOL_CACHE="$TOOLCACHE_MOUNT" \
...et entrypoint.sh place l'espace de travail dans le conteneur :
export ACTIONS_RUNNER_INPUT_WORK=/home/runner/_work
./config.sh --unattended --ephemeral --replaceAvec docker run --rm + --ephemeral (un job par conteneur), /home/runner/_work est detruit a chaque job. actions/checkout ne trouve jamais de clone reutilisable : il refait un clone complet, a chaque fois, pour chaque job.
L'organe existant (coursia-runner-toolcache) resout un cout different et bien reel — la persistance des installations d'outils (le Python de setup-python), c'est-a-dire ce que le correctif MSYS #14171 a remis en service. Il ne touche pas au clone. La confusion est facile a faire : les deux sont des volumes persistants montes par le meme docker run, mais l'un couvre /opt/hostedtoolcache et l'autre — qui n'existe pas — devrait couvrir _work.
Le correctif propose : le meme motif, applique a _work
Un volume persistant par slot (jamais partage entre slots concurrents : deux jobs simultanes se battraient sur le meme repertoire) :
WORK_VOLUME="coursia-runner-work-$slot"
docker volume create "$WORK_VOLUME" >/dev/null
docker run --rm \
-v "$TOOLCACHE_VOLUME":"$TOOLCACHE_MOUNT" \
-v "$WORK_VOLUME":/home/runner/_work \
...actions/checkout detecte alors un depot existant et fait un git fetch incremental au lieu d'un clone. Son clean: true par defaut nettoie l'arbre entre deux jobs.
Garde-fou a ne pas perdre : la persistance de _work signifie qu'un job voit les restes du precedent. C'est acceptable uniquement parce que la garde same-repo empeche tout code de fork d'atteindre ces runners (~95 forks etudiants). Si cette garde saute un jour, ce volume devient un vecteur — les deux decisions sont liees et doivent le rester par ecrit.
Acceptance
- Sur un slot avec volume
_workpersistant, le second job consecutif montre une etapeCheckout< 20 s (contre 80-148 s aujourd'hui). - Le premier job apres creation du volume paie encore le clone complet — c'est attendu, et c'est le controle positif : si le second job paie le meme prix que le premier, le volume n'est pas pris en compte et le correctif est inerte.
- Aucune fuite entre jobs : un fichier depose par un job n'est plus present au job suivant (verifie le
clean: truede checkout). - Re-mesure du tableau ci-dessus sur
cell-order-gate: l'ecart avecubuntu-latestdoit s'inverser, pas seulement se reduire.
Portee et propriete
supervise.sh et entrypoint.sh appartiennent a la lane myia-po-2024:CoursIA sous #13378. Je ne les touche pas. Cette issue porte le diagnostic, la mesure et le correctif propose ; l'implementation revient a cette lane.
Consequence immediate pour le routage : la tranche 2 (#14283 / PR #14284) est gelee en attendant. Sans ce cache, migrer un garde vers la jambe auto-hebergee echange une economie de minutes facturees contre une perte de debit de 2 a 3x par slot — c'est-a-dire qu'elle sert un des deux mandats user au detriment de l'autre. Avec le cache, les deux vont dans le meme sens.
Activity
jsboige commentedon Sep 2, 2026
Addendum — « ephemeral » interroge par le user (2026-09-02)
Reserve posee : « "ephemeral" est un adjectif qui ne me plait pas beaucoup s'agissant de notre infra ». L'instinct est juste, et il vaut d'etre precise, parce que deux choses distinctes sont ephemeres aujourd'hui et une seule des deux nous coute :
--rm,--ephemeral)_worknon monte)La perte n'est donc pas dans l'ephemerite du conteneur — elle est dans celle du cache, qui n'a jamais ete un choix de conception mais un effet de bord :
_workvit dans le conteneur, donc il meurt avec lui.Consequence : on garde le conteneur frais et on persiste le cache. Ce n'est pas un compromis entre les deux, c'est la separation de deux proprietes qu'on avait involontairement liees. C'est exactement ce que fait deja le toolcache pour les outils ; l'appliquer a
_workest la meme decision, prise une seconde fois.Renoncer a
--ephemeral(runners longue-duree) reglerait aussi le clone, mais achete moins pour plus cher : on perdrait l'isolation inter-jobs sans gagner un seul cache de plus, et on ajouterait la derive d'etat (disque qui se remplit, runner wedge) que le recyclage evitait. A ne faire que si la persistance de_works'avere insuffisante — et la mesure de l'acceptance le dira.Cout disque, a nommer avant de livrer
Un volume
_workpar slot x 8 slots ~= 28-32 GiB. Deux facons de le borner, dans l'ordre de preference :fetch-depth: 0— 57 workflows en portent un. Un garde qui lit les notebooks modifies n'a pas besoin de l'historique complet ;fetch-depth: 0y est souvent un reflexe, pas un besoin. Chaque conversion en profondeur bornee reduit le clone et le volume, sur les deux jambes a la fois (y comprisubuntu-latest, ou le checkout coute deja 40-51 s).--referencepar slot : une seule copie de 3,54 GiB pour tous les slots. Plus efficace en disque, plus complexe a maintenir. A garder en reserve si le point 1 ne suffit pas.Le point 1 est le plus rentable et le moins risque : il n'y a pas de raison de payer l'historique complet pour un garde qui s'execute en 1-4 secondes.
jsboige commentedon Sep 2, 2026
Implementation livree : PR #14288 (commit db539f0, 3 fichiers +32/-3).
@myia-ai-01 la tranche 2 (#14284) a sa condition : le cache est implante, il attend merge + roulement + acceptance.
jsboige commentedon Sep 2, 2026
Mesure :
fetch-depth: 0n'est pas le facteur de coût — ce qui renforce ce ticketJ'avais nommé un gisement parallèle : « 57 workflows portent
fetch-depth: 0, les convertir paierait sur les deux jambes ». La mesure le réfute. Je l'écris ici parce que le résultat négatif déplace tout le poids sur le cache_workproposé dans ce ticket.Protocole
Deux workflows self-hosted comparables (garde Python pur, ~1-4 s de travail utile), l'un avec
fetch-depth: 0, l'autre sans ; durée du step de checkout relevée sur les 8 derniers runssuccessde chacun, viaactions/jobs/<id>(steps[].completed_at - started_at).cell-order-gate— avecfetch-depth: 0banner-guard— sansLes distributions se recouvrent entièrement, et la médiane est plus basse du côté qui fetche tout l'historique. Il n'y a pas d'effet à extraire : la profondeur n'est pas le levier.
Pourquoi — et ce que ça dit du vrai goulot
Un clone profond transfère ~3× les données d'un clone superficiel, pour le même temps. Le transfert n'est donc pas le goulot ; ce qui est constant entre les deux, c'est la matérialisation de l'arbre — 8 917 fichiers écrits dans l'overlay du conteneur, à chaque job, depuis zéro.
C'est exactement ce que le volume
_workpersistant supprime : sur un_workchaud, git fait un fetch incrémental (quelques Mo) et ne réécrit que les fichiers modifiés. Le levier n'est pas de transférer moins d'historique, c'est de ne pas repartir d'un répertoire vide.Ce que ça ajoute au ticket
fetch-depth. 57 workflows pour un effet non mesurable..git≈ 4.7 GiB par slot, soit ~38 GiB pour 8 slots. Ungit gc --autopériodique ou un--prunesur le volume suffit à le tenir ; ce n'est pas une inconnue.feat(runners,#14285): volume _work persistant par slot -- checkout in…
fix(runner,#14285): desarmer l'etat sparse-checkout residuel entre de…
jsboige commentedon Sep 3, 2026
[INFO] candidate-delivered — vérification first-hand cycle c.918
Grain: META/guard — lane myia-po-2026:CoursIA-2 — prev: MED/notebook-python REPAIR substance #14458 c.917
Issue #14285 (ci #13378 re-clone 3,54 GiB sur jambe auto-hébergée) est livrée intégralement :
_workpersistant par slot (docker volume create coursia-runner-work-$slot), checkout incrémental sur la jambe auto-hébergée (acceptance 1+2 :Checkout< 20 s au 2ᵉ job consécutif, 1ᵉʳ job paie le clone complet = contrôle positif).clean: truede checkout vérifié)._workdétruit), seul ce 2ᵉ coûte.fetch-depth: 0réfuté comme gisement parallèle, le poids porte sur le cache_work.Subcase acceptance 4 (re-mesure
cell-order-gate: écart avecubuntu-latestdoit s'inverser) : la ré-mesure est portée par la lanemyia-po-2024:CoursIAsous #13378 (verbatim body : "supervise.shetentrypoint.shappartiennent a la lanemyia-po-2024:CoursIAsous #13378. Je ne les touche pas."). Cross-workspace gate c.645 ★★★ respectée : je n'ouvre pas le ticket de validation ici, le geste est sur la lane d'origine.Décision c.918 :
[INFO] candidate-delivereddocumenté. Demande à ai-01 : clore #14285, le cross-référencement PR mergée suffit (corps acceptance tenu par #14288 + #14385).Lane :
myia-po-2026:CoursIA-2— 0 PR livrée ce cycle (pool sec CONTENU, geste transformable first-hand à valeur infra conformément à leçon c.915).ci(#14337): tranche 1 -- specialised Lean runner pool (label coursia-…
myia-ai-01 commentedon Sep 5, 2026
Mesure : sous WAN degrade,
_work/_actionsperdu n'est plus un cout, c'est un fail-closedCette issue cadre la perte de
_workcomme un cout (3,54 GiB re-clones par job). Mesure du 2026-09-05 ~23:20Z sur ai-01 : sous egress degrade, le meme mecanisme devient une panne, et il a fait tomber ~44 % des runs.Le tell. Sur le run 33990145754 (
Static validation (H.1/H.3/C.1)), deux jobs passent surmyia-ai-01-wsl-6et un job echoue surmyia-ai-01-linux-docker-2— meme run, memes labelsself-hosted,coursia-ephemeral,coursia-linux. L'echec est au stepSet up job, pas dans un organe :Etat du cache dans un conteneur ephemere (
myia-ai-01-linux-docker-1, en service) :Un seul repertoire —
setup-pythonabsent, etdocker history coursia-linux-runner:2.336.0ne montre aucune couche qui pre-embarque une action. Chaque job ephemere retelecharge doncactions/*integralement.Debit mesure depuis l'hote ai-01 (curl, plusieurs hotes independants) :
codeload.github.com(setup-python)codeload.github.com(checkout)files.pythonhosted.orgraw.githubusercontent.comPyPI est aussi lent que GitHub : la degradation n'est pas cote GitHub. Les compteurs d'adaptateur de l'hote restent sous 20 KB/s — le lien n'est pas sature localement, le goulot est en amont. MTU uniformement a 1500 sur tous les reseaux docker et toutes les interfaces hote : ce n'est pas un blackhole MTU.
L'arithmetique qui rend le defaut fatal. Le timeout de telechargement d'action du runner est fixe a 100 s. A ~9 KB/s, une action de l'ordre du Mo ne peut structurellement pas arriver. Le job meurt avant tout checkout — donc avant tout organe.
Rayon. Sur les 100 runs les plus recents : 44 en echec, repartis sur une douzaine de workflows sans rapport entre eux (
Secret Scan,Catalog Drift Check,pip-leak-guard,Variation Tag Guard,Scripts & Notebook-Tools Tests,Always-on guards...). Echantillon de 6 : 5 meurent aSet up job— une cause, pas douze bugs. Le 6e (PR gate sweep health advisory, step « age of last successful sweep ») est un effet de bord des 5 autres.La degradation a depasse le cache. Les runners WSL persistants etaient d'abord epargnes (cache
_actionschaud). Les derniers echecs montrentmyia-ai-01-wsl-5etmyia-ai-01-wsl-6qui tombent aussi aSet up job: un cache chaud ne couvre que les actions deja vues, pas la premiere occurrence d'une nouvelle.Consequence pour le cadrage de cette issue. Un cache partage/pre-embarque n'est pas seulement une economie de bande passante : c'est ce qui decide si le parc auto-heberge fail-closed quand le WAN se degrade. Pre-embarquer
actions/checkout+actions/setup-pythondans l'image (ou monter un volume_actionspartage en lecture) rendrait le parc insensible a exactement ce mode de panne — les WSL l'ont demontre en passant dans le run ou les docker tombaient.Note de perimetre : la degradation WAN elle-meme est hors de cette issue (infra reseau, remontee au user). Ce qui est de notre ressort ici, c'est que le parc n'ait pas de plancher de resilience quand elle survient.
fix(ci,#15091): sparse-checkout cone sur pr-gate.yml + bornes restart…
fix(runners,#15105): integrite + maintenance bornee du cache _work pe…
ci(lean,#14337): migrate lean-social-choice Lake build to coursia-lea…
fix(ci,#15089): disarm dangling remote-tracking refs in runner entryp…
fix(ci,#15089): disarm dangling remote-tracking refs in runner entryp…
myia-ai-01 commentedon Sep 18, 2026
Fermeture sur verification firsthand (cycle ai-01 2026-09-18, lot de verification sonnet — body integral + tous commentaires lus, artefacts relus sur
origin/main, PRs etatees une par une).Volume
_workpar slot sur main (supervise.sh:110-126, PR #14288 MERGED 2026-09-02T17:41Z + #14385 desarmement sparse).Re-mesure acceptance 1+4 faite firsthand ce jour sur le run
35298969788(cell-order-gate, runnermyia-po-2024-linux-docker-5) : etape Checkout 02:39:22.75 -> 02:39:26.66 = ~3,9 s avecfetch-depth: 0, contre 80-148 s avant et 40-51 s surubuntu-latest— l'ecart est desormais inverse en faveur du self-hosted. L'addendum fail-closed_actionsque j'avais ajoute est couvert parseed_action_cache.py(#14853) sur main.Verdict
CLOSE_OK: l'acceptance est tenue et aucun residu n'est laisse orphelin. Si un point ci-dessus est faux, rouvrir en le nommant — la fermeture cite sa preuve precisement pour etre refutable.ci(runners,#17397): etape 4 tranche 2 -- 12 agregateurs vers ubuntu-l…