RedisのLuaスクリプティング機能について

the Lua interpreter built into Redis
the Lua interpreter built into Redis

僕のRedisについての知識はv2.2くらいで止まっていたので、それ以降のRedisに備わった機能を調べているんですけど、その中でもv2.6からサポートされたLuaスクリプト実行環境について今回は整理します。

技術Wikiの方にもRedisについてのメモを残しています。
* Redis – Tech Note

環境

CentOS 5.8 (x86_64)
Redis 2.6.10 (malloc=jemalloc-3.2.0 bits=64)
Pythonクライアント (redis-py 2.7.2)

※ Redis 2.6で利用できるLuaのバージョンは5.1です。

Lua言語について

Lua – Wikipediaによると、

Lua は、C言語のホストプログラムに組み込まれることを目的に設計されており、高速な動作と、高い移植性、組み込みの容易さが特徴である。いったんバイトコードにコンパイルされ、Lua VM で実行される。LuaJIT は The Computer Language Benchmarks Game によると、変数に型のないスクリプト言語では最速の言語・処理系である。

LuaJITの速さは有名ですね。ただ、Web開発の現場だとLuaを使う人は少ないかもしれません。ちなみにApache2.4からは mod_lua が使えますけど、これも遊びでならともかく実務で活用している人はそんなに多くはないと思います。僕の周りでは独自プロトコルを使っている社内システム用にWireshark(パケットキャプチャツール)のプラグインを書くときなどに使っている人はたまにいますが、Luaの主戦場はやはりゲーム業界のようです。あと、TAS(Tool Assisted Speedrun)制作をする人はLuaスクリプトが書けるといろいろ捗るらしいですね、ゲームは好きなのでちょっと興味あります。

Luaパッケージリポジトリ LuaRocks

LuaRocksはLuaモジュールパッケージのアーカイブ、CPANやRubyGemsみたいなものです。今回はRedisサーバ上でLuaスクリプトを実行するのですが、パッケージ管理ツールを含めたLua開発環境をローカルに整えておくと自作スクリプトの動作確認をする時などに便利なのでぜひ入れておきましょう。

CPANやRubyGemsほどではないですが、データベースドライバやHTTPクライアントなどいろんなモジュールが揃っているようなので、組み込み用途だけではなく簡単なバッチ処理などでもLuaは使えるのではないでしょうか。

では、Redis公式サイトのEVAL – Redisを読みながら整理を進めていきます。

Hello, World

EVALコマンドは引数に渡した文字列ををLuaスクリプトとして実行(評価)します。numkeys には操作するkeyの数を指定します。keyの操作が必要ないスクリプトの場合は0を指定すればOKです。

スクリプトファイルを読み込んで実行

redis-cli コマンドの –eval オプションを使えば外部スクリプトファイルを読み込んで実行できます。

または、以下のようにシェルでファイルの中身を展開して渡す方法もあります。

LuaからRedisのコマンドを呼ぶ

Luaスクリプト内でRedisの各コマンドを呼ぶことができます。Luaから使えるAPIは以下の2つ。

 * redis.call() (エラー発生時は処理を終了)
 * redis.pcall() (エラー発生時はエラーを捕捉して処理を継続)

この2つのAPIはエラー時の挙動が異なるという違いがあり、実務で使うには違いを正しく知っておかなければならないと思いますが、ここでは2つのAPIを使い分けることはせず redis.call() の方を使うことにします。
参考: Lua 5.1 リファレンスマニュアル 2.7 – エラー処理

まずは簡単な例から。

ここで新しくKEYS配列(Luaでは配列もテーブルで表現されますが、ここでは便宜上”配列”と表記)が登場しました。これは名前の通り操作対象となるkeyが格納されている配列になります(Luaでは配列の添字は1から始まるので注意)。上記例では’foo’というkeyに対してINCRコマンドを呼んで値をインクリメントしています(存在しないkeyに対してINCRコマンドを実行するとRedisはintegerの1を返却する)。

また、更新する値を指定する場合はKEYSと併せてARGV配列も使います。このARGV配列には操作するkeyに対する値(value)が格納されます。

なお、redis-cli コマンドの –eval オプションは操作するkeyの数を指定できないためARGV配列は利用できません。

エラー処理

エラー処理用のヘルパー関数が提供されているので、これをLuaスクリプト内で利用することができます。

 * redis.error_reply(message)
 * {err=message} (errフィールドを持ったテーブルオブジェクト)

上のAPIは内部的にerrフィールドを持ったテーブルオブジェクトを生成して返すラッパー関数になっていますので、どちらを使っても挙動に違いはありません。

ログ処理

ロギング用の関数も提供されているので、こちらもLuaスクリプト内で利用できます。

 * redis.log(loglevel, message)
  loglevel: LOG_DEBUG, LOG_VERBOSE, LOG_NOTICE, LOG_WARNING

Redisのログファイルに出力されているか確認。

Luaモジュールの利用

Redis v2.6.10に組み込まれているLua処理系では以下のモジュールが利用できます。

 * base
 * table
 * string
 * math
 * debug
 * cjson (3rd party)
 * cmsgpack (3rd party)

ここではいくつかのモジュールを紹介します。

table (テーブルモジュール)

tableモジュールはLuaの標準モジュールに含まれており、テーブル操作の為のユーティリティを提供しています。

返される文字列は “5 > 4 > 3 > 2 > 1” となります。

cjson (JSONモジュール)

cjsonモジュールを使えばJSONデータのエンコード/デコードが可能です。せっかくRedisを使っているのだからJSONのような構造化データは使わず、素直にRedisのListやHash型を活用した設計を心掛けた方が良いと思いますが、JSONデータをやりとりするWeb APIとの連携が必要な際などに有用なのではないでしょうか。

cmsgpack (MessagePackモジュール)

cmsgpackモジュールを使えばMessagePack形式のデータを扱うことができます。

それはそうとMessagePackはIETF絡みに巻き込まれていろいろざわついているようですね。これからどうなるのか気になります。

簡単にですがいくつかのモジュールを紹介しました。今後はcjsonやcmsgpack以外のサードパーティーモジュールも組み込まれるのでしょうか。

RedisへのLuaスクリプトの登録

作成したLuaスクリプトをRedisサーバに登録する機能が提供されています。あらかじめサーバ側に登録しておけばスクリプトの転送コストを削減することができます。MySQLなどのRDBMSでいうところのStored Procedure/Functionのような機能ですね。

登録したLuaスクリプトの管理はどうするんだろうと悩んでしまいますが、MySQLでいうところの mysql.proc みたいなメタテーブルを別途用意して運用するんでしょうか。それはなんか事故りそう。。

SHA1ダイジェストを計算するLua API

公式ドキュメントには書かれていませんが、SHA1ダイジェストを計算するLua APIも公開されているので(実装はscripting.cの650行目あたり、fritzyさんのパッチ)、SHA1の計算をサーバ側に任せることができるので便利に利用できそうです。

* redis.sha1hex()

アプリケーションからの利用

実際にはコマンドラインからではなくプログラムから使うことが多いと思うので、ここではPythonクライアント(redis-py)からLuaスクリプトの実行を試してみます。

* 実行結果

特に問題はなさそうです。他言語のクライアントライブラリもEVALコマンド対応済みのものが多いので確認してみてください。

注意点

RedisのLuaスクリプト実行環境を利用する上での注意点をいくつか挙げておきます。

グローバル変数は作れない

RedisのLuaインタプリタではグローバル変数の作成は許可されていません。

Redis-Lua間での型変換

RedisとLuaの間でデータをやりとりする際の型変換には注意する必要があります。特に数値の扱いは正しく理解しておかないと罠にハマってしまいます。

ということで、Redis-Lua間で浮動小数点値をやりとりしたい時は文字列データとして運用しましょう。

テーブル内のnil

Luaのテーブルに nil が入ってしまわないように注意してください。テーブル {1, 2, nil, 4} はRedisでは {1, 2} と解釈されてしまいます。忘れがちなので注意。

スクリプト実行のアトミック性

Redis上で動作させるLuaスクリプトはアトミックに実行されるため、keyに対する操作を安全に行うことができます。ただし、アトミックということは1つのスクリプトの実行時間が長いと他のクライアントのスクリプト実行がブロックされてしまうので注意しておく必要があります。

Redis Luaスクリプトアーカイブ – EVALSHA

EVALSHA

In Redis, each Lua script is identified by taking the SHA1 of its body. This is a public place to discover, share and discuss scripts. Think of it like luarocks, cpan, rubygems, or npm but for Redis scripts.

Redis用Luaスクリプトのアーカイブサイトです。非公式で登録されているスクリプトの数もまだ少ないですが、誰でも簡単に自作Luaスクリプトを登録できます。僕もcjsonモジュールを使った簡単なスクリプトを登録してみました。
* JSON Decode – EVALSHA

感想とか

以上、RedisのLuaスクリプティング機能について簡単に整理しました。Lua言語自体の学習コストは低いですし、Redis-Lua間での型変換についてもルールを正しく知っていれば問題にはならないと思います。頭を悩ませそうなのは、やはりサーバ側に登録したLuaスクリプトの管理でしょうか。また、実務でRedisを活用しているところだと僕の周りではv2.2系のシェアが一番多いのですが、大規模運用している現場だとv2.6系への移行コストがどのくらいなのか気になります。

今回の検証でLua言語自体に強い興味を持ちましたので、C言語との連携等も含めいろいろ試していきたいと思います。

* 参考
EVAL – Redis
Lua: A Guide for Redis Users
Redis reliable queues with Lua scripting

あわせて読む:

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です