Aller au contenu

etcd fichiers de stockage persistant

Référence du format de stockage persistant et des fichiers

Ce document explique le format de stockage persistant d’etcd : nomenclature, contenu et outils permettant aux développeurs d’en inspecter le contenu. À l’avenir, ce document devrait être mis à jour pour refléter les évolutions du modèle de stockage. Il s’adresse aux développeurs d’etcd afin de les aider dans leurs besoins de récupération de données.

Prérequis

Les articles suivants fournissent des informations de fond utiles pour ce document :

Aperçu

Fichiers en attente prolongée

Nom de fichierObjectif général
./member/snap/db
bbolt b+tree qui stocke toutes les données appliquées, les informations d'autorisation d'appartenance et les métadonnées. Il est au courant de l'index du dernier journal WAL appliqué ("consistent_index").
./member/snap/0000000000000002-0000000000049425.snap
./member/snap/0000000000000002-0000000000061ace.snap

Instantanés périodiques de l’ancien magasin v2, contenant :

  • informations de base sur le membre
  • etcd-version

À compter de etcd v3, le contenu est redondant par rapport au contenu des fichiers /snap/db.

Périodiquement (30s), ces fichiers sont supprimés, et les derniers --max-snapshots=5sont conservés.

/member/snap/000000000007a178.snap.db

Un instantané bbolt téléchargé depuis le leader etcd si la réplica était trop en retard.

Possède le même type de contenu que le fichier (./member/snap/db)

Le fichier est utilisé dans deux scénarios :

  • En réponse à la demande du leader de se rétablir à partir de l'instantané.
  • Lors du démarrage du serveur, lorsque le dernier instantané (.snap.db) est trouvé et détecté comme ayant un index plus récent que l'index cohérent dans le fichier actuel snap.db.
Note : Les instantanés périodiques générés sur chaque réplica ne sont émis qu'au format de fichier *.snap (et non *.snap.db). Ainsi, aucune garantie n'est donnée que le dernier instantané (dans le journal WAL) dispose d'un fichier *.snap.db. Toutefois, dans un tel cas, le backend (snap/db) doit être plus récent que l'instantané.

Le fichier n'est pas supprimé une fois la récupération terminée (le contenu entier est donc copié dans le fichier ./member/snap/db). Périodiquement (30s), les fichiers sont purgés. Ici aussi, --max-snapshots=5 sont conservés. Comme ces fichiers peuvent atteindre plusieurs Go, cela peut entraîner un risque d'épuisement de l'espace disque.

./member/wal/000000000000000f-00000000000b38c7.wal
./member/wal/000000000000000e-00000000000a7fe3.wal
./member/wal/000000000000000d-000000000009c70c.wal

Journaux d'écriture de Raft, contenant les transactions récentes acceptées par Raft, ainsi que des instantanés périodiques ou des enregistrements CRC.

Les fichiers récents --max-wals=5 sont conservés. Chaque fichier mesure ~64*10^6 octets. Le fichier est tronqué lorsqu'il dépasse cette taille fixée, de sorte que les fichiers peuvent légèrement dépasser cette taille (le préallocation 0.tmp ne garantit donc pas une protection complète contre le dépassement du disque).

Si les instantanés sont trop espacés, il peut y avoir plus de --max-wals=5, car les verrous au niveau du système de fichiers protègent les fichiers, empêchant qu'ils ne soient supprimés trop tôt.

./member/wal/0.tmp (or .../1.tmp)
Espace préalloué pour le prochain fichier de journalisation d'avance. Utilisé pour éviter que Raft ne reste bloqué en raison d'un manque de capacité des journaux WAL, sans possibilité de déclencher une alarme.

Fichiers temporaires

Pendant le traitement interne d’etcd, il est possible de rencontrer plusieurs fichiers à durée de vie courte :

FichierObjectif général
./member/snap/0000000000000002-000000000007a178.snap.broken

Les fichiers d'instantané sont renommés en « broken » lorsqu'ils ne peuvent pas être chargés.

L'essai de charger le fichier le plus récent a lieu lorsque etcd est démarré.

Ou lors des commandes de sauvegarde/restauration d'etcdctl.

./member/snap/tmp071677638 (random suffix)

Fichier temporaire (bbolt) créé sur les réplicas en réponse à la demande de snapshot du leader, afin de répondre à la demande du leader de restaurer le stockage à partir de l'instantané fourni.

Après une récupération réussie (complète) du contenu, le fichier est renommé en : /member/snap/[SNAPSHOT-INDEX].snap.db. En cas de mort du serveur ou de son interruption pendant le téléchargement des fichiers, ceux-ci restent sur le disque et ne sont jamais nettoyés automatiquement. Ils peuvent être de taille importante (en gigaoctets).

Voir etcd/issues/12837. Corrigé dans etcd 3.5.

/member/snap/db.tmp.071677638 (random suffix)

Un fichier temporaire contenant une copie du contenu du backend (/member/snap/db), pendant le processus de défragmentation. Une fois le processus réussi, le fichier est renommé en /member/snap/db, remplaçant ainsi le backend original.

Au démarrage du serveur etcd, ces fichiers sontsupprimés.

bbolt arbre B+ : member/snap/db

Ce fichier contient le contenu principal d’etcd, appliqué à un point spécifique du journal Raft (voir consistent_index ).

Organisation physique

Le stockage bolt est physiquement organisé sous la forme d’un arbre b+tree . Les pages physiques de l’arbre b+ ne sont jamais modifiées in situ[^1]. À la place, leur contenu est copié vers une nouvelle page (récupérée à partir de la liste des pages libres), et la page ancienne est ajoutée à la liste des pages libres dès qu’aucune transaction ouverte ne peut plus y accéder. Grâce à ce processus, une transaction RO ouverte voit un état cohérent de l’historique du stockage. Une transaction RW est exclusive et bloque toutes les autres transactions RW. Les grandes valeurs sont stockées sur plusieurs pages continues. Le processus de récupération de pages combiné à la nécessité d’attribuer des zones contiguës de pages de tailles différentes peut entraîner une fragmentation croissante du stockage bbolt.

Le fichier bbolt ne se réduit jamais automatiquement. Seul le processus de défragmentation permet de réécrire le fichier dans un nouveau fichier disposant d’une certaine réserve de pages libres à la fin et dont la taille a été tronquée.

Organisation logique

Le stockage bbolt est divisé en compartiments. Dans chaque compartiment, des clés (paires byte[]->byte[] valeurs) sont stockées dans l’ordre lexicographique. La liste ci-dessous représente les compartiments utilisés par etcd (à partir de la version 3.5) ainsi que les clés en usage.

BucketCléValeur d'exempleDescription
alarmerpcpb.Alarm : {MemberID, Alarme : NONE|NOSPACE|CORRUPT}nilIndique que des problèmes ont été diagnostiqués sur l'un des membres.
auth"authRevision""" (vide) ou BigEndian.PutUint64

Tout changement de rôles ou d'utilisateurs incrémente ce champ à la validation de la transaction.

La valeur n'est utilisée que pour le verrouillage optimiste durant le processus d'autorisation.

authRoles[roleName] en tant que chaîneauthpb.Role sérialisé
authUsers[userName] en tant que chaîneauthpb.User sérialisé
cluster"clusterVersion""3.5.0" (chaîne)mineur version du consensus-agréé de la version de stockage commun.
"désinstaller"JSON:
{
  "target-version": "3.4.0"
  "enabled": true/false
}

Persiste l'intention configurée par la requête la plus récente : Downgrade RPC.

Depuis la version v3.5

clé

[révisionId] encodée à l'aide de bytesToRev{principal,sous}

Les suppressions de paires clé-valeur sont sérialisées avec un « t » à la fin (comme une « sépulture »)

mvccpb.KeyValue protocole encodé (key, create_rev, mod_rev, version, value, lease id)
bailleasepb.Lease protocole marshalled (ID, TTL, RemainingTTL)

Remarque : LeaseCheckpoint étend uniquement RemainingTTL. Le TTL provient uniquement de l'origine de Grant.

Note2 : les TTL sont conservés en secondes (à partir de la valeur « now » non définie). Un serveur pris dans une boucle de redémarrage ne libère pas les baux !!!

membres[memberId] en hexadécimal sous forme de chaîne : "8e9e05c52164694d"Chaîne JSON sérialisée du type membre :
{
  "id":10276657743932975437,
  "peerURLs":[
  "http://localhost:2380"],
  "name":"default",
  "clientURLs": ["http://localhost:2379"]
}
Informations d'appartenance au cluster convenues.
membres_supprimés[memberId] en hexadécimal sous forme de chaîne : "8e9e05c52164694d"[]byte("removed")

Identifiants de tous les membres supprimés. Utilisé pour vérifier qu'un membre supprimé n'est jamais réajouté sous le même identifiant.

Le champ est actuellement (3.4) lu à partir du magasin V2 et jamais à partir de V3. Voir https://github.com/etcd-io/etcd/pull/12820

meta"consistent_index"uint64 octets (BigEndian)Représente le décalage de la dernière entrée WAL appliquée au stockage Bolt DB.
"scheduledCompactRev"bytesToRevencodés {main,sub}. (16 octets)Utilisé pour réinitialiser le compactage si un incident s'est produit après une demande de compactage.
"finishedCompactRev"bytesToRevencodés {main,sub}. (16 octets)Révision à laquelle le magasin a été récemment compacté (https://github.com/etcd-io/etcd/blob/ae7862e8bc8007eb396099db4e0e04ac026c8df5/server/mvcc/kvstore_compaction.go#L54)
"confState"Depuis etcd 3.5
"term"Depuis etcd 3.5
"version-stockage"

Outils

bbolt

bbolt dispose d’un outil en ligne de commande permettant d’inspecter le contenu du fichier.

Exemples d’utilisation :

Lister tous les buckets dans le fichier bbolt donné :
% go run go.etcd.io/bbolt/cmd/bbolt buckets ./default.etcd/member/snap/db
Lire une paire clé/valeur particulière :
% go run go.etcd.io/bbolt/cmd/bbolt get ./default.etcd/member/snap/db cluster clusterVersion

etcd-dump-db

etcd-dump-db peut être utilisé pour lister le contenu du backend v3 d’etcd (bbolt).

% go run go.etcd.io/etcd/v3/tools/etcd-dump-db  list-bucket default.etcd
alarm
auth
...

Voir d’autres exemples dans : https://github.com/etcd-io/etcd/tree/master/tools/etcd-dump-db

WAL : journal d’écriture anticipée

Le journal d’écriture anticipée (Write ahead log) est un stockage persistant Raft utilisé pour stocker les propositions. Le leader stocke d’abord la proposition dans son journal, puis la réplique simultanément aux suiveurs à l’aide du protocole Raft. Chaque suiveur persiste la proposition dans son WAL avant de confirmer la réplication au leader.

Le journal WAL utilisé dans etcd diffère du modèle Raft canonique en deux sens :

  • Il persiste non seulement les entrées indexées, mais aussi les instantanés Raft (légers) et l’état dur. Ainsi, l’état Raft complet du membre peut être récupéré à partir du journal WAL seul.
  • Il est en écriture seule. Les entrées ne sont pas remplacées in situ, mais une entrée ajoutée ultérieurement dans le fichier (avec le même index) remplace la précédente.

Noms de fichiers

Les fichiers de journal WAL sont nommés selon le motif suivant :

"%016x-%016x.wal", seq, index

Exemple : ./member/wal/0000000000000010-00000000000bf1e6.wal

Ainsi, les noms de fichiers contiennent des chaînes codées en hexadécimal :

  • Numéro séquentiel du fichier de journal WAL
  • Index de la première entrée ou instantané dans le fichier. En particulier, le premier fichier « 0000000000000000-0000000000000000.wal » contient l’enregistrement initial d’instantané avec l’index=0.

Contenu physique

Le fichier de journal WAL contient une séquence de “Frames ”. Chaque trame contient :

  1. LittleEndian [^2] entier non signé 64 bits encodé qui contient la longueur de la structure walpb.Record (3).
  2. Remplissage : un certain nombre d’octets nuls, de manière à ce que la taille totale du cadre soit alignée (modulo 8)
  3. Données marshallées walpb.Record :
    1. type - énumération entière codée déterminant l’interprétation du champ de données ci-dessous
    2. data - selon le type, généralement une donnée protocole marshallée
    3. crc - somme de contrôle RC-32 de tous les champs « data » combinés (sans type) dans tous les enregistrements du journal sur cette réplica particulière depuis la création du journal WAL. Veuillez noter que la somme de contrôle prend en compte TOUS les enregistrements (même ceux qui n’ont pas été validés par Raft).

Les fichiers sont « coupés » (un nouveau fichier est créé) lorsque le fichier actuel dépasse 64*10^6 octets.

Contenu logique

Les fichiers de journalisation anticipée dans la couche logique contiennent :

  • Raftpb.Entry: propositions récentes répliquées par le leader Raft. Certaines de ces propositions sont considérées comme « validées », tandis que d’autres peuvent être logiquement remplacées.
  • Raftpb.HardState(term,commit,vote): information périodique (très fréquente) sur l’index d’une entrée de journal qui est « validée » (répliquée sur la majorité des serveurs), garantissant ainsi qu’elle ne sera pas modifiée ou remplacée, et pouvant être appliquée aux backends (v2, v3). Elle contient également un « terme » (indicateur indiquant s’il y a eu des modifications liées à une élection) et un vote — le membre pour lequel la réplique actuelle a voté durant le terme en cours.
  • walpb.Snapshot(term, index): instantanés périodiques de l’état Raft (aucun contenu de base de données, uniquement l’index du journal d’instantané et le terme Raft)
    • Le contenu du magasin V2 est stocké dans des fichiers *.store séparés.
    • Le contenu du magasin V3 est conservé dans le fichier bbolt, et devient un instantané implicite dès que les entrées y sont appliquées.
  • enregistrement de somme de contrôle crc32 (au début de chaque fichier), utilisé pour reprendre le contrôle CRC pour le reste du fichier.
  • etcdserverpb.Metadata(node_id, cluster_id) - identification du cluster et de la réplica représentés par le journal.

Chaque fichier de journal WAL est construit à partir de (dans l’ordre) :

  1. CRC-32 (valeur CRC calculée sur tous les fichiers précédents, 0 pour le premier fichier).

  2. Cadre de métadonnées (identifiants du cluster et de la réplica)

  3. Uniquement pour le premier fichier WAL :

    • Trame d’instantané vide (Index : 0, Terme : 0). L’objectif de cette trame est de maintenir l’invariant selon lequel toutes les entrées sont « précédées » par un instantané.

Pour le fichier WAL non initial (2e+ fichier) :

* Trame HardState.
  1. Mélange d’entrées, d’états durs et d’enregistrements d’instantané

Le journal WAL peut contenir plusieurs entrées pour l’index identique. Une telle situation peut survenir dans les cas décrits dans la figure 7 du document Raft . Le journal WAL d’etcd est uniquement ajouté, les entrées sont donc remplacées en ajoutant une nouvelle entrée portant le même index.

En particulier lors de la lecture du WAL, la logique remplace les anciennes entrées par les nouvelles . Ainsi, seule la dernière version des entrées dont entry.index <= HardState.commit peut être considérée comme définitive. Les entrées dont l’index est supérieur à HardState.commit sont sujettes à modification.

Les « termes » dans le journal WAL sont censés être monotones.

Les « indexes » dans le journal WAL sont censés :

  1. démarre à partir d’un instantané
  2. croît séquentiellement à partir de cet instantané tant qu’il reste dans le même « terme »
  3. si le terme change, l’index peut diminuer, mais uniquement jusqu’à une nouvelle valeur supérieure à celle de HardState.commit
  4. un nouvel instantané peut avoir lieu avec un index quelconque supérieur ou égal à HardState.commit, ce qui ouvre une nouvelle séquence pour les index
fichiers de stockage persistant etcd

Outils

etcd-dump-logs

Les journaux WAL d’etcd peuvent être lus à l’aide de l’outil etcd-dump-logs :

% go install go.etcd.io/etcd/v3/tools/etcd-dump-logs@latest

% go run go.etcd.io/etcd/v3/tools/etcd-dump-logs --start-index=0 aname.etcd

Prenez note que :

  • Outil qui affiche uniquement les entrées, et non toutes les enregistrements WAL (instantanés, HardStates) présents dans les fichiers de journal WAL.
  • L’outil applique automatiquement des « substitutions » aux entrées. Si une entrée est remplacée (par une entrée plus récente au même index), l’outil n’affiche que la valeur finale.
  • L’outil affiche également les entrées non validées (issues de la fin du LOG), sans information sur HardState.commitIndex, de sorte qu’il n’est pas possible de savoir si les entrées sont définitives ou non.

Instantanés de (Store V2) : membre/snap/{term}-{index}.snap

Noms de fichiers :

membre/snap/{term}-{index}.snap

Les noms de fichiers sont générés ici ("%016x-%016x.snap") et utilisent deux composants encodés en hexadécimal :

  • term -> Terme Raft (période entre les élections) au moment de l’émission de l’instantané
  • index -> Index de la dernière proposition appliquée au moment de l’émission de l’instantané

Création

Les fichiers *.snap sont créés par la méthode Snapshotter.SaveSnap .

Il existe 2 déclencheurs contrôlant la création de ces fichiers :

  • Un nouveau fichier est créé toutes les –snapshotCount= propositions appliquées environ (100'000 par défaut). Cette valeur est approximative : les propositions peuvent arriver par lots, la création d’un instantané n’est envisagée qu’à la fin du lot et le processus est finalement planifié de manière asynchrone. Le nom de l’option (–snapshotCount) est assez trompeur : elle contrôle la différence de valeur d’index entre le dernier index d’instantané et le dernier index de proposition appliquée.
  • Raft demande au réplica de restaurer à partir de l’instantané. Pendant qu’un réplica reçoit l’instantané via le message msgSnap, il le sauvegarde également (de manière légère) dans le journal WAL. Cela garantit que dans la queue du journal WAL se trouve toujours un instantané valide suivi d’entrées. Cela supprime ainsi tout risque de discontinuité dans les journaux WAL.

Actuellement, les fichiers sont approximativement [^3] associés 1 à 1 aux journaux WAL. Avec le décommissionnement du store v2, nous prévoyons que les fichiers ne seront plus écrits du tout (optionnel : 3.5.x, obligatoire : 3.6.x).

Contenu

Le fichier contient un proto snapdb.snapshot (uint32 crc, bytes data) marshallé,

qui se trouve dans le champ ‘data’ et contient Raftpb.Snapshot :

(bytes data, SnapshotMetadata{index, term, conf } metadata),

Enfin, les données imbriquées contiennent un contenu store v2 sérialisé au format JSON.

En particulier, il y a :

  • Terme
  • Index
  • Données d’appartenance :
    • /0/members/8e9e05c52164694d/attributes -> {"name":"default","clientURLs":["http://localhost:2379"]}
    • /0/members/8e9e05c52164694d/RaftAttributes -> "{"peerURLs":["http://localhost:2380"]}"
  • Version du stockage : /0/version- > 3.5.0

Outils

protoc

La commande suivante vous permet de visualiser le contenu du fichier lorsqu’elle est exécutée depuis le répertoire racine d’etcd :

cat default.etcd/member/snap/0000000000000002-0000000000049425.snap |
  protoc --decode=snappb.snapshot \
    server/etcdserver/api/snap/snappb/snap.proto \
    -I $(go list -f '{{.Dir}}' github.com/gogo/protobuf/proto)/.. \
    -I . \
    -I $(go list -m -f '{{.Dir}}' github.com/gogo/protobuf)/protobuf

De même, vous pouvez extraire le champ ‘data’ et le décoder en tant que ‘Raftpb.Snapshot '

Exemple de contenu du magasin sérialisé JSON version 2 dans les fichiers *.snap d’etcd 3.4 :

{
  "Root":{
    "Path":"/",
    "CreatedIndex":0,
    "ModifiedIndex":0,
    "ExpireTime":"0001-01-01T00:00:00Z",
    "Value":"",
    "Children":{
      "0":{
        "Path":"/0",
        "CreatedIndex":0,
        "ModifiedIndex":0,
        "ExpireTime":"0001-01-01T00:00:00Z",
        "Value":"",
        "Children":{
          "members":{
            "Path":"/0/members",
            "CreatedIndex":1,
            "ModifiedIndex":1,
            "ExpireTime":"0001-01-01T00:00:00Z",
            "Value":"",
            "Children":{
              "8e9e05c52164694d":{
                "Path":"/0/members/8e9e05c52164694d",
                "CreatedIndex":1,
                "ModifiedIndex":1,
                "ExpireTime":"0001-01-01T00:00:00Z",
                "Value":"",
                "Children":{
                  "attributes":{
                    "Path":"/0/members/8e9e05c52164694d/attributes",
                    "CreatedIndex":2,
                    "ModifiedIndex":2,
                    "ExpireTime":"0001-01-01T00:00:00Z",
                    "Value":"{\"name\":\"default\",\"clientURLs\":[\"http://localhost:2379\"]}",
                    "Children":null
                  },
                  "RaftAttributes":{
                    "Path":"/0/members/8e9e05c52164694d/RaftAttributes",
                    "CreatedIndex":1,
                    "ModifiedIndex":1,
                    "ExpireTime":"0001-01-01T00:00:00Z",
                    "Value":"{\"peerURLs\":[\"http://localhost:2380\"]}",
                    "Children":null
                  }
                }
              }
            }
          },
          "version":{
            "Path":"/0/version",
            "CreatedIndex":3,
            "ModifiedIndex":3,
            "ExpireTime":"0001-01-01T00:00:00Z",
            "Value":"3.5.0",
            "Children":null
          }
        }
      },
      "1":{
        "Path":"/1",
        "CreatedIndex":0,
        "ModifiedIndex":0,
        "ExpireTime":"0001-01-01T00:00:00Z",
        "Value":"",
        "Children":{


        }
      }
    }
  },
  "WatcherHub":{
    "EventHistory":{
      "Queue":{
        "Events":[
          {
            "action":"create",
            "node":{
              "key":"/0/members/8e9e05c52164694d/RaftAttributes",
              "value":"{\"peerURLs\":[\"http://localhost:2380\"]}",
              "modifiedIndex":1,
              "createdIndex":1
            }
          },
          {
            "action":"set",
            "node":{
              "key":"/0/members/8e9e05c52164694d/attributes",
              "value":"{\"name\":\"default\",\"clientURLs\":[\"http://localhost:2379\"]}",
              "modifiedIndex":2,
              "createdIndex":2
            }
          },
          {
            "action":"set",
            "node":{
              "key":"/0/version",
              "value":"3.5.0",
              "modifiedIndex":3,
              "createdIndex":3
            }
          }
        ]
      }
    }
  }
}

Modifications

Cette section est réservée à la description des modifications apportées aux formats de fichier introduits entre différentes versions d’etcd.

[^1] : Les pages de métadonnées situées au début du fichier bbolt sont modifiées in situ.

[^2] : Incohérent, car la majorité des uint sont écrits en big endian

[^3] : L’instantané initial (index : 0) au début du journal WAL n’est pas associé à un fichier *.snap. Les anciens fichiers *.snap (ou journaux WAL) peuvent être supprimés.