本当は怖いMemcached

# はじめに
データアクセスの高速化、セッションの保持などに非常に重要なポジションを占めているMemcached
特徴をあげると、速い安い美味いで、AWS上のサービス化などされており、非常に扱いやすいプロダクトなのですが、Memcachedそのものが単一障害点とならないように冗長化を測った時に深刻な問題が発生する可能性があることをご存知でしょうか。
システムに心あたりがある方は今すぐ代替手段を検討しなければなりません。

# どうしてもMemcachedを使いたいという方はこちらへ
[それでもMemcachedを使いたいあなたへ](http://qiita.com/taruhachi/items/b4c938dce1646aeb7204)

# 前提条件
* そもそも冗長化をしなければ問題ないという運用はその時点で怖いのでNG
* cache機構という性質上、データが飛ぶのは問題ない(”正”となるデータを他から読み出すだけ)が、誤ったデータが読み出されるのをNGとする
* Memcachedを利用した時に利用ノードを決定するのはMemcachedのクライアントライブラリの責任ですので、MemcachedそのものやElastiCacheなどのサービスとは無関係です。
* 障害時にキャッシュのミスヒットが発生することでシステム全体の負荷が増えてしまうという問題に関しては今回は触れていません。（※これもレプリケーション機構が動作していれば防ぐことはできますが、、、。）

# Memcachedの何が問題か
Memcachedではデータのレプリケーションを行えません。
そのため、ノードを複数利用した運用においては以下のケースにおいてデータ不整合を発生してしまいます。
1. 特定のノードに対して一時的なネットワーク障害などが発生する
2. 上記ノードに対してはデータの書き込みができないので、他のノードの中から生きているノード（以下、代替ノード）を利用してサービスを継続しようとする
3. 初回はデータが存在しないので、他のRDBなど”正”となるデータソースからデータを取得して、代替ノードにキャッシュを書き込む
4. 更新系のリクエストにより、RDB等のデータと同時に代替ノード上のデータ更新（もしくは削除）を行う。
5. 障害ノードが復旧する。
6. 障害ノード上のデータは更新前の古いデータが残っているため、データ不整合となる。

# 上記障害の発生頻度および影響
高負荷時に各ノードへの接続が失敗することは頻発します。
これはノードが完全に沈黙していた場合だけではないので、何らかの死活監視システムにて切り替えを行う対処などでは拾いきれないことがあります。
この時は接続に失敗しただけで、元のノードのデータは削除されていません。
※イメージ的にはRDBの接続失敗が高負荷時には発生するので接続リトライ機構を入れるといった感じです。

また、更新前の古いデータがあった時にはそもそもRDB上の”正”のデータを参照しないのが普通なのでそれが古いデータであることは検知しにくいため、該当のリクエストに関しては深刻な障害を発生する可能性があります。
この障害はMemcachedにおいて設定したExpireや、新たなデータをセットするまで継続しますが、Memcached上のデータが正しいと仮定して構築されたシステムにおいては、その間違ったデータをRDBにコミットする元データとして扱ってしまったり、傷口をどんどん広げる可能性があります。

# Memcached代替システムの検討
代替システムとして、RedisとKyotoTycoonを挙げて検討しました。
Redisを挙げた理由は、AWSのサービスとして展開されているためもともとElastiCacheを利用している場合に心理的な敷居が低いと考えられたためです。

|  | 説明 | Memcached | Redis | Kyoto Tycoon | 
|---|:---|:--------:|:----:|:----------:| 
| AWSのサービス化がされている | ElastiCacheの機能として提供されているかどうか？ | ○ | ○ | × | 
| memcacheプロトコルをそのまま利用可能 | memcached前提のサービスを書き換え無しで導入可能かどうか。 | ○ | × | ○ | 
| データExpire設定 | データの自動削除機能を持つか | ○ | ○ | ○ | 
| データ永続化 | データをストレージに同期して保全できるか | × | ○ | ○ | 
| 冗長化方式 | どのような形で冗長化を実現するか？ | マルチマスタノンスレイブ <br>(※造語)| シングルマスタマルチスレイブ | マルチマスタ双方向レプリケーション | 
| 速度 | Set/Getの速度、ただしベンチマークの内容や、ストレージへの保存ポリシーによって大きく変わってくるのであくまでも参考値 | 高速 <br>(ノードの追加により書き込み、読み込み共にスケールする) | Memcachedとほぼ同等<br>(読み出しはスケールする) | 前者2つの半分程度？<br>(データ同期を行っていた場合は書き込みのスケールには限度があると考えられる。読み出しはある程度スケールしそう。) | 
| アトミック操作 | データのアトミック操作が可能かどうか | △<br>casを実装 | ○ | △<br>casを実装 | 
| 扱えるデータ | valueとして扱えるデータの種別<br>(Redisはindexをkeyとは別に作ることにより、value側のデータを利用したアクセスも可能。)| Stringのみ | String <br>List<br>Set<br>Sorted Set<br>Hash | Stringのみ | 
| 一言評価 | memcachedのサービスを冗長化したい場合においての評価 | ElastiCacheとして提供されており、一時キャッシュの共有にはお手軽だが、冗長化したい場合はかなり注意が必要なので強くはお勧めしない。 | 高機能なのでMySQLが苦手な一部集計機能などを担うには良いかもしれない。<br>しかし、MySQLの全機能の代替は非常に厳しそうなので、適材適所といったところか。<br>冗長化したい場合はRedis Sentinelを利用する必要があるが、復旧までの速度や運用しやすさなどは不明。 <br>また、クライアント側に全面的に書き直しが必要。| クライアント側の書き直しが不要なので、機能的にはMemcachedで十分だが、冗長化だけはしておきたい場合においてはこの中ではベストソリューションだと考えられる。 |

# 補足　冗長化の方式とそれぞれのメリット、デメリットについて
## マルチマスタノンスレイブ方式（memcached）

（クライアントライブラリが）格納したいデータのキー毎に、格納したいノードを決定する。
ただし、接続不能だった時には残りのノードから決定するという方式。

![n_master_0_slave.png](https://qiita-image-store.s3.amazonaws.com/332/38018/5d2bb2b0-0abe-e850-d634-a70b9e727d04.png)

### メリット
+ マスタノードの追加とともに読み書き速度が上がる
+ どれかのノードが生きていればとりあえずサービスは可能
+ レプリケーション遅延に関しては一切考慮する必要がない

### デメリット
+ 接続不能状態から元に戻ったタイミングで、古いデータにアクセスしてしまう。
　※前述の通りこれは重大な問題でかつ、アプリケーション側は根本的な対策を取ることができない。

## シングルマスタマルチスレイブ方式
MySQLのレプリケーションと同じく、書き込む対象のマスタノードは常にひとつで、複数のスレイブノードにレプリケーションを行う方式。

 ![1_master_n_slave.png](https://qiita-image-store.s3.amazonaws.com/332/38018/6c99b6df-baa9-64b8-6374-e0a714c5b7c5.png)

### メリット
* スレイブノードの追加とともに読み込み性能が上がる
* マスタノードの読み出し負荷を分散できる

### デメリット
* このままではマスタノードが単一障害点なので、スレイブノードのマスタ昇格機能が必要(Redisの場合はRedisSentinelでサポート)
* 高速とはいえ、スレイブノードのデータはレプリケーション遅延があるという前提で利用しなければならない。
* 書き込みはマスタ、読み込みはスレイブといった割り切りはNG
* 書き込み性能をスケールアップすることができない
* Memcachedの単純な代替としては不適格

## マルチマスタ双方向レプリケーション方式(KyotoTycoon)

マルチマスタノンスレイブ方式とほぼ同じであるが、マスタノード間でデータのレプリケーションを行なうことで対障害性をアップさせる方式

![n_master_repl.png](https://qiita-image-store.s3.amazonaws.com/332/38018/a28f1a82-9f7f-90f4-da90-e2cad395c44c.png)


### メリット
* クライアント側のシステムはMemcachedと完全に同一のロジックで実装可能
マスタノードが単一障害点とならない
* 通常時はスレイブノードを利用しないので、データの一貫性が担保される
* マスタノードの障害が発生してもデータの不整合が発生しにくい
ある程度まではスケールしやすい

### デメリット
* ノード数が同じ時はMemcached利用時よりパフォーマンスが落ちる
* マスタノード障害発生直前に書き込んだデータはデータ不整合の対象となる可能性がある。
* データ書き込みが増えた場合にデータ書き込みのオーバヘッドが大きくなる。

# 追記1
memcachedをソースからインストールするのであればKLabさんの開発されたrepcachedを利用するという方法もあるようです。
こちらはノードを2つまで設定可能ということですので、スケールアウト要件が少ない場合には有効な選択肢になると思います。
[repcachedの紹介pdf](https://www.nic.ad.jp/ja/materials/iw/2008/proceedings/F2/IW2008-F2-08.pdf)
* こちらの製品は過去に検討したことがありましたが、Memcachedのスレッドサポートを利用できないといった制限があったため案件導入を見送ったことがあります。

# 追記２
実は最初にこの問題に気がついた時が２年近く前で、その時に行っていた調査との再整理を兼ねた記事でしたので、新しい製品を見落としていました。
新しいものとしてはサイボウズ様のyrmcdsという製品もあるそうです。
[yrmcds 1.0.0 をリリースしました](http://developer.cybozu.co.jp/tech/?p=6527)
こちら、Memcached互換ですが、ホットバックアップのサーバにレプリケーションを行っておき、障害時には自動昇格するという機構らしいです。