Designing Data-Intensive Applications - Part 2. Distributed Data
第2部: 分散データ
- 5章: レプリケーション
- 6章: パーティション/シャーディング
- 7章: トランザクション
- 8章: 分散システムの問題
- 9章: 一貫性と合意
データを分散したい理由
- スケーラビリティ
- 耐障害性/高可用性
- (地理的な)レイテンシ
高負荷に対応するためのスケーリング方法
- 共有メモリアーキテクチャ(スケールアップ)
- シェアードナッシングアーキテクチャ(スケールアウト)
この部はスケールアウトでデータを分散した場合に生じる問題についてのお話
- 複数のノードにデータを分散(レプリケーション、パーティショニング)
- トランザクション
- 分散システムの基本的な制約
Chapter 5. Replicaiton
リーダーベースレプリケーション
writeはリーダー、readは任意のレプリカ
アクティブ/パッシブ、マスタースレーブレプリケーションとも呼ばれる
リーダーベースレプリケーションの動作
- レプリカの1つからリーダーを指定。リーダーはクライアントから受け取ったリクエストをローカルストレージに書き込む
- リーダーはデータをそれ以外のフォロワーに送り、受け取ったフォロワーはローカルストレージに書き込む
- クライアントはリーダーかフォロワーにreadリクエストを送る。writeリクエストはリーダーのみ
同期、非同期
レプリケーションは同期的に行われるか、非同期的に行われるか
同期的に行われる場合、フォロワーのデータは常に最新になり一貫性が保証されるが、フォロワーの障害時に書き込みができなくなる。
順同期型(1つのフォロワーを同期型にして、残りを非同期型にする)がおすすめ
ノード障害
フォロワーがダウンした場合。ダウン前からの全てのデータ変更をリーダーにキャッチアップする
リーダーのときは複雑。リーダーがダウンした場合、あるフォロワーをリーダーに昇格させて、クライアントの書き込み先を新しいリーダーに変更するプロセスが必要
タイムアウトによる障害の確認 -> コントローラによるリーダー選出 -> 新しいリーダーのための設定変更
このフェイルオーバーには いくつか問題もある
- 非同期レプリケーションだとフォロワーに完全に同期されていない状態でそのフォロワーがリーダーになると、データがロストする
- 2つのノードが同時に自分がリーダーであると認識してしまう現象(スプリットブレイン)が起こり得る
- リーダーがダウンしたと判断するためのタイムアウトの設定が難しい。長過ぎればリカバリに時間がかかってしまい、短すぎれば誤ったファイルオーバーが発生してしまう
レプリケーションラグ
リーダーの書き込みとフォロワーへの反映のラグ
レプリケーションラグを解決するための一貫性モデル
- read-after-write一貫性
- モノトニックな読み取り
- 一貫性のあるプレフィックス読み取り
read-after-write一貫性
ユーザは自分自身が投入したデータが必ず反映されていることを保証する
ユーザ自身が変更した情報はリーダーからreadする、自身の最新の書き込みタイムスタンプを記録しておく、などの方法
モノトニックな読み取り
ある時点でのデータをユーザが一度見たら、それ以前の時点のデータを見ることがない
ユーザは常に読み取りを同じレプリカから行う、などの方法
一貫性のあるプレフィックス読み取り
ユーザは適切な順序で保持したデータを見ることができる
順序を保持したい情報は同じパーティションに書き込む、などの方法
マルチリーダーレプリケーション
writeは複数あるリーダーのどれか、readは任意のレプリカにリクエスト
マスターマスター、アクティブアクティブレプリケーションとも呼ばれる
マルチデータセンターで利用するときなどに検討する
最大の問題は、書き込みで衝突しうること
衝突の回避方法として、LWW(last write wins, 最後に書いた方を優先する), 値のマージ、衝突回避のためのアプリケーションコードを用意する、などがある
リーダーレスレプリケーション
writeもreadも複数ノードに並列リクエスト
w + r < n である限り最新のデータを取得できる(=クオラム)
n: レプリカ、w: 書き込み成功ノード、r: 読み込み成功ノード
Chapter 6. Partitioning
パーティショニングはスケーラビリティを向上させるため
ノードはパーティションのリーダーであると同時に他のパーティションのフォロワーになる
一部のパーティションが他のパーティションに比べて多くのデータを持っている状態をスキュー、不均等な高負荷が集中しているパーティションはホットスポットと呼ばれる
ホットスポットを防ぐ方法は、ノードに対してレコードをランダムに割り当てる
キーの範囲によるパーティショニング
連続的なキーの範囲を各パーティションに割り当てる。日付の範囲など
単純なタイムスタンプだと特定のパーティションに集中して、他のパーティションは遊んでいる状態になってしまう。これを回避するためには日付以外のキーを用いる
ハッシュパーティショニング
多くの分散データストアはキーに対するパーティションを決定する際に、ハッシュ関数を使う
ハッシュを使っても完全にホットスポットが発生しなくなるわけではない。例えばTwitterのIDのハッシュがキーになっている場合。こういった場合は、キーにランダムな数値を加えるなど、アプリケーション側での対応も必要。
ローカルインデックス
ドキュメントによってパーティショニングされたインデックス
セカンダリインデックスをプライマリキーおよび値と同じパーティションに保存する
書き込み時に更新するパーティションが1つで済むが、読み込みには全てのパーティションへクエリを送って結果を結合しなければならない(スキャッタ、ギャザー)
グローバルインデックス
語によってパーティショニングされたインデックス
セカンダリインデックスはインデックスが張られた値を使って独立にパーティショニングされる
書き込み時に複数のパーティションを更新する必要がある。読み取り時は単一のパーティションだけで処理できる
リクエストのルーティング
- クライアントが任意のノードに接続できる方法
- クライアントからルーティング層にリクエストし、ルーティング層がノードに転送する方法
- クライアントにノードとパーティションの割り当てを認識させる方法
ZooKeeperはルーティングのためのメタデータとなるノードとパーティションのマッピングを管理している