etcd の Learner とは?

etcd v3.4 から利用可能になった Learner についてまとめた記事です. ゼットラボで開発･保守している etcd オペレータへの機能追加の際に必要になったため調査しました.

## ずばり Learner とは?

Learner というのは投票権を持たないノードのことです.

etcd の合意形成アルゴリズムとして採用されている Raft では Leader と呼ばれるノードを, ノードからの投票によって決定します. 投票権を持つノードは Follower と呼ばれ, Learner は Leader からのデータを十分キャッチアップできた段階で, Follower へ昇格することができます.

## Raft とは?

Raft というのは合意形成アルゴリズムの一つです. よく比較されるアルゴリズムとして Paxos というものがありますが, 耐障害性やパフォーマンスに関しては同等なのに対し, より理解しやすいというのが Raft の特徴です.

Raft では基本的に Leader がクライアントからのリクエストの処理などを含む全ての意思決定を行います. その代わり (Learner を除く) 全てのノードによって Leader を選出します. ここで Learder を選出するために最低限必要な得票数が quorum (定足数) と呼ばれるもので, クラスタのレプリカ数を N としたときに N/2+1 という整数が quorum に相当します.

そのためクラスタが正常に Leader を選出できるためには, 過半数のノードが生存し, ネットワーク的に孤立していない必要があります.

## 背景にある問題

クラスタのローリングアップデートを行うとき, 通常は新しいノードを追加して古いノードを削除するという流れをレプリカ数分だけ繰り返します.

しかしながら, Learner が登場する以前では, クラスタに対してノードは常に投票権のある Follower として追加されるため, 一般的には quorum の大きさを変えてしまいます. 次の表はレプリカ数が 3 のクラスタのローリングアップデートを行うときの, 新しいノードの追加と古いノードの削除を行う工程において quorum の大きさや耐障害性 (failure tolerance: 壊れても良いノードの数) がどのように遷移するかを示しています.

| # | タイミング | # of Followers | # of active Followers | size of quorum | failure tolerance | 備考 |
|---|---|---|---|---|---|---|
| 0 | 始状態 | 3 | 3 | 2 | 1 | |
| 1 | ノードの追加 | 4 | 3 | 3 | 0 | ノードの追加は etcd プロセスが起動する前に行わなければならない |
| 2 | etcd プロセスの起動 | 4 | 4 | 3 | 1 | |
| 3 | 古いノードの削除 | 3 | 3 | 2 | 1 | |

etcd クラスタでは新しい etcd プロセスをクラスタに参加させる前に, メンバー API を利用して新しいノードの追加を宣言しなくてはなりません. そのため表の #1 のように **quorum の大きさは大きくなるがアクティブなノードは増えない**という状況が発生してしまいます.

これに加えて, クラスタに追加されたばかりのノードはデータを持っていないので Leader からデータを受け取る必要があり, Learder のネットワーク負荷が上がりやすく, (heartbeat が失敗しやすいため) Leader の再選出が起こりやすいので通常時よりもクラスタは若干脆弱になります.

## Learner を導入することで得られる成果

上述した問題に対して, Learner を導入し, Leader からのデータをキャッチアップできた段階で Follower へ昇格させるという手順を踏んだ場合, quorum の大きさや耐障害性の遷移は次のようになります.

| # | タイミング | # of Followers | # of active Followers | # of Learners | size of quorum | failure tolerance | 備考 |
|---|---|---|---|---|---|---|---|
| 0 | 始状態 | 3 | 3 | 0 | 2 | 1 | |
| 1 | Learner ノードの追加 | 3 | 3 | 1 | 2 | 1 | quorum の大きさは変わらない |
| 2 | etcd プロセスの起動 | 3 | 3 | 1 | 2 | 1 | |
| 3 | Follower ノードへの昇格 | 4 | 4 | 0 | 3 | 1 | quorum は大きくなるが同時にアクティブな Follower が追加される |
| 4 | 古いノードの削除 | 3 | 3 | 0 | 2 | 1 | |

表の #1 では Learner を追加しても, 投票権を持たないため quorum の大きさに影響を与えていません. また, #3 では十分に Leader のデータをキャッチアップしたあとで Follower へ昇格するため, quorum が大きくなるとほぼ同時にアクティブな Follower が増えることにより耐障害性を保ったままになっています.

このように **Learner を導入することで, 新しいノードを追加するときの耐障害性の低下を避けることができるようになります**.

## etcd v3.4 と v3.5 での Learner に関する挙動の違い

v3.4 の機能と v3.5 で追加予定の機能を比較することでそれぞれの挙動の違いをまとめます. これをみると v3.5 では v3.4 のように**明示的な Learner の利用を行わなくても, 内部的には Learner が利用されており自動的に耐障害性に関する考慮が行われるようになる**ことが予想されます. 加えて v3.5 では Learner を standby ノードとして利用できるようになるようです.

### v3.4

- メンバー API でノードを追加するときに Learner として追加することを明示的に指定する必要があります.
- Learner を Follower へ昇格させるときにはメンバー API によるリクエストが必要です. また, Leader のデータを十分にキャッチアップできていない場合にはそのリクエストは失敗します.
- Learner はクライアントからのリクエストを受け付けません.

### v3.5

- メンバー API でノードを追加するときにはかならず Learner として追加されます.
- Learner は Leader のデータをキャッチアップできた段階で自動的に Folloewr へ昇格します.
- Learner を read-only の standby ノードとして利用可能になります.

## 参考文献

- [etcd docs | Learner](https://etcd.io/docs/v3.4.0/learning/design-learner/)
    - etcd の Learner のデザインやロードマップが書かれています.
- [In Search of an Understandable Consensus Algorithm (Extended Version) / Diego Ongaro and John Ousterhout / Stanford University](https://raft.github.io/raft.pdf)
    - いわゆる Raft の論文です. Learner という言葉は出てきませんが non-voting member として言及されています.