Couchbase(Memcachedモード)でサーバーの増減で古いデータがgetされることがある話

事の発端

勤め先で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』で似たようなお話しが出ていたようです。



対策(追記)

先日開催された『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