Redisのアブナイ話

そういえば最近引っかかったRedisのトラップ(?)について
復習も兼ねて、色々思い出しつつ、まとめておこうと思った。

細かいところは間違ってるかもしれないけど…

Redisのコードはこのあたりで読める。

Redisってなんなの?

オンメモリKVSの1つで、key - value形式でデータを保持するNoSQL DBの1つ。
メモリ上にDBを保持 & C言語のみで他のライブラリに依存しない実装なので、速い。
一定条件(設定可能)でディスクにDBを書き出すことが可能なので、
仮にRedis本体が落っこちても、ちょっと前の状態までなら復元することができる。

同じようなKVSにmemcachedというモノがあるが、こっちはディスク書き出しが無い。
ので、Redis的な使い方をするなら、従来のDBと組み合わせて実装するなどの
工夫が求められる。その分、ややこしくはなる。

仮想メモリ機能もVer2.0から導入されたんだけど、ポリシー的な問題か、
あるいは実装上の問題かは分からないけど、実装したこと自体が失敗だったという
結論に至ったらしく、Ver2.4で使うと警告が出るようになり、
Ver2.6では機能自体が廃止された。


日本語のドキュメントとか、ノウハウ的な文書は少なめ。
実はあまり使われていないんだろうか・・・?

ほかには?

期限付きのkeyを設定することができる。保持期間をExpireと呼ぶ。

キャッシュに使ってるけど、実データが時々更新される可能性があるので、
短時間だけ保持しておいて、切れたら取得しなおす、みたいなときに有用。

いくらRedisが速いとは言っても、常に全keyを監視してるわけじゃないので、
期限が切れてもすぐにメモリ上から消えてくれるわけじゃない。

「いつでも消える条件が整った」というだけで、実際には参照されるまで消えない。
逆に言うと、keysコマンドなどで故意に参照することで広範囲の期限切れkeyを
消すことができるが、そういった処理自体が重いので、乱用するのはやめた方が良い。


何がアブナイの? (バージョン編)

まず最新のStableなVerに追従しよう。

Redisは(他のDBと比べると)歴史が浅いので、速度改善やバグ修正の余地は
たくさんあって、毎度のリリースでそれなりに修正されている。
古いVerは爆弾を抱えていることもあるので、理由もなく使い続けるのはやめよう。

ログとかも、新しいVerの方が色々吐いてくれたりするので、
問題発生時の原因特定がしやすい。

古いVerの問題は、この辺に書かれてる。

かなりヘビーに酷使したりしないと起こらない問題とかもあるので、
ちゃんと動いてると思って使い続けていたら
ある日いきなり爆発、なんてこともあるかもしれない。

何がアブナイの?(設定編)

Redisは割とテキトーな設定でもそれなりに動いてくれたりするが、
ちゃんと意味を把握して使わなければ、あとで痛い目に遭うかもしれない。

maxmemoryについて

最大メモリ・・・というと、ここに設定した以上のメモリは、
Redisは消費しないってことかな。


…というのは間違いで、Evict(押し出し)を開始する閾値がこの値となっている。
Evictは既存の消せそうなkeyを消して、新しくキャッシュできる領域を確保する
Redisの処理のこと。どんなkeyをEvictするかは、おおよその条件を設定可能。

だから先に結論。Redisはmaxmemoryを超える量のメモリを「消費する」

Redisのメモリ消費量を厳密に制限することはできない
…という前提をまずは認識すべき。

まぁこういう設定名だと、できそうに見えちゃうんだけどね…。

後述するoutput bufferの問題とかも絡んでくるんだけど、
気付いたらmaxmemoryの2~3倍以上も実メモリを消費していた、ということも。

じゃあメモリを節約するには値を小さくすればいいのか?
どれだけEvictが発生し得るシステムなのか次第ではあるけれど、
状況によってはそれは有効だったり、あるいは逆効果であることも。

何事もバランスが重要なのです、きっと。

saveについて

「save 60 10000」などと書かれている(設定ファイルに)。
これは10000回のkeyの書き換えがあったら、60秒後にディスクに保存するよ、の意味。
クライアントから明示的に実行するには、BGSAVEというコマンドを送り付ければOK。

このsaveなんだけど、keyの数が非常に多くなった場合、意外と時間が掛かる。
そして、CPUリソースをけっこう消費する。

saveは別プロセスを立ち上げて、バックグラウンドで実行される。
同時に複数のsaveプロセスが立ち上がることはないけど、後述のoutput buffer問題などで
メモリが枯渇すると、プロセス作成が失敗することもある。

BGSAVEはRedisの特徴的な機能の1つであり、非常に重要な機能ではあるけど、
システムによっては、あまり重要ではないかもしれない。
思い切って、save間隔を長くしてみたりすると負荷の低減に有効かも。

client-output-buffer-limitについて

設定の中でもとりわけ注意しなければいけない項目
ただし、この項目はVer2.6以上で設定可能。


まずoutput-bufferってなんやねん、という話から。

output-bufferはRedisがメモリ上に確保するバッファの一種。
レプリケーション環境で運用してる際、slaveはmasterのコピーであるので、
keyの削除時には「このkey消しといてー」とslaveに通知する必要がある。
その情報はoutput bufferにため込んで、まとめて送信される。

あまりに削除するkeyが多かったり、masterが抱えるslaveがたくさんあったりすると、
output bufferの分だけ、メモリを大量に消費してしまう。

そうすると、通信がいつまで経っても終わらなくなって、しかし削除要求は増え続け、
slaveの更新ができないまま、メモリ消費量だけ肥大化する。

こうなると非常に危険で、色々な問題を誘発することがある。
空きメモリが無くなって、HTTPが死んだり、前述のBGSAVEプロセス作成に失敗して
スナップショットが長期間保存されない状況になったりする。


client-output-buffer-limitは所定の容量をbufferが超えたら、
クライアントとの通信を強制的に切断するという設定。
これはこれで問題があるけど、メモリが枯渇するよりはマシかもしれない。

masterから見たクライアントというのは、slaveのことなので、
仮にbufferのピーク容量が500MBで、slaveが4台繋がってたら、
最大で2GBぐらい消費することになる。
実際は詰まり始めたらどんどん消費してしまうので、「最大で」とか「最悪でも」なんて
想定をしても、あまり意味はないかもしれない。


ただ、output-bufferの危険性を認識しておくのは非常に大事。

なんかRedisのメモリ消費量がヤバいんですけどー、という状況に陥ったら
このあたりを見直してみるのが良さそう。

いろいろ書いたけど、要するに

Redisのバージョン上げよう

古いバージョンは爆弾を抱えているので危険!

Redisの邪魔はしないようにしよう

Redisの入ったサーバーで別の重い処理をさせたりするのは極力やめよう。
Redisの性能が落ちる原因になるよ。

1つのRedisに丸投げするのはやめよう

キャッシュの種類や役割に応じて、適切にインスタンスを分けよう。
1つのRedisに何もかも丸投げするのはやめよう。
ある日突然、死んでしまうかもしれません。

時には「おカネ」の力も大事だ

状況に応じてサーバー本体の増設も必要(かもしれない)。