monolithic kernel

Redis の Lua Scripting におけるスクリプトキャッシュの管理

Redis には、サーバサイドで Lua スクリプトを実行する Lua Scripting 機能がある。積極的に使いたい機能というわけではないが、複数のコマンド呼び出しからなる Lua スクリプトを書いた場合に、スクリプト全体で atomic になるので、凝った Redis の使い方をする場合には役に立つ。

ただ、使い方に若干癖がある。シンプルに実行するだけなら EVAL コマンドを使えばよいのだが、毎回スクリプトの文字列をパースすることになるので、パフォーマンスを考えるとよろしくない。

そこで登場するのが SCRIPT LOADEVALSHA である。SCRIPT LOAD でスクリプトをロードしておいて、そのとき返ってくる SHA1 ハッシュ値を EVALSHA コマンドに渡すことで、ロードしておいたスクリプトを EVAL よりも高速に実行できる。

パフォーマンス的には改善されるものの、実際これをやるのは意外と厄介。SCRIPT LOAD を呼び出して SHA1 ハッシュ値を控えておくというのはかなり面倒だ。ハッシュ値を管理するデータベースとか用意したくない。

というわけで、自分はこの方法は使っていない。会社の人がやっていてなるほどと思った方法があったので、それを使っている。この方法をさらに他の人に紹介したら感心されたので、ドキュメントやインターネット上の情報だけでこの方法にたどり着くのはそこまで簡単ではないのかなと思い、記事にしてみた次第。

その方法というのは、おもむろに EVALSHA を実行するというもの。何もない状態で EVALSHA に渡すハッシュ値をどうするのかと思うかもしれないが、実は単にスクリプト文字列の SHA1 ハッシュ値なので、手元で計算しても問題ない。つまり、先程述べたようなハッシュ値のデータベースを動的に構築する必要はない。

ただ、EVALSHA だけだと、SCRIPT LOAD されていないので当然失敗する。そこで、EVALSHA に失敗した場合にはスクリプト文字列を渡して EVAL する。EVAL は実行したスクリプトをキャッシュする仕様になっているので、一度 EVAL すれば SCRIPT LOAD されたのと同じ状態になり、次回以降の EVALSHA は成功するようになる。

この方法の利点は、起動時に SCRIPT LOAD みたいな特別な処理が必要なく、単に EVALSHA -> EVAL とフォールバックするコードを書いておけばよいので楽ということ。初回以外は EVALSHA だけで完結するので、性能上のペナルティもほぼない。

普通の Redis ではちょっと楽という程度だが、Redis Cluster を使う場合にはもう少し事情が変わってくる。というのも、Redis Cluster の場合、スクリプトのキャッシュは shard ごとに保持されている。SCRIPT LOAD による方法だと、shard ごとにロードしておく必要があるし、途中で shard が増えたら増えた shard に対してロードする対応も必要になる。EVALSHA -> EVAL の方法であればこのあたりも意識する必要がない。各 shard について初回の EVALSHA が失敗し、EVAL が呼ばれるだけである。

管理方法というより管理しない方法になってしまった感もあるが、まあ管理しなくて済むなら管理しないのがよいだろう。

参考