Файлы постоянного хранилища etcd
В этом документе описан формат постоянного хранилища etcd: именование, содержимое и инструменты, позволяющие разработчикам исследовать файлы. В дальнейшем документ следует дополнять по мере изменений модели хранения. Он предназначен для разработчиков etcd и помогает при восстановлении данных.
Предварительные сведения
Для понимания документа полезны следующие вводные материалы:
- обзор модели данных etcd
- обзор Raft (особенно раздел “5.3 Log replication”).
Обзор
Долгоживущие файлы
| Имя файла | Высокоуровневое назначение |
|---|---|
./member/snap/db | b+tree bbolt, хранящее все применённые данные, сведения об авторизации состава кластера и метаданные. Оно знает последний применённый индекс журнала WAL ("consistent_index"). |
./member/snap/0000000000000002-0000000000049425.snap ./member/snap/0000000000000002-0000000000061ace.snap | Периодические снимки устаревшего хранилища v2, содержащие:
Начиная с etcd v3 их содержимое дублирует содержимое файлов /snap/db. Периодически (каждые 30s) эти файлы удаляются, при этом сохраняются последние |
/member/snap/000000000007a178.snap.db | Полный загруженный снимок bbolt с лидера etcd, если реплика отставала слишком сильно. Содержит данные того же типа, что и файл ( Файл используется в 2 сценариях:
Файл не удаляется после завершения восстановления, когда всё его содержимое перенесено в ./member/snap/db. Периодически (каждые 30s) файлы удаляются.
Здесь также сохраняются последние |
./member/wal/000000000000000f-00000000000b38c7.wal ./member/wal/000000000000000e-00000000000a7fe3.wal ./member/wal/000000000000000d-000000000009c70c.wal | Журналы предзаписи Raft, содержащие недавние транзакции, принятые Raft, периодические снимки или записи CRC. Сохраняются последние Если снимки создаются слишком редко, файлов может быть больше |
./member/wal/0.tmp (or .../1.tmp) | Предварительно выделенное пространство для следующего файла журнала предзаписи. Позволяет не допустить остановки Raft из-за нехватки места для журналов WAL без возможности активировать аварийный сигнал. |
Временные файлы
Во время внутренней обработки etcd могут встречаться несколько краткоживущих файлов:
| Файл | Высокоуровневое назначение |
|---|---|
./member/snap/0000000000000002-000000000007a178.snap.broken | Файлы снимков переименовываются в ‘broken’, если их невозможно загрузить. Попытка загрузить самый новый файл выполняется при запуске etcd. Либо при выполнении команд backup/migrate в etcdctl. |
./member/snap/tmp071677638 (random suffix) | Временный файл bbolt, создаваемый на репликах в ответ на запрос лидера msgSnap, то есть на требование лидера восстановить хранилище из заданного снимка. После успешного полного получения содержимого файл переименовывается в См. etcd/issues/12837. Исправлено в etcd 3.5. |
/member/snap/db.tmp.071677638 (random suffix) | Временный файл, содержащий копию содержимого бэкенда (/member/snap/db) во время дефрагментации. После её успешного завершения файл переименовывается в /member/snap/db и заменяет исходный бэкенд. При запуске сервера etcd эти файлы удаляются. |
b+tree bbolt: member/snap/db
Этот файл содержит основное содержимое etcd, применённое до определённой точки журнала Raft (см. consistent_index ).
Физическая организация
Физически хранилище better bolt организовано как b+tree
. Физические страницы b-tree никогда не изменяются на месте1. Вместо этого содержимое копируется на новую страницу, полученную из списка свободных, а старая страница добавляется в список свободных, как только не остаётся открытых транзакций, способных к ней обратиться. Благодаря этому открытая транзакция RO видит согласованное историческое состояние хранилища. Транзакция RW является исключительной и блокирует все остальные транзакции RW.
Большие значения хранятся на нескольких непрерывных страницах. Освобождение страниц в сочетании с необходимостью выделять непрерывные области страниц разного размера может усиливать фрагментацию хранилища bbolt.
Файл bbolt сам по себе никогда не уменьшается. Только при дефрагментации его можно переписать в новый файл с некоторым запасом свободных страниц в конце и усечённым размером.
Логическая организация
Хранилище bbolt разделено на сегменты. В каждом сегменте ключи (пары byte[]->value byte[]) хранятся в лексикографическом порядке. Ниже перечислены сегменты, используемые etcd по состоянию на версию 3.5, и задействованные ключи.
| Сегмент | Ключ | Пример значения | Описание |
|---|---|---|---|
| alarm | rpcpb.Alarm:
{MemberID, Alarm: NONE|NOSPACE|CORRUPT} | nil | Указывает, что у одного из участников диагностированы проблемы. |
| auth | "authRevision" | "" (empty) or BigEndian.PutUint64 | Любое изменение ролей или пользователей увеличивает это поле при фиксации транзакции. Значение используется только для оптимистической блокировки во время авторизации. |
| authRoles | [roleName] в виде строки | сериализованный authpb.Role | |
| authUsers | [userName] в виде строки | сериализованный authpb.User | |
| cluster | "clusterVersion" | "3.5.0" (string) | Дополнительная версия общей версии хранилища, согласованной консенсусом. |
| "downgrade" | JSON:{
"target-version": "3.4.0"
"enabled": true/false
} | Сохраняет намерение, заданное последним запросом Начиная с v3.5 | |
| key | [revisionId], закодированный с помощью bytesToRev{main,sub} Удаления пар «ключ — значение» сериализуются с 't' в конце (как "Tombstone") | сериализованный proto mvccpb.KeyValue (key, create_rev, mod_rev, version, value, lease id) | |
| lease | сериализованный proto leasepb.Lease (ID, TTL, RemainingTTL) | Примечание: LeaseCheckpoint продлевает только RemainingTTL. TTL берётся из исходного Grant. Примечание 2: TTL сохраняются в секундах (от неопределённого 'now'). Сервер в цикле аварийных перезапусков не освобождает аренды!!! | |
| members | [memberId] в шестнадцатеричном виде как строка: "8e9e05c52164694d" | Структура Member, сериализованная в JSON как строка:{
"id":10276657743932975437,
"peerURLs":[
"http://localhost:2380"],
"name":"default",
"clientURLs": ["http://localhost:2379"]
} | Согласованные сведения о составе кластера. |
| members_removed | [memberId] в шестнадцатеричном виде как строка: "8e9e05c52164694d" | []byte("removed") | Идентификаторы всех удалённых участников. Используются для проверки, что удалённый участник никогда не добавляется снова с тем же идентификатором. Сейчас (3.4) поле читается из хранилища V2 и никогда из V3. См. https://github.com/etcd-io/etcd/pull/12820 |
| meta | "consistent_index" | байты uint64 (BigEndian) | Представляет смещение последней записи WAL, применённой к хранилищу БД bolt. |
| "scheduledCompactRev" | закодированный bytesToRev{main,sub}. (16 байтов) | Используется для повторной инициализации компактизации, если после её запроса произошёл сбой. | |
| "finishedCompactRev" | закодированный bytesToRev{main,sub}. (16 байтов) | Ревизия, на которой недавно успешно компактизировано хранилище (https://github.com/etcd-io/etcd/blob/ae7862e8bc8007eb396099db4e0e04ac026c8df5/server/mvcc/kvstore_compaction.go#L54) | |
| "confState" | Начиная с etcd 3.5 | ||
| "term" | Начиная с etcd 3.5 | ||
| "storage-version" |
Инструменты
bbolt
bbolt предоставляет инструмент командной строки для исследования содержимого файла.
Примеры использования:
Перечисление всех сегментов заданного файла bbolt:
Чтение определённой пары «ключ — значение»:
etcd-dump-db
etcd-dump-db позволяет перечислить содержимое бэкенда etcd v3 (bbolt).
Дополнительные примеры: https://github.com/etcd-io/etcd/tree/master/tools/etcd-dump-db
WAL: журнал предзаписи
Журнал предзаписи — постоянное хранилище Raft для предложений. Сначала лидер сохраняет предложение в своём журнале, а затем параллельно реплицирует его последователям по протоколу Raft. Каждый последователь сохраняет предложение в своём WAL, прежде чем подтвердить репликацию лидеру.
Используемый в etcd журнал WAL отличается от канонической модели Raft по 2 признакам:
- Он сохраняет не только индексированные записи, но также облегчённые снимки Raft и hard-state. Поэтому полное состояние Raft участника можно восстановить только из журнала WAL.
- Он доступен только для добавления. Записи не перезаписываются на месте: добавленная позднее в файл запись с тем же индексом замещает предыдущую.
Имена файлов
Файлы журнала WAL именуются по следующему шаблону:
Пример: ./member/wal/0000000000000010-00000000000bf1e6.wal
Таким образом, имя файла содержит в шестнадцатеричной кодировке:
- Порядковый номер файла журнала WAL
- Индекс первой записи или снимка в файле. В частности, первый файл “0000000000000000-0000000000000000.wal” содержит запись исходного снимка с index=0.
Физическое содержимое
Файл журнала WAL содержит последовательность «кадров ». Каждый кадр содержит:
- Закодированное в LittleEndian 2 значение uint64, содержащее длину сериализованной walpb.Record (3).
- Заполнение: некоторое количество байтов 0, обеспечивающее выравнивание размера всего кадра (mod 8)
- Сериализованные данные walpb.Record
:
- type — перечисление в кодировке int, определяющее интерпретацию поля data ниже
- data — в зависимости от типа, обычно сериализованный proto
- crc — контрольная сумма RC-32 совокупности всех полей “data” без type во всех записях журнала этой реплики с момента создания WAL. Обратите внимание: CRC учитывает ВСЕ записи, даже если Raft их не зафиксировал.
Файлы «обрезаются» (начинается новый файл), когда текущий превышает 64*10^6 байтов.
Логическое содержимое
На логическом уровне файлы журнала предзаписи содержат:
Raftpb.Entry:недавние предложения, реплицированные лидером Raft. Некоторые из них считаются зафиксированными, а остальные могут быть логически замещены.Raftpb.HardState(term,commit,vote):периодические и очень частые сведения об индексе зафиксированной записи журнала, то есть реплицированной на большинство серверов, гарантированно не подлежащей изменению или замещению и применимой к бэкендам (v2, v3). Также содержит “term”, указывающий на изменения, связанные с выборами, и vote — участника, за которого текущая реплика проголосовала в текущем сроке полномочий.walpb.Snapshot(term, index):периодические снимки состояния Raft без содержимого БД, только индекс журнала снимка и срок полномочий Raft- Содержимое хранилища V2 хранится в отдельных файлах *.store.
- Содержимое хранилища V3 находится в файле bbolt и становится неявным снимком сразу после применения записей.
- запись контрольной суммы crc32 в начале каждого файла, позволяющая продолжить проверку CRC для остальной части файла.
etcdserverpb.Metadata(node_id, cluster_id)— идентификаторы кластера и реплики, представленных журналом.
Каждый файл журнала WAL состоит из следующих элементов по порядку:
Кадр CRC-32 (накопленная crc всех предыдущих файлов, 0 для первого файла).
Кадр метаданных (идентификаторы кластера и реплики)
Только для исходного файла WAL:
- Пустой кадр Snapshot (Index:0, Term: 0). Он поддерживает инвариант, согласно которому всем записям «предшествует» снимок.
Для не исходного файла WAL (2nd+):
- Кадр HardState.
Смесь записей entry, hard-state и snapshot
Журнал WAL может содержать несколько записей с одним индексом. Такая ситуация возможна в случаях, описанных на рисунке 7 статьи о Raft . Журнал WAL etcd доступен только для добавления, поэтому запись замещается добавлением новой записи с тем же индексом.
В частности, при чтении WAL логика замещает старые записи новыми . Поэтому окончательной можно считать только последнюю версию записей с entry.index <= HardState.commit. Записи с index > HardState.commit могут изменяться.
Значения “terms” в журнале WAL должны быть монотонными.
Значения “indexes” в журнале WAL должны:
- начинаться с некоторого снимка
- последовательно расти после снимка, пока остаются в том же ‘term’
- при изменении term индекс может уменьшиться, но только до нового значения, превышающего последний HardState.commit.
- новый снимок может появиться с любым index >= HardState.commit, открывая новую последовательность индексов.

Инструменты
etcd-dump-logs
Журналы WAL etcd можно читать инструментом etcd-dump-logs :
Учитывайте следующее:
- Инструмент показывает только Entries, а не все записи WAL (Snapshots, HardStates) из файлов журнала.
- Инструмент автоматически применяет «замещения» записей. Если запись замещена более новой с тем же индексом, выводится только окончательное значение.
- Инструмент также выводит незафиксированные записи из хвоста LOG без сведений о HardState.commitIndex, поэтому неизвестно, являются ли они окончательными.
Снимки (хранилища V2): member/snap/{term}-{index}.snap
Имена файлов:
member/snap/{term}-{index}.snap
Имена файлов создаются здесь
по шаблону ("%016x-%016x.snap") и содержат 2 компонента в шестнадцатеричной кодировке:
- term -> срок полномочий Raft (период между выборами) на момент создания снимка
- index -> индекс последнего применённого предложения на момент создания снимка
Создание
Файлы *.snap создаются методом Snapshotter.SaveSnap .
Создание этих файлов управляется 2 триггерами:
- Новый файл создаётся примерно каждые –snapshotCount=(по умолчанию 100'000) применённых предложений. Значение приблизительно, поскольку предложения могут поступать пакетами, создание снимка рассматривается только в конце пакета, а сам процесс планируется асинхронно. Имя флага (–snapshotCount) не вполне точно: он определяет разницу значений индекса между индексом последнего снимка и индексом последнего применённого предложения.
- Raft требует, чтобы реплика восстановилась из снимка. Получая снимок по сети в сообщении msgSnap, реплика также создаёт его облегчённую контрольную точку в журнале WAL. Это гарантирует, что в хвосте журналов WAL всегда находится действительный снимок с последующими записями, и предотвращает возможный разрыв непрерывности журналов.
Сейчас файлы приблизительно3 соответствуют записям Snapshot журналов WAL в отношении 1-1. После вывода хранилища v2 из эксплуатации запись этих файлов должна полностью прекратиться (необязательно в 3.5.x, обязательно в 3.6.x).
Содержимое
Файл содержит сериализованный proto snapdb.snapshot
(uint32 crc, bytes data),
в поле ‘data’ которого находится Raftpb.Snapshot :
(bytes data, SnapshotMetadata{index, term, conf } metadata),
Наконец, вложенные данные содержат сериализованное в JSON содержимое хранилища v2 .
В частности, присутствуют:
- Term
- Index
- Данные о составе кластера:
/0/members/8e9e05c52164694d/attributes -> {"name":"default","clientURLs":["http://localhost:2379"]}/0/members/8e9e05c52164694d/RaftAttributes -> "{"peerURLs":["http://localhost:2380"]}"
- Версия хранилища: /0/version-> 3.5.0
Инструменты
protoc
Следующая команда позволяет просмотреть содержимое файла при выполнении из корневого каталога etcd:
Аналогично можно извлечь поле ‘data’ и декодировать его как ‘Raftpb.Snapshot
'
Пример сериализованного в JSON содержимого хранилища v2 в файлах *.snap etcd 3.4:
Изменения
Этот раздел предназначен для описания изменений форматов файлов между различными версиями etcd.