From 9a4b63544eb52dc8b0a161c9e85536d6ae5bb2dc Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Wed, 7 Oct 2026 23:44:04 -0400 Subject: [PATCH] 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 --- CHANGELOG.md | 25 +++++++++ roles/client_backup/defaults/main.yml | 2 +- .../files/setops-copie-a-froid.sh | 54 +++++++++++++------ scripts/tests/test_restauration.py | 22 ++++++++ 4 files changed, 86 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10f55af..2175878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/roles/client_backup/defaults/main.yml b/roles/client_backup/defaults/main.yml index e120bcb..f960b0b 100644 --- a/roles/client_backup/defaults/main.yml +++ b/roles/client_backup/defaults/main.yml @@ -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 diff --git a/roles/client_backup/files/setops-copie-a-froid.sh b/roles/client_backup/files/setops-copie-a-froid.sh index b613085..2857f72 100644 --- a/roles/client_backup/files/setops-copie-a-froid.sh +++ b/roles/client_backup/files/setops-copie-a-froid.sh @@ -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 diff --git a/scripts/tests/test_restauration.py b/scripts/tests/test_restauration.py index bf20c40..f55890b 100644 --- a/scripts/tests/test_restauration.py +++ b/scripts/tests/test_restauration.py @@ -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()