事の発端
勤め先でCouchbaseを使っているのですが、社長より以下の条件で障害が発生したら古いデータがgetされるのでは?というアドバイスを受け試してみました。
アドバイスをまとめると
- CouchbaseのBucket TypeをMemcachedにして使っている(データのレプリケーションは無い)
- この状態で色々データをsetしまくる。
- ネットワーク障害等でキャッシュサーバーが一時応答不可になりCouchbaseクラスタから外れる(デーモンが落ちた訳ではないからサーバーのデータはFlushされない)
- CouchBaseクラスタから外れている間、同じキー名でまた色々データをsetしまくる。(データを更新する)
- ネットワークダウンで通信不可だったキャッシュサーバーのネットワークが戻り、Couchbaseクラスタに復帰
- Couchbaseクラスタに復帰したらサーバーの増減でハッシュの再計算が発生して、データ格納(担当)サーバーが変わるから古いデータがgetされるのでは?
ということでした。
最初はどういうことかな?と思ったのですが、試してみたらその通りだったのでテスト状況をまとめておきます。
検証
Couchbaseクラスタ稼働状態
サーバー名 | ステータス | 格納key&value |
---|---|---|
キャッシュサーバー1 | 稼働 | - |
キャッシュサーバー2 | 稼働 | - |
キャッシュサーバー3 | 稼働 | - |
キャッシュサーバー4 | 稼働 | - |
何もデータが入ってない状態ですね。
キーと値をset
まず、
キー(key) | key1 |
---|---|
値(value) | 12345 |
をtelnetを使ってサーバーに接続してsetしました。
Escape character is '^]'. set key1 0 3600 5 12345 STORED get key1 VALUE key1 0 5 12345 END
Couchbaseの管理画面で確認したところ、データの格納サーバーがキャッシュサーバー3でした。
キーと値の格納状況をまとめるとこんな感じです。
サーバー名 | ステータス | 格納key&value |
---|---|---|
キャッシュサーバー1 | 稼働 | - |
キャッシュサーバー2 | 稼働 | - |
キャッシュサーバー3 | 稼働 | key1=12345 |
キャッシュサーバー4 | 稼働 | - |
わざとネットワーク障害を発生させてみる
この状態でキャッシュサーバー3のネットワークインターフェースをわざとダウンさせてみます。
ifdown eth1
みたいな。
これでkey1の値は取れなくなりました。
Escape character is '^]'. get key1 END
key1のデータが入っているキャッシュサーバー3と通信が出来なくなったので当然の結果です。
この時点のCouchbaseクラスタの状態はこんな感じです。
サーバー名 | ステータス | 格納key&value |
---|---|---|
キャッシュサーバー1 | 稼働 | - |
キャッシュサーバー2 | 稼働 | - |
キャッシュサーバー3 | ネットワークダウン | key1=12345 |
キャッシュサーバー4 | 稼働 | - |
1台足りない状態で同じキーと違う値をset(上書き更新を想定)
キャッシュサーバー3がCouchbaseクラスタから外れた状態の3台で、
同じキーのkey1と今回は先程と違うデータをsetしてみます。
キー(key) | key1 |
---|---|
値(value) | abcde |
これまでkey1というキー名であった場合、キャッシュサーバー3にデータがsetされていましたが、
今回はCouchbaseクラスタを構成するサーバーの台数が減ったのでハッシュの再計算が行われ、格納サーバーがキャッシュサーバー2に変わりました。
この時点でget key1すると値「abcde」が返ります。
Escape character is '^]'. set key1 0 3600 5 abcde STORED get key1 VALUE key1 0 5 abcde END
これも当然です。
また表にまとめてみるとこんな感じです。
サーバー名 | ステータス | 格納key&value |
---|---|---|
キャッシュサーバー1 | 稼働 | - |
キャッシュサーバー2 | 稼働 | key1=abcde |
キャッシュサーバー3 | ネットワークダウン | key1=12345 |
キャッシュサーバー4 | 稼働 | - |
ネットワーク障害から復帰
で、この状態でネットワークダウンしていたキャッシュサーバー3のインターフェースをupさせCouchbaseクラスタに戻してみます。
サーバー名 | ステータス | 格納key&value | 備考 |
---|---|---|---|
キャッシュサーバー1 | 稼働 | - | - |
キャッシュサーバー2 | 稼働 | key1=abcde | 新しいデータ |
キャッシュサーバー3 | 稼働 | key1=12345 | 古いデータ |
キャッシュサーバー4 | 稼働 | - | - |
ここでget key1すると
後からsetした「abcde」ではなく、最初にsetしたキャッシュサーバー3に格納されている古い値「12345」が返ってきます。
Escape character is '^]'. get key1 VALUE key1 0 5 12345 END
結果
Couchbaseの格納(担当)サーバーの選出計算の結果
- サーバー台数が4台の場合、key1はキャッシュサーバー3に格納される。
- サーバー台数が3台の場合、key1はキャッシュサーバー2に格納される。
となる為、これをまたサーバー台数を4台に戻すと格納(担当)サーバーがキャッシュサーバー3に戻る為、古いデータがgetされるということのようです。
対策
これはこういうクラスタ環境であれば起きる問題だと思うのでCouchbaseに限った話しではなさそうですが、
対策としてはネットワーク障害を検知したら各BucketをFlushして回ればいいでしょうか。
いい方法がないか探ってみようと思います。
この問題については2012/1/27 に開催された『CouchConf Tokyo』で似たようなお話しが出ていたようです。
ネットワークの分断があった場合、データベースが分断される.どちらかのコピーが勝つ.(新しいコピー?).将来的なリリースではどちらのコピーを選択するか自由度を与えたい. #couchconf
— はやと (@Hayato_Kapibara) January 27, 2012
対策(追記)
先日開催された『couchbase-jp meetup #3』に参加させて頂き、
CouchbaseのソリューションアーキテクトのPerry Krugさんに聞いてみました。
通訳してくださった@ijokarumawakさん本当にありがとうございました。
この事象は当然ご存知でCouchbaseモードでバケットを使ってください。とアドバイスを頂きました。
で、Couchbaseモードのバケットだとネットワーク障害時の挙動はどうなるのか確認してみたところ、
setもgetも以下のようにエラーが返ってきました。
エラーを返すことでデータの整合性が保たれるようになっているんですね。
Trying 10.0.0.1... Connected to 10.0.0.1 Escape character is '^]'. get key1 SERVER_ERROR proxy write to downstream 10.0.0.3 set key1 0 3600 5 abcde SERVER_ERROR proxy write to downstream 10.0.0.3