結論
InnoDB memcachedプラグインにて、存在しないキーに対するdelete()メソッドを利用すると未COMMITのデータが失われてしまうことがある。
前提条件
パフォーマンスチューニングとして、daemon_memcached_r_batch_size および daemon_memcached_w_batch_size の値を両方共増やした状態であるとする。
https://dev.mysql.com/doc/refman/5.6/ja/innodb-memcached-tuning.html
この時、システムがクラッシュすると未COMMITのデータが失われるのは当然の話なので許容範囲としますが、クラッシュしなくても未コミットのデータが失われるケースがある。
検証コード
以下、検証はRDS for MySQLにおける結果です。
検証コード1:正しい動作
<?php
define ("RDS_HOST_NAME", "******");
$memcache_obj = new Memcache;
$memcache_obj->addServer(RDS_HOST_NAME, 11211);
$memcache_obj->set("key1","value1", 0 ,10);
$memcache_obj->set("key2","value2", 0 ,10);
$memcache_obj->delete("key2"); // set()したキーを削除する
echo "rtn = ". $memcache_obj->get("key1")."\n";
→ rtn = value1 が出力される
検証コード2:set()したばかりのkey1の値が取得できない
<?php
define ("RDS_HOST_NAME", "******");
$memcache_obj = new Memcache;
$memcache_obj->addServer(RDS_HOST_NAME, 11211);
$memcache_obj->set("key1","value1", 0 ,10);
// $memcache_obj->set("key2","value2", 0 ,10);
$memcache_obj->delete("key2"); // set()していないキーを削除する
echo "rtn = ". $memcache_obj->get("key1")."\n";
→ rtn = が出力される
対策
以下のいずれかの対策が必要
- daemon_memcached_r_batch_size および daemon_memcached_w_batch_size の値を1にする
- delete()を行わずに、削除済みを表現するデータを1秒間だけset()する。
※2. のケースでnullを入れる場合にはnullを明示的にキャッシュするパターンとの検証は必要です。
[おまけ]InnoDB memcached プラグインをセッションストレージとして利用したい理由
一言で言うと、お手軽にそれなりに信頼性のあるセッションストレージを準備出来るため。
※通常のMemcachedをセッションストレージとして利用したい場合には、冗長化対応が難しい。
参考:http://qiita.com/taruhachi/items/a844bf373623991873ff
メリット
- MultiAZ環境であればノード障害時に自動フェイルオーバーが実行される
- スケールアップ、スケールイン時にキャッシュが揮発しない
- 能力を超えたスパイク時にもレイテンシが低下するだけで、深刻な障害に直結しにくい
- memcachedプロトコルを利用可能なので開発環境を整えやすい
デメリット
- スケール変更時に若干のダウンタイムが発生する
- 自動でのスケール変更ができない
- memcachedと比較した場合にはコストパフォーマンスが悪い
他のセッションストレージとの比較
ElastiCache Memcached | ElastiCache Redis | DynamoDB | RDS for MySQL memcached plugin | |
---|---|---|---|---|
ノード障害時の挙動 | 該当キャッシュが揮発する 該当のキャッシュの汚染が発生する |
自動フェイルオーバー実行 | 自動フェイルオーバー実行 | 自動フェイルオーバー実行 |
性能上限 | 比較的高速 | 比較的高速 | 比較的高速 | スケールアップの上限あり |
オートスケール | 出来ない | 出来ない | 出来る | 出来ない |
スケール変更時のデータ担保 | "該当キャッシュが揮発する 該当のキャッシュの汚染が発生する |
キャッシュが全て揮発する | 影響が発生しない | 影響が発生しない |
スケール変更時の挙動 | そもそもデータが担保されない | そもそもデータが担保されない | ダウンタイムが発生しない | ダウンタイムが発生する(数十秒) |
メンテナンスウインドウ | あり(キャッシュが揮発する) | あり(キャッシュが揮発しない) | あり(キャッシュが揮発しない) | あり(キャッシュが揮発しない) |
スパイク時の挙動 | レイテンシが低下する | レイテンシが低下する | キャパシティユニットの追加が完了するまでの間、スロットルされた以上のリクエストに対してエラーが発生する | レイテンシが低下する |
特記事項(メリット) | memcachedプロトコルを利用可能 | 使用頻度が低い時に低コスト | memcachedプロトコルを利用可能 | |
特記事項(デメリット) | 高負荷時にはノード間のデータ汚染はかなりの頻度で発生する。 | スケール変更ができないので、高コストになりがちである。 | ・独自プロトコルであるため変更しにくい ・使用頻度が高い時に高コストである ・一度パーティション分割が進んでしまうと、その後恒常的にスロットルが発生し始めることがある。 |
del()利用時にロールバックされる。 同時接続数を1024より増やせない。 永続的接続時に接続の切断が発生する。 |
デメリットとしてコストパフォーマンスの悪さがあるが、スケール変更や障害時におけるデータの担保のしやすさなどからのメリットも多そうである。
追記
アプリケーションを構築し、負荷試験を実施したところ、2点問題が発覚しました。
問題1
永続的接続を利用した際に、サーバからの接続の切断が頻繁に発生する。(そもそも永続化されていない可能性あり??)
問題2
順次負荷を増やしていったところ、
MemcachePool::set(): Server [HOSTNAME] (tcp 11211, udp 0) failed with: Connection refused (111)"
というエラーが頻発するようになりました。
接続周りのエラーであることから、
MAX_SIMULTANEOUS_CONNECTIONS
を変更しようとしたのですが、RDSではこの値の範囲は10-1024の範囲でしか設定できないようです。