第22章: 一貫性

データが複数のサーバーにレプリケートされると、根本的な疑問が生じます:読み取り側はどのようなデータの保証を得られるのか?一貫性とは、分散システムにおける書き込みとその後の読み取りの関係を支配するルールのセットです。

ストレージクォーラムレプリケーション

ストレージサービスは3レプリカ(N=3)として動作し、各レプリカが独自のWALとスナップショットを維持します。すべての値に単調増加するバージョン番号がタグ付けされます:

storage/src/engine.rsでバージョニングを参照してください。
struct VersionedValue {
    value: String,
    version: u64,
}

// put assigns the next version
pub fn put(&mut self, key: String, value: String) -> u64 {
    let version = self.next_version;
    self.next_version += 1;
    self.append_wal(&format!("VPUT {}={}@{}", key, value, version));
    self.data.insert(key, VersionedValue { value, version });
    version
}

クォーラム書き込みはクライアントに返す前にW個のackを必要とします。W=2、N=3の場合、書き込みノードはローカル書き込みを実行し(1つのackとしてカウント)、discovery::list("storage")を通じてピアにレプリケートし、追加の1つのピアackを待ちます。

クォーラム読み取りは対称的に機能します:R=2の場合、読み取りノードはローカルと1つのピアから読み取り、最新バージョンの値を返します。重要な不変条件はW + R > N(2 + 2 > 3)であり、これは任意の読み取りクォーラムが任意の書き込みクォーラムと重なることを保証します——読み取りセット内の少なくとも1つのノードが最新の書き込みを持っています。

バージョンゲーティング

storage/src/engine.rs レプリケーションメッセージが到着した場合、受信ノードはそれを受け入れるかどうかを決定する必要があります。put_versionedメソッドはlast-writer-winsを実装します:書き込みは、そのバージョンが現在の値のバージョン以上の場合にのみ受け入れられます。これにより、ネットワークで遅延した古いレプリケーションメッセージが新しいデータを上書きすることを防ぎます。

pub fn put_versioned(
    &mut self, key: String, value: String, version: u64,
) -> bool {
    if let Some(current) = self.data.get(&key) {
        if version < current.version {
            return false; // Reject stale write
        }
    }
    // Accept the write
    true
}