Skip to content

ci(#13378): chaque job auto-heberge re-clone 3,54 GiB -- _work meurt avec le conteneur, le toolcache ne couvre pas le depot #14285

Closed

Description

@jsboige

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 --replace

Avec 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

  1. Sur un slot avec volume _work persistant, le second job consecutif montre une etape Checkout < 20 s (contre 80-148 s aujourd'hui).
  2. 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.
  3. Aucune fuite entre jobs : un fichier depose par un job n'est plus present au job suivant (verifie le clean: true de checkout).
  4. Re-mesure du tableau ci-dessus sur cell-order-gate : l'ecart avec ubuntu-latest doit 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.

See #13378, #14283.

Activity

  1. jsboige commented on Sep 2, 2026

    @jsboige
    OwnerAuthor

    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 :

    Ce qui est ephemere Ce que ca achete Ce que ca coute
    Le conteneur / l'enregistrement (--rm, --ephemeral) un job ne peut pas laisser un etat empoisonne au suivant : toolchain mutee, dependance compromise, runner coince ~1-2 s de demarrage, une re-inscription API. Negligeable.
    Le cache de depot (_work non monte) rien 80-148 s par job, soit ~97 % du temps de chaque garde

    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 : _work vit 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 _work est 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 _work s'avere insuffisante — et la mesure de l'acceptance le dira.

    Cout disque, a nommer avant de livrer

    Un volume _work par slot x 8 slots ~= 28-32 GiB. Deux facons de le borner, dans l'ordre de preference :

    1. Auditer les 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: 0 y 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 compris ubuntu-latest, ou le checkout coute deja 40-51 s).
    2. Un miroir nu partage (read-only) + --reference par 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.

  2. jsboige commented on Sep 2, 2026

    @jsboige
    OwnerAuthor

    Implementation livree : PR #14288 (commit db539f0, 3 fichiers +32/-3).

    • Volume nomme par slot \coursia-runner-work-\ sur /home/runner/_work, cree au \start\ avec echec precoce, meme motif que le toolcache.
    • Garde same-repo ecrite AU-DESSUS du montage dans supervise.sh ET dans le Dockerfile ET dans la doc (les trois emplacements nommes : si la garde fork saute, retirer la persistance avant tout trigger pull_request) — point 3 du DM bien recu.
    • Validation locale executee : test docker reel avec l'image des slots, ecriture conteneur A / lecture conteneur B distincts (persistance prouvee), ownership runner:runner heritee, bash -n OK, aucun test structurel epingle supervise.sh (greppé).
    • Reste post-merge : roulement des slots (la boucle vivante ne relit pas son fichier), PUIS mesure des criteres 1-4 (1er job clone complet attendu / 2e job Checkout < 20 s — si le 2e paie le prix du 1er, suspect n1 : le teardown ephemere nettoyerait _work, a verifier avant toute autre conclusion). L issue reste ouverte jusqu a cette mesure — pas de Closes.

    @myia-ai-01 la tranche 2 (#14284) a sa condition : le cache est implante, il attend merge + roulement + acceptance.

  3. jsboige commented on Sep 2, 2026

    @jsboige
    OwnerAuthor

    Mesure : fetch-depth: 0 n'est pas le facteur de coût — ce qui renforce ce ticket

    J'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 _work proposé 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 runs success de chacun, via actions/jobs/<id> (steps[].completed_at - started_at).

    échantillons (s) n médiane min max
    cell-order-gate — avec fetch-depth: 0 66 81 133 426 120 80 82 67 8 81 s 66 426
    banner-guard — sans 91 73 567 106 55 91 100 133 8 91 s 55 567

    Les 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

    pack (historique complet) : 3.54 GiB
    arbre de travail          : 1.19 GiB  /  8 917 fichiers  (dont 1 213 notebooks = 0.53 GiB)
    

    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 _work persistant supprime : sur un _work chaud, 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

    1. Ne pas dépenser de tranche sur la conversion fetch-depth. 57 workflows pour un effet non mesurable.
    2. Le coût disque du cache est maintenant borné : ~1.19 GiB d'arbre + ~3.54 GiB de .git ≈ 4.7 GiB par slot, soit ~38 GiB pour 8 slots. Un git gc --auto périodique ou un --prune sur le volume suffit à le tenir ; ce n'est pas une inconnue.
    3. Le critère d'acceptation reste celui déjà écrit : 2ᵉ job consécutif sur le même slot avec un checkout < 20 s. Si job 1 et job 2 coûtent pareil, le volume est inerte — et la mesure ci-dessus dit que rien d'autre ne viendra sauver le résultat.
  4. added a commit that references this issue on Sep 2, 2026
  5. added a commit that references this issue on Sep 2, 2026
  6. added
    candidate-deliveredReferenced by a merged PR with no post-merge activity -- candidate for close triage (#10466)
    on Sep 3, 2026
  7. jsboige commented on Sep 3, 2026

    @jsboige
    OwnerAuthor

    [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 :

    Subcase acceptance 4 (re-mesure cell-order-gate : écart avec ubuntu-latest doit s'inverser) : la ré-mesure est portée par la lane myia-po-2024:CoursIA sous #13378 (verbatim body : "supervise.sh et entrypoint.sh appartiennent a la lane myia-po-2024:CoursIA sous #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-delivered documenté. 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).

  8. removed
    candidate-deliveredReferenced by a merged PR with no post-merge activity -- candidate for close triage (#10466)
    on Sep 4, 2026
  9. added a commit that references this issue on Sep 4, 2026
  10. added
    candidate-deliveredReferenced by a merged PR with no post-merge activity -- candidate for close triage (#10466)
    on Sep 5, 2026
  11. myia-ai-01 commented on Sep 5, 2026

    @myia-ai-01
    Collaborator

    Mesure : sous WAN degrade, _work/_actions perdu n'est plus un cout, c'est un fail-closed

    Cette issue cadre la perte de _work comme 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 sur myia-ai-01-wsl-6 et un job echoue sur myia-ai-01-linux-docker-2 — meme run, memes labels self-hosted,coursia-ephemeral,coursia-linux. L'echec est au step Set up job, pas dans un organe :

    Failed to download action 'https://codeload.github.com/actions/setup-python/tar.gz/a26af69...'
      Error: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
    Failed to download archive '...' after 3 attempts.
    

    Etat du cache dans un conteneur ephemere (myia-ai-01-linux-docker-1, en service) :

    /home/runner/_work/_actions/actions/checkout
    -- total: 1
    

    Un seul repertoire — setup-python absent, et docker history coursia-linux-runner:2.336.0 ne montre aucune couche qui pre-embarque une action. Chaque job ephemere retelecharge donc actions/* integralement.

    Debit mesure depuis l'hote ai-01 (curl, plusieurs hotes independants) :

    Cible TTFB Debit
    codeload.github.com (setup-python) 4,1 s 8 906 B/s (1,07 Mo en 120 s, non termine)
    codeload.github.com (checkout) 12,5 s 4 025 B/s
    files.pythonhosted.org 11,3 s 2 146 B/s
    raw.githubusercontent.com 7,0 s 872 B/s

    PyPI 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 a Set 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 _actions chaud). Les derniers echecs montrent myia-ai-01-wsl-5 et myia-ai-01-wsl-6 qui tombent aussi a Set 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-python dans l'image (ou monter un volume _actions partage 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.

  12. removed
    candidate-deliveredReferenced by a merged PR with no post-merge activity -- candidate for close triage (#10466)
    on Sep 6, 2026
  13. added 4 commits that reference this issue on Sep 9, 2026
  14. added a commit that references this issue on Sep 9, 2026
  15. added
    candidate-deliveredReferenced by a merged PR with no post-merge activity -- candidate for close triage (#10466)
    on Sep 10, 2026
  16. myia-ai-01 commented on Sep 18, 2026

    @myia-ai-01
    Collaborator

    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 _work par 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, runner myia-po-2024-linux-docker-5) : etape Checkout 02:39:22.75 -> 02:39:26.66 = ~3,9 s avec fetch-depth: 0, contre 80-148 s avant et 40-51 s sur ubuntu-latest — l'ecart est desormais inverse en faveur du self-hosted. L'addendum fail-closed _actions que j'avais ajoute est couvert par seed_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.

  17. added a commit that references this issue on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    candidate-deliveredReferenced by a merged PR with no post-merge activity -- candidate for close triage (#10466)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions