Dateien der persistenten Speicherung in etcd
Dieses Dokument erläutert das Format der persistenten Speicherung in etcd: Benennung, Inhalt und Werkzeuge, mit denen Entwickler die Dateien untersuchen können. Künftig sollte es um Änderungen am Speichermodell erweitert werden. Es richtet sich an etcd-Entwickler und soll sie bei der Datenwiederherstellung unterstützen.
Voraussetzungen
Die folgenden Artikel liefern hilfreiche Hintergrundinformationen zu diesem Dokument:
- Überblick über das Datenmodell von etcd
- Überblick über Raft , insbesondere Abschnitt „5.3 Logreplikation“.
Überblick
Langlebige Dateien
| Dateiname | Zweck |
|---|---|
./member/snap/db |
bbolt B+-Baum, der alle angewendeten Daten, Informationen zur Mitgliedschaft und Autorisierung sowie Metadaten speichert. Er kennt den zuletzt angewendeten WAL-Index ("consistent_index"). |
./member/snap/0000000000000002-0000000000049425.snap ./member/snap/0000000000000002-0000000000061ace.snap |
Regelmäßige Snapshots des alten v2-Speichers, mit:
Seit etcd v3 ist dieser Inhalt gegenüber den /snap/db-Dateien redundant. Regelmäßig (30s) werden diese Dateien bereinigt, wobei die letzten |
/member/snap/000000000007a178.snap.db |
Ein vollständiger heruntergeladener bbolt-Snapshot vom etcd-Leader, falls das Replikat zu weit zurücklag. Enthält dieselbe Art von Daten wie die Datei ( Die Datei wird in 2 Fällen verwendet:
Die Datei wird nach Abschluss der Wiederherstellung nicht gelöscht (der gesamte Inhalt wurde dann in ./member/snap/db übernommen). Regelmäßig (30s) werden die Dateien bereinigt.
Auch hier bleiben |
./member/wal/000000000000000f-00000000000b38c7.wal ./member/wal/000000000000000e-00000000000a7fe3.wal ./member/wal/000000000000000d-000000000009c70c.wal |
Write-Ahead-Logs von Raft, mit aktuellen von Raft akzeptierten Transaktionen, regelmäßigen Snapshots oder CRC-Einträgen. Die neuesten Bei zu seltenen Snapshots können mehr als |
./member/wal/0.tmp (or .../1.tmp) |
Vorab zugewiesener Speicherplatz für die nächste Write-Ahead-Log-Datei. Verhindert, dass Raft wegen fehlender WAL-Kapazität blockiert, ohne einen Alarm auslösen zu können. |
Temporäre Dateien
Während der internen Verarbeitung von etcd können mehrere kurzlebige Dateien auftreten:
| Datei | Zweck |
|---|---|
./member/snap/0000000000000002-000000000007a178.snap.broken |
Snapshot-Dateien werden in „broken“ umbenannt, wenn sie nicht geladen werden können.
Der Versuch, die neueste Datei zu laden, erfolgt, wenn etcd gestartet wird. Oder während der backup/migrate-Befehle von etcdctl. |
./member/snap/tmp071677638 (random suffix) |
Temporäre bbolt-Datei, die auf Replikaten als Antwort auf die msgSnap-Anfrage des Leaders erstellt wird, also auf dessen Aufforderung, den Speicher aus dem angegebenen Snapshot wiederherzustellen.
Nach erfolgreichem vollständigem Abruf des Inhalts wird die Datei umbenannt in: Siehe etcd/issues/12837. In etcd 3.5 behoben. |
/member/snap/db.tmp.071677638 (random suffix) |
Eine temporäre Datei, die während der Defragmentierung eine Kopie des Backend-Inhalts (/member/snap/db) enthält. Nach erfolgreichem Abschluss wird sie in /member/snap/db umbenannt und ersetzt das ursprüngliche Backend. Beim Start des etcd-Servers werden diese Dateien bereinigt. |
bbolt-B+-Baum: member/snap/db
Diese Datei enthält die Hauptdaten von etcd, angewendet bis zu einer bestimmten Position im Raft-Log (siehe consistent_index ).
Physischer Aufbau
Der Speicher von „better bolt“ ist physisch als B+-Baum
organisiert. Die physischen Seiten des B-Baums werden niemals an Ort und Stelle geändert1. Stattdessen wird ihr Inhalt auf eine neue Seite kopiert, die aus der Liste freier Seiten stammt. Die alte Seite wird dieser Liste hinzugefügt, sobald keine offene Transaktion mehr auf sie zugreifen könnte. Dadurch sieht eine offene RO-Transaktion einen konsistenten historischen Speicherzustand. Die RW-Transaktion ist exklusiv und blockiert alle anderen RW-Transaktionen.
Große Werte werden auf mehreren zusammenhängenden Seiten gespeichert. Die Wiederverwendung von Seiten zusammen mit der nötigen Zuweisung zusammenhängender Seitenbereiche unterschiedlicher Größe kann zu einer zunehmenden Fragmentierung des bbolt-Speichers führen.
Die bbolt-Datei schrumpft niemals von selbst. Nur bei der Defragmentierung kann sie in eine neue Datei mit einem Puffer freier Seiten am Ende und verkleinerter Dateigröße umgeschrieben werden.
Logischer Aufbau
Der bbolt-Speicher ist in Buckets unterteilt. In jedem Bucket werden Schlüssel (Paare vom Typ byte[]->value byte[]) in lexikografischer Reihenfolge gespeichert. Die folgende Liste zeigt die von etcd verwendeten Buckets mit Stand Version 3.5 und ihre Schlüssel.
| Bucket | Schlüssel | Beispielwert | Beschreibung |
|---|---|---|---|
| alarm | rpcpb.Alarm:
{MemberID, Alarm: NONE|NOSPACE|CORRUPT}
|
nil |
Zeigt an, dass bei einem Mitglied Probleme erkannt wurden. |
| auth | "authRevision" | "" (leer) oder BigEndian.PutUint64 |
Jede Änderung an Rollen oder Benutzern erhöht dieses Feld beim Bestätigen der Transaktion. Der Wert dient ausschließlich zur optimistischen Sperrung während des Autorisierungsvorgangs. |
| authRoles | [roleName] als Zeichenfolge | authpb.Role serialisiert |
|
| authUsers | [userName] als Zeichenfolge | authpb.User serialisiert |
|
| cluster | "clusterVersion" | "3.5.0" (string) |
Minor-Version der durch Konsens vereinbarten gemeinsamen Speicherversion. |
| "downgrade" | JSON: {
"target-version": "3.4.0"
"enabled": true/false
}
|
Speichert die Absicht dauerhaft, die durch die neueste Anfrage Seit v3.5 |
|
| key |
[revisionId], kodiert mit bytesToRev{main,sub} Löschungen von Schlüssel-Wert-Paaren werden mit einem abschließenden 't' als „Tombstone“ serialisiert |
mvccpb.KeyValue als serialisiertes Proto (key, create_rev, mod_rev, version, value, lease id) |
|
| lease | leasepb.Lease als serialisiertes Proto (ID, TTL, RemainingTTL) |
Hinweis: LeaseCheckpoint verlängert nur RemainingTTL. TTL stammt weiterhin vom ursprünglichen Grant. Hinweis2: TTLs werden in Sekunden ab einem nicht definierten „jetzt“ gespeichert. Ein ständig abstürzender und neu startender Server gibt keine Leases frei!!! |
|
| members | [memberId] hexadezimal als Zeichenfolge: "8e9e05c52164694d" |
Als JSON-Zeichenfolge serialisierte Member-Struktur:
{
"id":10276657743932975437,
"peerURLs":[
"http://localhost:2380"],
"name":"default",
"clientURLs": ["http://localhost:2379"]
}
|
Durch Konsens vereinbarte Informationen zur Clustermitgliedschaft. |
| members_removed | [memberId] hexadezimal als Zeichenfolge: "8e9e05c52164694d" |
[]byte("removed") |
IDs aller entfernten Mitglieder. Damit wird geprüft, dass ein entferntes Mitglied niemals unter derselben ID erneut hinzugefügt wird. Das Feld wird derzeit (3.4) aus dem V2-Speicher und niemals aus V3 gelesen. Siehe https://github.com/etcd-io/etcd/pull/12820 |
| meta | "consistent_index" | uint64-Bytes (BigEndian) | Repräsentiert den Offset des letzten auf den bolt-DB-Speicher angewendeten WAL-Eintrags. |
| "scheduledCompactRev" | bytesToRev{main,sub} kodiert. (16 Bytes) | Dient zur erneuten Initialisierung der Kompaktierung, wenn nach einer Kompaktierungsanfrage ein Absturz aufgetreten ist. | |
| "finishedCompactRev" | bytesToRev{main,sub} kodiert. (16 Bytes) | Revision, bis zu der der Speicher zuletzt erfolgreich kompaktiert wurde (https://github.com/etcd-io/etcd/blob/ae7862e8bc8007eb396099db4e0e04ac026c8df5/server/mvcc/kvstore_compaction.go#L54) | |
| "confState" | Seit etcd 3.5 | ||
| "term" | Seit etcd 3.5 | ||
| "storage-version" |
Werkzeuge
bbolt
bbolt bietet ein Befehlszeilenwerkzeug zum Untersuchen des Dateiinhalts.
Anwendungsbeispiele:
Alle Buckets einer bbolt-Datei auflisten:
Ein bestimmtes Schlüssel-Wert-Paar lesen:
etcd-dump-db
etcd-dump-db kann den Inhalt des etcd-v3-Backends (bbolt) auflisten.
Weitere Beispiele: https://github.com/etcd-io/etcd/tree/master/tools/etcd-dump-db
WAL: Write-Ahead-Log
Das Write-Ahead-Log ist ein persistenter Raft-Speicher für Vorschläge. Zuerst speichert der Leader den Vorschlag in seinem Log und repliziert ihn anschließend gleichzeitig über das Raft-Protokoll an die Follower. Jeder Follower speichert den Vorschlag dauerhaft in seinem WAL, bevor er dem Leader die Replikation bestätigt.
Das in etcd verwendete WAL unterscheidet sich in 2 Punkten vom kanonischen Raft-Modell:
- Es speichert nicht nur indizierte Einträge dauerhaft, sondern auch kleine Raft-Snapshots und HardState. So lässt sich der gesamte Raft-Zustand eines Mitglieds allein aus dem WAL wiederherstellen.
- Es wird nur angehängt. Einträge werden nicht an Ort und Stelle überschrieben; ein später in der Datei angehängter Eintrag mit demselben Index ersetzt den vorherigen logisch.
Dateinamen
Die WAL-Dateien werden nach folgendem Muster benannt:
Beispiel: ./member/wal/0000000000000010-00000000000bf1e6.wal
Die Dateinamen enthalten also hexadezimal kodiert:
- Die fortlaufende Nummer der WAL-Datei
- Den Index des ersten Eintrags oder Snapshots in der Datei. Insbesondere enthält die erste Datei „0000000000000000-0000000000000000.wal“ den anfänglichen Snapshot-Eintrag mit index=0.
Physischer Inhalt
Die WAL-Datei enthält eine Folge von „Frames “. Jeder Frame enthält:
- Eine in LittleEndian 2 kodierte uint64-Zahl mit der Länge des serialisierten walpb.Record (3).
- Auffüllung: Eine Anzahl von 0-Bytes, sodass die Größe des gesamten Frames ausgerichtet ist (mod 8)
- Serialisierte walpb.Record
-Daten:
- type - Als int kodierte Aufzählung, die die Interpretation des folgenden data-Felds bestimmt
- data - Je nach type gewöhnlich ein serialisiertes Proto
- crc - CRC-32-Prüfsumme aller zusammengefassten „data“-Felder ohne type in allen Logeinträgen dieses Replikats seit Erstellung des WAL. CRC berücksichtigt ALLE Einträge, auch wenn sie nicht durch Raft bestätigt wurden.
Eine neue Datei beginnt, sobald die aktuelle Datei 64*10^6 Bytes überschreitet.
Logischer Inhalt
Auf logischer Ebene enthalten Write-Ahead-Log-Dateien:
Raftpb.Entry:Aktuelle vom Raft-Leader replizierte Vorschläge. Einige gelten als bestätigt, andere können noch logisch überschrieben werden.Raftpb.HardState(term,commit,vote):Regelmäßige, sehr häufige Informationen über den Index eines bestätigten Logeintrags, der also auf die Mehrheit der Server repliziert wurde. Er wird garantiert nicht mehr geändert oder überschrieben und kann auf die Backends v2 und v3 angewendet werden. Enthalten sind außerdem ein „term“ als Hinweis auf wahlbezogene Änderungen sowie eine Stimme: das Mitglied, für das das aktuelle Replikat im aktuellen Term gestimmt hat.walpb.Snapshot(term, index):Regelmäßige Snapshots des Raft-Zustands ohne DB-Inhalt, nur mit Snapshot-Logindex und Raft-Term- Der Inhalt des V2-Speichers liegt in separaten *.store-Dateien.
- Der Inhalt des V3-Speichers wird in der bbolt-Datei verwaltet und bildet einen impliziten Snapshot, sobald die Einträge dort angewendet sind.
- Ein crc32-Prüfsummeneintrag am Anfang jeder Datei, um die CRC-Prüfung für den Rest der Datei fortzusetzen.
etcdserverpb.Metadata(node_id, cluster_id)- Identifiziert den Cluster und das Replikat, für die das Log steht.
Jede WAL-Datei besteht in dieser Reihenfolge aus:
-
CRC-32-Frame mit der fortlaufenden crc aller vorherigen Dateien, 0 für die erste Datei.
-
Metadaten-Frame mit Cluster- und Replikat-IDs
-
Nur für die erste WAL-Datei:
- Leerer Snapshot-Frame (Index:0, Term: 0). Er stellt die Invariante sicher, dass allen Einträgen ein Snapshot vorausgeht.
Für spätere WAL-Dateien (ab der 2.):
- HardState-Frame.
-
Mischung aus Entry-, HardState- und Snapshot-Einträgen
Das WAL kann mehrere Einträge mit demselben Index enthalten. Dies kann in den in Abbildung 7. der Raft-Veröffentlichung beschriebenen Fällen auftreten. Da an das etcd-WAL nur angehängt wird, werden Einträge durch Anhängen eines neuen Eintrags mit demselben Index überschrieben.
Insbesondere ersetzt die Leselogik alte Einträge durch neuere Einträge . Daher kann nur die jeweils letzte Version von Einträgen mit entry.index <= HardState.commit als endgültig gelten. Einträge mit index > HardState.commit können sich noch ändern.
Es wird erwartet, dass die Terms im WAL monoton sind.
Für die Indizes im WAL wird Folgendes erwartet:
- Sie beginnen bei einem Snapshot.
- Nach diesem Snapshot steigen sie fortlaufend, solange sie im selben Term bleiben.
- Bei einem Term-Wechsel kann der Index sinken, jedoch nur auf einen neuen Wert oberhalb des neuesten HardState.commit.
- Ein neuer Snapshot kann mit jedem Index >= HardState.commit auftreten und beginnt eine neue Indexfolge.

Werkzeuge
etcd-dump-logs
WAL-Dateien von etcd können mit dem Werkzeug etcd-dump-logs gelesen werden:
Zu beachten:
- Das Werkzeug zeigt nur Entries und nicht alle WAL-Einträge wie Snapshots und HardStates in den WAL-Dateien.
- Es berücksichtigt automatisch das Überschreiben von Einträgen. Wurde ein Eintrag durch einen neueren mit demselben Index überschrieben, wird nur der endgültige Wert ausgegeben.
- Es gibt auch unbestätigte Einträge vom Ende des LOG aus, ohne Informationen zu HardState.commitIndex. Daher ist nicht erkennbar, ob diese Einträge endgültig sind.
Snapshots des V2-Speichers: member/snap/{term}-{index}.snap
Dateinamen:
member/snap/{term}-{index}.snap
Die Dateinamen werden hier
mit ("%016x-%016x.snap") erzeugt und verwenden 2 hexadezimal kodierte Bestandteile:
- term -> Raft-Term, also der Zeitraum zwischen Wahlen, zum Zeitpunkt der Snapshot-Erstellung
- index -> Index des zuletzt angewendeten Vorschlags zum Zeitpunkt der Snapshot-Erstellung
Erstellung
Die *.snap-Dateien werden von der Methode Snapshotter.SaveSnap erstellt.
Es gibt 2 Auslöser für die Erstellung dieser Dateien:
- Ungefähr alle –snapshot-count=(standardmäßig 10,000 in v3.7.0) angewendeten Vorschläge wird eine neue Datei erstellt. Dies ist nur ein Näherungswert, da Vorschläge gebündelt eintreffen können, ein Snapshot erst am Ende eines solchen Pakets geprüft und der Snapshot-Vorgang schließlich asynchron eingeplant wird. Der Optionsname –snapshot-count ist recht irreführend, da er die Differenz der Indexwerte zwischen dem letzten Snapshot und dem zuletzt angewendeten Vorschlag steuert.
- Raft fordert das Replikat auf, sich aus einem Snapshot wiederherzustellen. Während das Replikat den Snapshot über eine msgSnap-Nachricht empfängt, hält es ihn auch als kleinen Checkpoint im WAL fest. Damit ist garantiert, dass am Ende des WAL stets ein gültiger Snapshot gefolgt von Einträgen steht. Mögliche Lücken im WAL werden so vermieden.
Derzeit entsprechen die Dateien ungefähr3 im Verhältnis 1-1 den Snapshot-Einträgen im WAL. Mit der Stilllegung des v2-Speichers wird erwartet, dass diese Dateien überhaupt nicht mehr geschrieben werden, optional ab 3.5.x und verpflichtend ab 3.6.x.
Inhalt
Die Datei enthält das serialisierte snapdb.snapshot-Proto
(uint32 crc, bytes data),
das im Feld ‘data’ einen Raftpb.Snapshot enthält:
(bytes data, SnapshotMetadata{index, term, conf } metadata),
Die verschachtelten Daten enthalten schließlich den JSON-serialisierten Inhalt des v2-Speichers .
Insbesondere enthalten sind:
- Term
- Index
- Mitgliedschaftsdaten:
/0/members/8e9e05c52164694d/attributes -> {"name":"default","clientURLs":["http://localhost:2379"]}/0/members/8e9e05c52164694d/RaftAttributes -> "{"peerURLs":["http://localhost:2380"]}"
- Speicherversion: /0/version-> 3.5.0
Werkzeuge
protoc
Der folgende Befehl zeigt den Dateiinhalt an, wenn er im Stammverzeichnis von etcd ausgeführt wird:
Ebenso lässt sich das Feld ‘data’ extrahieren und als Raftpb.Snapshot dekodieren.
Beispiel für JSON-serialisierten v2-Speicherinhalt in *.snap-Dateien von etcd 3.4:
Änderungen
Dieser Abschnitt ist für die Beschreibung von Dateiformatänderungen zwischen etcd-Versionen vorgesehen.
-
Die Metadatenseiten am Anfang der bbolt-Datei werden an Ort und Stelle geändert. ↩︎
-
Inkonsistent, da die meisten uint-Werte in Big Endian geschrieben werden ↩︎
-
Der anfängliche Snapshot mit index:0 am Anfang des WAL ist keiner *.snap-Datei zugeordnet. Zudem können alte *.snap-Dateien oder WAL-Dateien bereinigt werden. ↩︎