ログは無限に伸びる
一言でいうと
合意された順序をすべて保管すると、再起動の時間とディスクが、ログの長さに比例して増えます。スナップショットは、 「ここまで適用した結果」を1枚で保存して、その前のログを捨てられるようにしてくれますが、その代償として、 復旧の手順とバックアップの意味が変わります。
なぜ必要なのか
合意アルゴリズムは、値を保存しません。コマンドの順序を保存します。その順序を最初から 再び適用すれば、常に同じ状態になるというのが、複製ステートマシンの前提で、 raft.github.ioは、これを「合意アルゴリズムは、サーバーのログに入っているコマンドについて合意するために 使われる」と説明しています(raft.github.io)。
問題は、そのログが決して減らないという点です。毎秒100件を受け付けるクラスターは、1日に 860万個のエントリを積み上げます。ディスクが埋まることより痛いのは、再起動の時間です。 ログを最初から再び適用しなければならないなら、ノード1つの再起動に、数日かかることがあります。 キャッチアップも同じです。新しいメンバーを入れたり、長く落ちていたノードを復活させたりするとき、リーダーが 送らなければならないエントリが数百万個あれば、その復旧は実質的に不可能です。
どう動くのか
解決策は単純です。ある地点まで適用した結果をまるごと保存して、その前のログを捨てます。 保存したものには、最後に含めた位置番号と、その位置の任期を一緒に書いておきます。その2つが なければ、その後のログをつなげられないからです。
버린다 남긴다
[1..20000 로그] → 스냅샷(last_index=20000, last_term=7) + [20001.. 로그]
このコードブロックの韓国語コメントは、順に、捨てるものと残すものを述べており、角括弧内の韓国語の語は、ログを意味します。
ここで、新しい問題が1つ生じます。リーダーがフォロワーにインデックス20001のエントリを送ろうとするのに、そのフォロワーが インデックス19000までしか持っていなければ、リーダーには、送るログがすでにありません。そのため、実際の実装は、ログの 代わりにスナップショット自体を送信する経路を別に用意します。遅れの程度が、ログで埋められる 範囲を超えたら、スナップショットをまるごと送り、その後からログでつなぎます。
etcdで、このつまみは--snapshot-countです。メモリに持っているraftのエントリがこの数に
達すると圧縮が起き、デフォルト値はv3.2で10,000から100,000に変わりました。キーの
過去のリビジョンを整理するのは、別のつまみである自動コンパクションで、
--auto-compaction-modeと--auto-compaction-retentionで決めます。デフラグ
(etcdctl defrag)はまた別のことで、ドキュメントは「稼働中のメンバーでデフラグを行うと、状態を
再構築する間、読み取りと書き込みがブロックされる」と警告しています
(etcdのメンテナンス)。
バックアップの意味も変わります。etcdのスナップショットの保存はetcdctl snapshot saveで、復旧は
etcdutl snapshot restoreです。重要なのは、復旧が元のクラスターを復活させるのでは
ないという点です。ドキュメントは「復旧は、スナップショットのメタデータの一部(メンバーIDとクラスターID)を
上書きし、そのメンバーは以前のアイデンティティを失う」と書き、そのため「スナップショットからクラスターを
開始するには、新しい論理的なクラスターを開始しなければならない」と述べています
(etcdの災害復旧)。
名前が似ている3つを区別しておくと、運用で混乱しません。ログ圧縮は、raftのログを 減らすことで、自動コンパクションは、キーの過去のリビジョンを減らすことで、デフラグは、そうして空けた 場所をディスクに返すことです。前の2つをどれだけ実行しても、ファイルサイズは減らず、 デフラグをどれだけ実行しても、リビジョンは減りません。
現場での姿
最もよくある事故は、バックアップを取っておきながら、復旧を一度も試したことがないことです。上の文が 意味するのは、復旧が「昨日の状態に戻すこと」ではなく、新しいクラスターを立てることだという ことです。メンバーIDが変わるので、既存のメンバーが混ざって入ってきてはならず、すべてのメンバーが同じ スナップショットで復旧しなければなりません。この手順を障害の当日に初めて読むのでは、すでに遅いのです。
2つ目は、スナップショットの時点と実際のデータとの間隔です。ドキュメントは、member/snap/dbファイルから
スナップショットを取ると、「まだ記録されていないが、walフォルダーに入っているデータを失うおそれがある」と
警告しています。そのため、バックアップはファイルをコピーする方式ではなく、snapshot saveで取ります。
3つ目は、圧縮とバックアップの周期の関係です。ログを頻繁に圧縮するほど、再起動は速くなりますが、 スナップショットを作ること自体がコストなので、そのたびにレスポンスが少し遅くなります。逆に、まれに 圧縮すると、普段は静かなのに、再起動のときにまとめて代償を払います。どちらが良いかは、 「どれくらい頻繁に再起動するか」で決まり、ドキュメントのデフォルト値では決まりません。
次のクイズで確認すること
スナップショットが何を捨てて何を残すのか、なぜ最後の位置番号と任期を一緒に書く必要があるのか、 ログ圧縮とリビジョンの自動コンパクションとデフラグが、互いに異なることだという点、そして復旧がなぜ 新しいクラスターを作ることなのかを確認します。