loki : les morceaux sont lies, pas copies ; chunks/index reste copie

Copier 6 550 morceaux arretait Loki 13 s au site ; les lier prend 40 ms. Ils sont adresses par leur contenu ; chunks/index loge delete_requests.gz, qui se reecrit, et reste vraiment copie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-10-07 23:44:04 -04:00
parent 3e2846cc5f
commit 9a4b63544e
4 changed files with 86 additions and 17 deletions

View file

@ -1,5 +1,30 @@
# CHANGELOG — Set-OPS
## 2026-10-07 (109) — Les morceaux de Loki sont liés, pas copiés ; sa base de suppressions, si
**94 preuves.** Au site, la copie à froid arrêtait Loki **13 s**, pour copier 6 550 petits
fichiers. Ses morceaux de journal sont adressés par leur contenu : un nom décodé donne
`1a0d123a4e8:1a0d1918235:6b5feaac` (début, fin, somme de contrôle). Ils ne bougent donc
jamais, et les lier prend **40 ms** (mesuré sur `site-mon-01`).
### Le fichier qui bougeait parmi les morceaux
Un contrôle d'immuabilité, refait avec `stat` (le `find` de la machine ne connaît pas `%W` :
le premier passage ne mesurait rien), a trouvé **un fichier sur 6 550** modifié après sa
naissance : `chunks/index/delete_requests/delete_requests.gz`, né le 2026-09-12 et réécrit le
2026-10-07. Ce n'est pas un morceau : c'est la base des demandes de suppression du compacteur,
rangée dans le même stockage objet. Lier tout `chunks/` aurait laissé la sauvegarde changer
avec elle.
**Fait** : `setops-copie-a-froid` accepte `--lier REL` (lier tout un sous-dossier) et
`--copier REL` (y copier vraiment ce qui bouge). Loki : `--lier chunks --copier chunks/index`.
**Éprouvé** : `test_restauration.py` ; morceau lié (même inode), `chunks/index` copié (autre
inode), une réécriture après la copie ne touche pas la sauvegarde. Témoin : sans `--copier`,
les deux derniers échouent. `shellcheck` propre. `make verifier` conforme, **94/94**.
**Pas encore déployé** : la durée réelle se mesurera au prochain dépôt.
## 2026-10-07 (108) — Le site recevait encore la sauvegarde d'avant le 2026-09-30
**94 preuves.** Déployer la copie à froid de Prometheus et Loki au site (`site-mon-01`) a

View file

@ -104,7 +104,7 @@ client_backup_catalogue:
/usr/local/sbin/setops-copie-a-froid
{{ serveur_loki_service | default('loki') }}
{{ serveur_loki_chemin | default('/var/lib/loki') }}
{{ client_backup_staging }}/loki/donnees
{{ client_backup_staging }}/loki/donnees --lier chunks --copier chunks/index
chemins: ["{{ client_backup_staging }}/loki"]
serveur_icinga:
# L'AC D'ICINGA FAIT L'IDENTITE DE SA BASE (2026-10-07). Icinga 2 derive l'environnement

View file

@ -1,7 +1,7 @@
#!/bin/bash
# Géré par Set-OPS (rôle client_backup). Ne pas éditer à la main.
#
# setops-copie-a-froid SERVICE SOURCE DESTINATION [--blocs-immuables]
# setops-copie-a-froid SERVICE SOURCE DESTINATION [--blocs-immuables] [--lier REL]... [--copier REL]...
#
# Copie COHERENTE d'une base qui ecrit en continu (Prometheus, Loki) : le service est
# arrete le temps de la copie, puis redemarre — QUOI QU'IL ARRIVE a la copie, et seulement
@ -17,12 +17,28 @@
# les blocs de Prometheus, que rien ne reecrit apres leur fermeture. On les recopie par
# LIENS DURS — instantane, sans place — et le reste (WAL, `chunks_head`) par une vraie
# copie. Si la destination est sur un autre systeme de fichiers, on copie tout.
#
# `--lier REL` lie tout un sous-dossier ; `--copier REL` y copie vraiment un sous-dossier qui,
# lui, bouge (2026-10-07). Pour Loki : `--lier chunks --copier chunks/index`. Ses morceaux sont
# adresses par leur contenu (`debut:fin:somme`, decode sur site-mon-01) et ne bougent jamais ;
# mais `chunks/index/` loge aussi `delete_requests.gz`, la base du compacteur, mesuree
# reecrite trois semaines apres sa naissance. La lier, c'etait laisser la sauvegarde changer
# avec elle. Copier 6 550 morceaux arretait Loki 13 s ; les lier prend 40 ms.
set -euo pipefail
service="${1:?service}"
source="${2:?source}"
dest="${3:?destination}"
blocs="${4:-}"
shift 3
blocs="" lier=() copier=()
while (( $# )); do
case "$1" in
--blocs-immuables) blocs=1; shift ;;
--lier) lier+=("$2"); shift 2 ;;
--copier) copier+=("$2"); shift 2 ;;
*) echo "copie a froid : option inconnue : $1" >&2; exit 2 ;;
esac
done
[[ -d "${source}" ]] || { echo "copie a froid : ${source} n'existe pas — rien a copier." >&2; exit 0; }
@ -38,20 +54,26 @@ debut=$(date +%s%N)
if (( actif )); then systemctl stop "${service}"; fi
trap relancer EXIT
if [[ "${blocs}" == "--blocs-immuables" ]]; then
mkdir -p "${tmp}"
for e in "${source}"/* "${source}"/.[!.]*; do
[[ -e "${e}" ]] || continue
n="$(basename "${e}")"
if [[ -d "${e}" && ${#n} -eq 26 && "${n}" == 01* ]]; then
cp -al "${e}" "${tmp}/${n}" 2>/dev/null || cp -a "${e}" "${tmp}/${n}"
else
cp -a "${e}" "${tmp}/${n}"
fi
done
else
cp -a "${source}" "${tmp}"
fi
lie() { cp -al "$1" "$2" 2>/dev/null || { rm -rf "$2"; cp -a "$1" "$2"; }; }
mkdir -p "${tmp}"
for e in "${source}"/* "${source}"/.[!.]*; do
[[ -e "${e}" ]] || continue
n="$(basename "${e}")"
if [[ -n "${blocs}" && -d "${e}" && ${#n} -eq 26 && "${n}" == 01* ]] \
|| [[ " ${lier[*]:-} " == *" ${n} "* ]]; then
lie "${e}" "${tmp}/${n}"
else
cp -a "${e}" "${tmp}/${n}"
fi
done
# Ce qui bouge, a l'interieur d'un dossier lie : remplace par une vraie copie.
for c in "${copier[@]:+${copier[@]}}"; do
if [[ -e "${source}/${c}" ]]; then
rm -rf "${tmp:?}/${c}"
mkdir -p "$(dirname "${tmp}/${c}")"
cp -a "${source}/${c}" "${tmp}/${c}"
fi
done
trap - EXIT
relancer

View file

@ -395,6 +395,28 @@ exit 0
verifier(r.returncode == 0 and "stop prometheus" not in appels and "start prometheus" not in appels,
"un service inactif n'est ni arrete ni relance")
# LOKI (2026-10-07) : les morceaux sont lies, `chunks/index/` copie. Il loge la base
# du compacteur (`delete_requests.gz`), qui se reecrit : liee, la sauvegarde
# changerait avec elle.
loki, prep = d / "loki", d / "prep" / "donnees"
(loki / "chunks" / "fake").mkdir(parents=True)
(loki / "chunks" / "fake" / "MWEw").write_text("morceau")
(loki / "chunks" / "index" / "delete_requests").mkdir(parents=True)
(loki / "chunks" / "index" / "delete_requests" / "delete_requests.gz").write_text("v1")
(loki / "wal").mkdir()
(loki / "wal" / "00000001").write_text("wal")
r = subprocess.run(["bash", str(outil), "loki", str(loki), str(prep), "--lier", "chunks",
"--copier", "chunks/index"], env=env, capture_output=True, text=True)
verifier(r.returncode == 0 and (prep / "chunks" / "fake" / "MWEw").stat().st_ino
== (loki / "chunks" / "fake" / "MWEw").stat().st_ino,
f"Loki : un morceau est lie (meme inode) ({r.stderr.strip()[-120:]})")
base = loki / "chunks" / "index" / "delete_requests" / "delete_requests.gz"
copie = prep / "chunks" / "index" / "delete_requests" / "delete_requests.gz"
verifier(copie.exists() and copie.stat().st_ino != base.stat().st_ino,
"Loki : chunks/index est vraiment copie (un autre inode)")
base.write_text("v2") # le compacteur reecrit sa base sur place
verifier(copie.read_text() == "v1", "une reecriture apres la copie ne change pas la sauvegarde")
def main() -> int:
couverture()