~/blog/fr/loki-retention-boltdb-tsdb
La rétention de Loki échouait sans cesse : chunks de minuit et passage à TSDB

Loki conserve mes journaux pendant 60 jours. Toutes les quelques semaines, une alerte disait qu’il avait arrêté de le faire :
level=error msg="failed to apply retention"
table=index_20684
err="could not find entry of chunk fake/8c447f75f2bb29ad:1a0171d4039:1a0178b1dbc:caa17911 to remove it"
Je l’ai corrigée trois fois avant de la comprendre. Voici la quatrième correction, celle qui devrait être la dernière.
La mauvaise explication
La première fois qu’elle s’est déclenchée, l’histoire était facile à croire. Les données de Loki avaient un jour été déplacées de /tmp/loki vers un vrai répertoire, et des chunks s’étaient sans doute perdus en route. L’index pointait vers des chunks qui n’existaient plus, donc la rétention ne pouvait pas les supprimer.
Alors à chaque fois, je déplaçais la table d’index en échec dans un dossier de quarantaine et l’alerte disparaissait. Une ou deux semaines plus tard, elle revenait sur la table suivante. L’histoire a survécu parce que la correction marchait.
Ce qui se passait vraiment
La quatrième fois, j’ai vérifié l’histoire. J’ai décodé le nom du chunk dans l’erreur et cherché le fichier. Il était là, 4,4 Ko, écrit le jour en question. Rien n’était perdu.
L’identifiant du chunk porte sa propre plage de temps : les deux champs hexadécimaux après l’empreinte sont le premier et le dernier horodatage, en millisecondes. Décodés, ils donnaient 23 h 03 à 1 h 03 UTC.
Le chunk traversait minuit. L’ancien stockage d’index de Loki, boltdb-shipper, écrit une table d’index par jour, donc un chunk qui couvre deux jours est indexé dans les deux. Quand la rétention réécrit la table la plus ancienne pour en retirer les entrées expirées, elle ne trouve pas l’entrée de ce chunk là où elle l’attend, et abandonne toute la table.
Cet abandon avait un second effet que je n’avais pas remarqué : une table en échec bloque la rétention de toutes les tables plus anciennes. Sept tables déjà au-delà des 60 jours étaient encore sur le disque derrière celle en échec. La rétention n’échouait pas sur un jour ; elle s’était arrêtée.
boltdb-shipper est déprécié dans Loki 3.x pour des faiblesses de ce genre. La solution, c’est d’arrêter de l’écrire.
La correction durable : une nouvelle période de schéma
Le schema_config de Loki est une liste de périodes, chacune avec son stockage et son schéma. On ne peut pas modifier une période qui contient déjà des données, mais on peut en ajouter une qui commence dans le futur :
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
- from: 2026-10-20 # tomorrow: never a date that already has data
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_tsdb_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /var/lib/loki/tsdb-shipper-active
cache_location: /var/lib/loki/tsdb-shipper-cache
Vérifiez-la avant de redémarrer, parce qu’un Loki qui ne démarre pas, c’est plus aucun journal de nulle part :
loki -config.file=/etc/loki-config.yaml -verify-config
À partir de minuit UTC à la nouvelle date, les nouvelles données vont dans des tables TSDB (index_tsdb_<jour>), et les anciennes restent lisibles via la période boltdb. Les requêtes qui couvrent les deux fonctionnent de façon transparente. Une fois que les dernières données boltdb ont dépassé 60 jours, l’ancienne période peut être retirée.
Il y a un hic, et c’est pour ça que ce billet ne s’arrête pas là : d’ici là, les anciennes tables peuvent encore échouer. Soixante jours, c’est long pour continuer à les corriger à la main.
Couvrir la transition : un timer d’auto-réparation
La correction manuelle avait toujours été la même :
- Arrêter Loki (une dizaine de secondes).
- Déplacer toutes les tables d’index boltdb jusqu’à celle en échec incluse dans un dossier de quarantaine.
- Démarrer Loki.
- Loki en marche, déplacer les fichiers de chunks dont les données se terminaient avant la limite de rétention. Plus rien ne les indexe, donc c’est sans risque pendant qu’il tourne.
Je l’avais écrite sous forme de script, je l’ai donc mise sur un timer. Toutes les 15 minutes, loki-retention-heal lit les 30 dernières minutes du journal de Loki. S’il trouve failed to apply retention … table=index_N, il fait les quatre étapes pour N.
Les garde-fous comptent plus que le cas nominal, parce que le script arrête un service partagé et déplace des données :
- N doit déjà être au-delà de la rétention. Une table en échec encore dans les 60 jours est refusée : ce serait un autre bug, et la déplacer ferait perdre des données vivantes.
- La quarantaine doit être sur un autre système de fichiers, avec au moins 2 Gio libres. Sinon, il refuse.
- Chaque exécution a son propre dossier avec un
MANIFEST.tsvde ce qui a été déplacé et où, et unrestore.shqui remet tout en place. DRY_RUN=1affiche le plan et ne touche à rien. Avant d’activer le timer, je lui ai donné quatre extraits de journal enregistrés : pas d’échec, une table de frontière, une table encore dans la rétention (refusée), et une table déjà déplacée.- Il journalise dans syslog, donc ses propres décisions sont dans Loki comme tout le reste.
L’alerte a changé avec lui. « Loki Retention Failure » se déclenchait immédiatement ; elle attend désormais 45 minutes. C’est trois passages du timer, donc elle ne m’atteint que si l’auto-réparation a échoué.
Les chiffres
Sur les quatre occurrences, la quarantaine a recueilli 28 tables d’index et plus de 15 000 fichiers de chunks que la rétention aurait dû supprimer. Tout est sur le disque de sauvegarde avec un manifeste, au cas où j’aurais un jour besoin d’une ligne de journal d’août.
Changer le schéma et vider l’arriéré a coûté un arrêt de Loki de 7 secondes. Les dix hôtes envoyaient de nouveau leurs journaux en moins de 20 secondes, et une requête 50 jours en arrière renvoyait toujours des données.
Pièges
- « Des chunks ont été perdus » est une supposition tant qu’on n’a pas cherché le chunk. Décodez son nom et vérifiez le fichier avant de bâtir une correction sur l’histoire.
- Un chunk qui traverse minuit UTC atterrit dans deux tables boltdb quotidiennes. C’est la racine de cet échec de rétention.
- Une table en échec bloque la rétention de tout ce qui est plus ancien. L’occupation disque grimpe pendant que l’alerte ne nomme qu’une table.
- Une nouvelle période de schéma doit commencer dans le futur. Ne modifiez ni n’antidatez jamais une période qui contient des données.
-verify-configavant de redémarrer. Loki arrêté, ce sont les journaux de tous les hôtes en attente ou perdus.- Une auto-réparation a besoin d’un garde-fou. Retardez l’alerte au-delà de quelques cycles de réparation plutôt que de la supprimer.
- Pensez à la démonter. Quand la dernière table boltdb a expiré, désactivez le timer, retirez l’ancienne période et remettez l’alerte en immédiat.