granja: la cosecha llenaba el disco — rsync sin -H y bajando .dmerge

La bajada del store murio con "No space left on device" con 95 G libres y un
store remoto de 25 G. Dos causas que se multiplican:

- `rsync -a` NO preserva hardlinks (eso es `-H`). El store es CAS y .dmerge
  hardlinkea los artefactos, asi que los 25 G que mide `du` en el origen -que
  deduplica por inodo- se escriben en destino como copias enteras, una por enlace.
- `.dmerge` es cache pura: hammer la reconstruye sola, no aporta un artefacto y
  es la parte mas pesada del arbol (132 G en el hub, 111,7 GiB exclusivos).

Ademas el fallo a mitad deja directorios creados sin ficheros, que es un
cache-hit envenenado: `mise` se cosecho asi, como nombre vacio. Se anade un
guardian que barre los vacios al terminar la bajada y avisa de re-correr.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
This commit is contained in:
Sergio
2026-09-01 15:53:35 +00:00
co-authored by Claude Opus 5
parent 95679adb6f
commit 6558b7c8de
+33 -2
View File
@@ -75,13 +75,44 @@ echo "==> 2. bajando artefactos sellados del worker"
# son inmutables y un artefacto superado no vuelve a ser vigente salvo que una receta retroceda—
# excluirlos es seguro; y si una receta retrocediera, su artefacto se reconstruye, que es barato.
LEDGER="work/store-gc-superados.txt"
# ── ⚠ `-H` Y EXCLUIR `.dmerge`: SIN ESTO LA COSECHA LLENA EL DISCO ─────────────────────────────
# Medido el 2026-09-01: esta bajada murió con `No space left on device (28)` escribiendo
# `store/.dmerge/b8bc5ecc…/usr/share/locale/az/LC_MESSAGES/kio6.mo`, con 95 G libres y un store
# remoto de 25 G. Dos causas que se multiplican:
#
# 1. **`rsync -a` NO preserva hardlinks** (eso es `-H`, y no está incluido en `-a`). El store es
# CAS y `.dmerge` hardlinkea los artefactos: en el worker, 25 G medidos con `du` —que deduplica
# por inodo— son MUCHOS más en el cable, porque cada enlace se transfiere y se escribe como una
# copia entera. El tamaño que informa `du` del origen NO es el que hace falta en el destino.
# 2. **`.dmerge` es CACHÉ PURA y no tiene por qué viajar.** Es la capa de merge que hammer
# reconstruye sola; copiarla no aporta un solo artefacto y es la parte más pesada del árbol
# (en el hub llegó a 132 G con 111,7 GiB exclusivos). Cosechar es traerse ARTEFACTOS.
#
# El fallo además es a mitad de camino: rsync limpia sus temporales al abortar, pero los directorios
# que ya creó quedan — y un directorio de artefacto sin ficheros es EXACTAMENTE un cache-hit
# envenenado (`mise` llegó así en esa pasada). Ver la regla 3 del CLAUDE.md.
RSOPTS=(-azH --exclude '/.dmerge')
if [ -s "$LEDGER" ]; then
echo " (excluyendo $(wc -l < "$LEDGER") artefactos ya podados — ver la nota del bucle de churn)"
rsync -az -e "$SSH" --exclude-from="$LEDGER" "$VPS:$REMOTE/store/" ./store/
rsync "${RSOPTS[@]}" -e "$SSH" --exclude-from="$LEDGER" "$VPS:$REMOTE/store/" ./store/
else
rsync -az -e "$SSH" "$VPS:$REMOTE/store/" ./store/
rsync "${RSOPTS[@]}" -e "$SSH" "$VPS:$REMOTE/store/" ./store/
fi
# ── GUARDIÁN: ningún artefacto cosechado puede llegar VACÍO ────────────────────────────────────
# Un directorio sin ficheros no es un artefacto, es un nombre — y `hammer build` lo lee como
# presencia y sella sin construir. Barrerlos acá es barato; detectarlos tres eslabones más abajo
# (manifiesto → grafo → build) cuesta una campaña.
VACIOS=0
for d in ./store/*/; do
case "$d" in ./store/.dmerge/) continue ;; esac
[ -d "$d" ] || continue
if [ -z "$(find "$d" -mindepth 1 -print -quit 2>/dev/null)" ]; then
echo " ⚠ artefacto VACÍO cosechado, lo quito: $d"; rmdir "$d" 2>/dev/null; VACIOS=$((VACIOS+1))
fi
done
[ "$VACIOS" -gt 0 ] && echo " ($VACIOS vacíos barridos — la bajada quedó a medias, re-corré el sync)"
if [ "${2:-}" = "--no-promote" ]; then
echo "✓ store sincronizado (sin promote)"; exit 0
fi