*レプリケーション

Redisレプリケーションの基盤(Redis Cluster あるいは Redis Sentinelによる追加の層として提供される高可用性の機能を除く)は、リーダー フォロワー (マスター-スレーブ) レプリケーションを使用し設定するのが非常に簡単です: それによりレプリカ Redis インスタンスは、マスター インスタンスの正確なコピーになることができます。レプリカはリンクが切断されるたびにマスターに自動的に再接続し、マスターに何が起こったかに関係なく、その正確なコピーになろうとするでしょう。

このシステムは以下の3つの主要な仕組みによって動作します:

  1. マスターとレプリカのインスタンスが適切に接続されている場合、マスターはクライアント側で下記によって起こるデータセットへの影響をリプリケートするために、レプリカにコマンドのストリームを送信することでレプリカを更新し続けます: クライアントの書き込み、キーの期限切れあるいは退去、マスターのデータセットを変更する全ての他のアクション。
  2. ネットワークの問題、あるいはマスターまたはレプリカで検知されたタイムアウトのために、マスターとレプリカの間のリンクが切断される場合、レプリカは再接続し、部分的な再同期を使って進めようとします: 切断の間に失われたコマンドのストリームの部分を単に取得しようとすることを意味します。
  3. 部分的な再同期が不可能な場合、レプリカは完全な再同期を要求するでしょう。マスターは全てのデータのスナップショットを作成し、それをレプリカに送信し、そしてデータセットの変更に応じてコマンドのストリームを送信し続けるという、より複雑なプロセスを意味します。

Redisはデフォルトで非同期リプリケーションを使います。これはレイテンシが低く、高パフォーマンスであるため、ほとんどのRedisのユースケースでうってつけのレプリケーションモードです。しかしRedisのレプリカは非同期でマスターと定期的に受信したデータ量を確認します。そのため、マスターはレプリカによってコマンドが処理されるのを毎回待つわけではありません。ですが、必要であればどのレプリカがどのコマンドを既に処理したかを知っています。これによりオプションの同期リプリケーションが可能になります。

特定のデータの同期リプリケーションはWAITコマンドを使ってクライアントによって要求することができます。しかし、WAIT は他のRedisインスタンス内に特定の数の応答されたコピーがあることを確認することしかできません。Redisインスタンスのセットを一貫性のあるCPシステムに変換することはしません: Redisの永続性の正確な設定によって、応答された書き込みは障害時の間はまだ失われたままかもしれません。しかし、WAIT を使うことで、障害後の書き込みの紛失の可能性は、障害モードを引き起こすのが難しいほど大幅に減少します。

高可用性とフェイルオーバーについての詳細はSentinelあるいはRedis Clusterのドキュメントを調べてください。この文章の残りの部分では主にRedisの基本的なレプリケーションの特性について説明します。

以下はRedisリプリケーションについてのとても重要な事実です:

  • Redisは非同期レプリケーションを使用し、非同期のレプリカからマスターへの処理済みデータの量の確認を行います。
  • マスターは複数のレプリカを持つことができます。
  • レプリカは他のレプリカから接続を受け付けることができます。同じマスターへ多数のレプリカを接続することとは別に、レプリカはカスケードのような構造で他のレプリカに接続することもできます。Redis 4.0から、全てのサブ レプリカはマスターから全く同じレプリケーションストリームを受信します。
  • Redisのレプリケーションはマスター側では非ブロッキングです。1つ以上のレプリカが初期の同期あるいは部分的な再同期を行う時にマスターがクエリをクエリを処理し続けることを意味します。
  • レプリカ側でもレプリケーションはほぼ非ブロッキングです。レプリカが初期の同期を行う間、redis.confでRedisをそうするように設定したとすると、古いバージョンのデータセットを使ってクエリを処理することができます。それ以外の場合は、レプリケーションのストリームがダウンした場合にRedisのレプリカがクライアントにエラーを返すように設定することができます。ただし、初期の同期の後で、古いデータセットが削除され新しいものがロードされるようにしなければなりません。レプリカはこの短いウィンドウの間にやってくる接続をブロックするでしょう (とても大きなデータセットについては最大で数秒かかるかもしれません)。Redis 4.0から、古いデータセットの削除が異なるスレッドで行われるようにRedisを設定することが可能ですが、新しい初期データセットはまだメイン スレッドで行われ、レプリカをブロックします。
  • レプリケーションは、読み込み専用クエリ用に複数のレプリカを持つため(たとえば、遅い O(N) 操作がレプリカに渡せます)のスケーラブルのためと、単純にデータの安全性および高可用性の改善のための両方のために使うことができます。
  • マスターが全データセットをディスクに書き込むためのコストを避けるためにリプリケーションを使うことができます: 一般的な方法は、ディスクに永続化しないようにマスターのredis.conf を設定し、時々保存するように設定、あるいはAOFが有効なレプリカに接続します。しかしこのセットアップは、マスターの再起動は空のデータセットで開始するため、注意して扱わなければなりません: もしレプリカがそれと同期しようとすると、レプリカは同様に空になるでしょう。

*マスターが永続性をオフにした時のレプリケーションの安全性

Redisのレプリケーションが使われているセットアップでは、マスターとレプリカ内で永続性を有効にすることを強くお勧めします。例えばとても低速なディスクによるレイテンシの問題のためにこれができない場合、再起動後に自動的に再起動しないようにインスタンスを設定する必要があります。

なぜ自動再起動を設定された永続化がオフになったマスターが危険なのかについてより良く理解するには、データがマスターおよび全てのレプリカから消去される以下の障害モードを調べてください。

  1. ノード A をマスターとして動作し、永続性を無効にし、ノード BとCがノード Aからリプリケートしているセットアップを持ちます。
  2. ノード A がクラッシュしたが、自動再起動システムがある場合、プロセスが再起動します。しかし、永続性がオフのため、ノードは空のデータセットで再開します。
  3. ノード BとC はノードAからリプリケートします。これは空のため、それらはデータのコピーを事実上破壊します。

高可用性のためにRedis Sentinelが使われている場合、プロセスの自動再起動とともにマスター上で永続性もオフにすることも危険です。例えばSentinelが障害を検知しないほど早くマスターは再起動できるため、上で説明した障害モードが発生します。

データの安全性が重要で、永続性無しに設定されたマスターでレプリケーションが使われる時はいつでも、インスタンスの自動再起動は無効にされるべきです。

*Redisのレプリケーションがどのように動作するか

全てのRedisサーバはレプリケーションIDを持ちます: データセットの特定のストリーをマークする大きな疑似ランダム文字列です。各マスターはデータセットの新しい更新でレプリカの状態を更新するために、レプリカに送信されるように生成されたレプリケーション ストリームの各バイトごとの増分のオフセットも取ります。レプリケーションのオフセットは、たとえ実際に接続しているレプリカが無いとしてもインクリメントされます。ですので、基本的に以下のペア:

Replication ID, offset

は、マスターのデータセットの正確なバージョンを識別します。

レプリカがマスターに接続する時に、古いマスターのレプリケーションIDとこれまで処理したオフセットを送信するために、PSYNC コマンドを使います。このようにして、マスターは単に必要な増分部分だけを送信することができます。しかし、もしマスターのバッファに十分なbacklogが無い場合、あるいはもしレプリカがもう知られていない履歴(レプリケーションID)を参照している場合は、完全な再同期が起こります: この場合、レプリカは最初からデータセットの完全なコピーを取得するでしょう。

以下は完全な同期がどのように動作するかの詳細です:

マスターはRDBファイルを生成するためにバックグランドの保存プロセスを開始します。同時にクライアントから受け取った全ての新しい書き込みコマンドをバッファし始めます。バックグラウンドの保存が完了すると、マスターはデータベースファイルをレプリカに送信します。レプリカはそれをディスクに保存し、メモリにロードします。マスターは全てのバッファされたコマンドをレプリカに送信するでしょう。これはコマンドのストリームとして行われ、Redisプロトコル自身と同じ形式です。

telnetを使って自分で試すことができます。サーバが何らかの作業をしている間に、Redisポートに接続し、SYNC コマンドを発行します。一括転送が行われ、マスターによって受信された各コマンドはtelnetセッション内で再発行されます。実際のところ SYNC は古いプロトコルで新しいRedisインスタンスではもう使われませんが、後方互換性のためにまだ存在しています: 部分再同期ができないため、今では代わりにPSYNC が使われます。

既に述べたようにマスター-レプリカ リンクが何らかの理由でダウンした場合は、レプリカは自動的に再接続することができます。もしマスターが複数の同時のレプリカ同期リクエストを受け取る場合、それら全てを処理するために単一のバックグラウンド保存が行われます。

*レプリケーションIDの説明

前の章で、もし2つのインスタンスが同じレプリケーションIDとレプリケーション オフセットを持つ場合、それらは全く同じデータを持つと述べました。しかし、レプリケーションIDが正確に何であるか、そしてなぜインスタンスが実際には2つのレプリケーションID、メインIDとセカンダリID、を持つのかを理解することは有用です。

レプリケーションIDは基本的にデータセットの特定の履歴をマークします。インスタンスがマスターとして最初から再起動する度、あるいはレプリカがマスターに昇格する度に、このインスタンスに対して新しいレプリケーションIDが生成されます。マスターに接続しているレプリカはハンドシェイクの後でそのレプリケーションIDを継承します。そのため同じIDを持つ2つのインスタンスは、異なる時点で可能性がありますが、同じデータを保持しているという事実によって関連付けられています。最新のデータセットを保持している特定の履歴(レプリケーションID)について、理解するための論理的な時間として機能するのはオフセットです。

例えば、もし2つのインスタンスAとBが同じレプリケーションIDを持つが、1つはオフセット 1000 でもう一つはオフセット 1023 の場合、最初のものはデータセットに適用される特定のコマンドが欠乏しています。また、ほんの少しのコマンドを適用することで、AはBと全く同じ状態に達することができることを意味します。

Redisインスタンスが2つのレプリケーションIDを持つ理由は、レプリカがマスターに昇格するためです。フェイルオーバーの後で、昇格したレプリカは過去のレプリケーションIDは以前のマスターのうちの1つのレプリケーションIDであるため、それをまだ記憶する必要があります。このように、他のレプリカが新しいマスターと同期する時、それらは古いマスターのレプリケーションIDを使って部分的な再同期を行おうとします。レプリカがマスターに昇格する時、このIDが切り替わった時にオフセットが何だったのかを記憶して2つ目のIDをメインのIDに設定するため、これは期待したように動作します。新しい履歴が開始するため、あとでそれは新しいランダムなレプリケーションIDを選択します。新しいレプリカの接続を処理する時、マスターはそれらのIDとオフセットを現在のIDとセカンダリIDの両方と照合します(安全のために所定のオフセットまで)。つまり、フェイルオーバーの後で新しく昇格したマスターに接続しているレプリカは完全な同期を行う必要が無いことを意味します。

フェイルオーバーの後でなぜマスターに昇格したレプリカがレプリケーションIDを変更する必要があるのか疑問に思う場合: 何らかのネットワーク分離のために古いマスターがまだマスターとして機能しているかもしれません: 同じレプリケーションIDを保持することは、任意の2つのランダムなインスタンスの同じIDと同じオフセットは同じデータセットを持つことを意味する事実に違反するかもしれません。

*ディスクレス レプリケーション

通常完全な再同期はRDBファイルをディスク上に作成し、レプリカにデータを供給するためにディスクから同じRDBをリロードする必要があります。

遅いディスクでは、これはマスターにとってこれは非常にストレスの掛かる操作です。Redis バージョン 2.8.18 はディスクレス レプリケーションをサポートする最初のバージョンです。このセットアップでは、子プロセスはディスクを中間ストレージとして使用せずに、wireを介して直接RDBをレプリカに送信します。

*設定

基本的なRedisレプリケーションを設定することは簡単です: 単純に以下の行をレプリカの設定ファイルに追加します:

replicaof 192.168.1.1 6379

もちろん、192.168.1.1 6379 をマスターのIPアドレス(あるいはホスト名)とポートに置き換える必要があります。別のやり方として、REPLICAOF コマンドを呼び出すと、マスターホストはレプリカとの同期を開始します。

部分再同期を行うためにマスターによってメモリ内に取られるリプリケーション バックログを調整するための幾つかのパラメータもあります。詳細はRedis配布物に同梱されている redis.conf の例を参照してください。

ディスクレス レプリケーションは repl-diskless-sync 設定パラメータを使って有効にすることができます。最初のレプリカの後で更にレプリカを待つために転送を開始するまでの遅延は、repl-diskless-sync-delay パラメータによって制御されます。詳細はRedis配布物の中の redis.conf の例を参照してください。

*読み取り専用レプリカ

Redis 2.6 からレプリカはデフォルトで有効にされる読み込み専用モードをサポートします。この挙動は reids.conf ファイル内の replica-read-only オプションによって制御され、CONFIG SETを使って実行時に有効または無効にすることができます。

読み込み専用レプリカは全ての書き込みコマンドを拒否するため、誤ってレプリカに書き込むことができません。DEBUG あるいは CONFIG のような管理コマンドがまだ有効なため、レプリカインスタンスをインターネットあるいはもっと一般的に信頼できないクライアントが存在するネットワークに公開することを目的としている事を意味しません。ただし、読み込み専用インスタンスのセキュリティはrename-command ディレクティブを使って redis.conf 内でコマンドを無効にすることで向上させることができます。

読み込み専用設定を戻して書き込み操作の対象となる可能性があるレプリカインスタンスを持つことが、なぜ可能なのかを疑問に思うかもしれません。レプリカとマスターが再同期されるかレプリカが再起動すると、これらの書き込みは破棄されますが、一時的なデータを書き込み可能なレプリカに格納する幾つかの正当な使用例があります。

例えば、遅い Set あるいは Sorted セット操作を計算しそれらをローカルのキーに格納することが、複数回参照される書き込み可能なレプリカのユースケースです。

ただし、バージョン 4.0 より前の書き込み可能なレプリカでは、有効期限が設定されているキーの有効期限が切れていなかった、ことに注意してください。このことは、もしキーに最大TTLを設定するEXPIREあるいは他のコマンドを使うと、キーがリークし、読み取りコマンドを使ってアクセスしている間はもう表示されなくなるかもしれませんが、キーの数には含まれ、メモリをまだ使うでしょう。つまり、一般的に書き込み可能なレプリカの(以前のバージョン 4.0)とTTLを持つキーとを混在させると問題が発生します。

Redis 4.0 RC3 以降のバージョンはこの問題を完全に解決し、書き込み可能なレプリカはマスターと同様にTTLでキーを追い出すことができます。ただし、DB番号が63を超えるキーは例外です (ただしデフォルトでRedisのインスタンスは16データベースのみを持ちます)。

また、Redis 4.0以降のレプリカの書き込みはローカルのみであり、インスタンスに接続されているサブレプリカには伝達されません。サブレプリカは代わりに最上位のマスターから中間レプリカに送信されたものと同一のレプリケーションストリームを常に受信します。例えば、以下のように設定します:

A ---> B ---> C

たとえ B が書き込み可能であっても、Cは B の書き込みを見ず、代わりにマスターインスタンスAと同一のデータセットを持ちます。

*マスタに認証するようにレプリカを設定

マスターがrequirepassを使ってパスワードを持つ場合、全ての同期操作内でパスワードを使うようにレプリカを設定することは簡単です。

実行中のインスタンスでこれを実行するには、以下のように redis-cli を使い入力します:

config set masterauth <password>

恒久的に設定するには、設定ファイルに以下を追加します:

masterauth <password>

*N個の接続レプリカのみ書き込みを許可

Redis 2.8以降、少なくともN個のレプリカが現在マスターに接続されている場合にのみ書き込みクエリを受け付けるように設定することができます。

ただし、Redisは非同期レプリケーションを使うため、レプリカが実際に特定の書き込みを受信したことを確認することはできません。そのためデータ喪失のウィンドウが常にあります。

以下はその機能の仕組みです:

  • Redisレプリカは毎秒毎にpingを行い、処理されたレプリケーション ストリームの量を確認します。
  • Redis マスターは各レプリカから最後に受け取ったpingの時間を記憶します。
  • ユーザは最大秒数を超えない遅れを持つレプリカの最少数を設定することができます。

もしM秒未満の遅延の少なくともN個のレプリカがあれば、書き込みは受け付けられます。

これは、特定の書き込みに対して一貫性が保証されていないが、少なくともデータの喪失の時間枠が特定の秒数に制限されている、ベスト エフォート型のデータ安全機構と考えることができます。一般的に制限のあるデータの喪失は制限のないものよりは優れています。

もし条件が満たされない場合、マスターは代わりにエラーで応答し、書き込みは受け付けられません。

この機能には2つの設定パラメータがあります:

  • min-replicas-to-write <number of replicas>
  • min-replicas-max-lag <number of seconds>

詳細はRedisソースの配布物に同梱されているredis.confファイルの例を確認してください。

*Redisのレプリケーションはキーの有効期限切れをどのように扱うか

Redisの有効期限により、キーの生存期間(TTL)は限定されます。そのような機能はインスタンスが時間をカウントする能力に依存しますが、Redisのレプリカは現在のところそのようなキーがLuaスクリプトを使って変更された場合でも、期限切れを持つキーで正しくレプリケートします。

そのような機能を実装するためにRedisはマスターとレプリカが同期クロックを持つという機能に頼ることができません。なぜならこれは解決することができず、競合状態とデータセットの発散に繋がるからです。ですので、Redisは有効期限が切れたキーのレプリケーションを動作させるために3つの主な技術を使います:

  1. レプリカはキーを期限切れにさせず、代わりにマスターがキーを期限切れにするのを待ちます。マスターがキーを期限切れにする(あるいはLRUのために追い出す)と、全てのレプリカに転送される DEL コマンドを合成します。
  2. ただしマスター手動の期限切れのために、マスターが時間内にDELコマンドを提供できなかったために、論理的には期限切れになっているキーをレプリカがメモリ内にまだ持っている場合があります。これに対処するために、レプリカは論理クロックを使って、(新しいコマンドがマスタから到着するので)データセットの整合性に違反しない読み取り操作のみのキーが存在しないことを報告しますこのようにして、レプリカは論理的に期限切れのキーがまだ存在していると報告することを避けます。実用的には、レプリカを使用してスケールするHTMLの断片のキャッシュは望ましい生存期間よりも既に古いアイテムを返すことを避けます。
  3. Luaスクリプトの実行中にはキーの期限切れは行われません。Luaスクリプトが実行されると、概念的にはマスター内の時間が凍結され、スクリプトが実行される間、常に特定のキーが存在するかどうかが決まります。これによりスクリプトの途中でキーが期限切れになることが避けられ、このことはデータセット内で同じ効果を持つことが保証される方法でレプリカに同じスクリプトを送信するために必要とされます。

レプリカがマスターに昇格すると、それは独立してキーを期限切れにさせ始め、古いマスターからの助けを必要としません。

*DockerとNAT内でのレプリケーションの設定

Docker、またはポート転送を使う他の種類のコンテナ、またはネットワークアドレス変換が使われた場合、特にレプリカのアドレスを見つけるためにマスターのINFO あるいは ROLE コマンドの出力が走査されるRedisあるいは他のシステムを使用する場合に、Redisのレプリケーションには更に注意が必要です。

問題は、ROLE コマンドと INFO 出力のレプリケーションセクションがマスタインスタンスに発行されると、レプリカが接続に使う IP を持っていると表示することです。これは NAT を使う環境では、レプリカインスタンス(クライアントがレプリカへの接続に使用する必要があるもの)の論理アドレスとは異なる場合があります。

同様に、レプリカは redis.conf に設定されたlistenポートでリスト化されますが、ポートが再マップされた場合の転送ポートと異なるかもしれません。

両方の問題を解決するために、Redis 3.2.2 からレプリカにマスターへ任意のIPとポートのペアを知らせるように強制することができます:使用する2つのディレクティブは以下の通りです:

replica-announce-ip 5.5.5.5
replica-announce-port 1234

そして最近のRedis配布物の redis.conf の例に文章化されています。

*INFO と ROLE コマンド

マスターとレプリカインスタンスの現在のレプリケーション パラメータについての多くの情報を提供する2つのRedisコマンドがあります。1つは INFO です。もしコマンドが replication 引数として INFO replication として呼び出された場合、レプリケーションに関係する情報のみが表示されます。もう1つのよりコンピュータ向けのコマンドは ROLE です。マスターとレプリカのレプリケーションオフセットを含むレプリケーション ステータスと、接続されているレプリカのリストなどを提供します。

*再起動およびフェイルオーバーの後の部分的な再同期

Redis 4.0 から、フェイルオーバー後にインスタンスがマスターに昇格した場合、そのインスタンスは古いマスターのレプリカとの部分的な再同期を行うことが可能です。そうるために、レプリカは古いレプリケーションIDと前のマスターのオフセットを記録し、接続しているレプリカが古いレプリケーションIDを要求したとしてもバックログの一部を提供することができます。

ただし、昇格されたレプリカの新しいレプリケーションIDはデータセットの異なる履歴を構成するため、異なるでしょう。例えば、マスターは使用可能な状態に戻りしばらくの間は書き込みを受け付け続けることができるため、昇格したレプリカで同じレプリケーションIDを使うと、レプリケーションIDとオフセットのペアが1つのデータセットだけを識別するという規則に違反します。

さらにゆっくりと電源が切られて再起動されると、レプリカはマスターと再同期するために必要な情報を RDB ファイルに格納することができます。これはアップグレードの時に便利です。これが必要な場合には、レプリカでsave & quit操作を行うためにSHUTDOWN コマンドを使うほうが良いです。

AOFファイルを介して再起動したレプリカを部分的に再同期することはできません。ですが、インスタンスをシャットダウンする前にRDB永続化に切り替えて再起動し、最後にAOFを再度有効化することができます。

TOP
inserted by FC2 system