Redis 4.0 RC1 出たし、Redis モジュールを紹介してみる

## TL; DR (予想外に長くなってしまったので)

- Redis 4.0 がもう来年には来そうです！モジュール機能が使えるようになりますよ！
- というわけで、現状作られているモジュールを紹介してみます
    - 個人的に特に面白いな/便利そうだなと思ったのは以下です
        - [Redis graph](#redis-graph): グラフデータベース
        - [redablooms](#redablooms): ブルームフィルタ
        - [redis-cell](#redis-cell): レートリミット (Rust で書かれてる！)
        - [RedisEx](#redisex): Redis 便利コマンド詰め合わせ
        - [redis-module-shm](#redis-module-shm): 変態モジュール
- 年末年始は Redis モジュール作ろう！

## 本編

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">Redis 4.0 RC1 is out! My blog post about it is here: <a href="https://t.co/sv37Um6Pgb">https://t.co/sv37Um6Pgb</a></p>&mdash; Salvatore Sanfilippo (@antirez) <a href="https://twitter.com/antirez/status/804738666300116992">2016年12月2日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

つい先日、Redis 4.0 の RC1 がリリースされました。Redis 4.0 ではいろいろと入りそうですが、その中でも個人的に一番注目しているのはモジュール機能です。

Redis はデータベース製品の中でも内部的なデータ構造とインターフェースに多様性があって (とはいえ他のデータベース製品について特に詳しいわけではないですが...)、特定の問題に対して適したデータ構造で時間的にも空間的にも効率的に処理を行えるのが魅力です。特定の問題と言っても、標準で用意されているデータ構造は割と汎用性が高く幅は広いのですが、それでも時々物足りなくなることがあります。一応、Lua スクリプティング機能を駆使すれば色んなことが実現できますが、例えば複数のキーをまたいでより高度なデータ構造を構築するとかはあまりやりたくないです。ていうかメモリとかもうちょっと低レイヤなところを触らせろ、という話です。

で、モジュールなわけです。もうその可能性は無限大、ではないですが、モジュールをロードした Redis サーバプロセスの権限でできることならなんでもできます。(ほぼ)シングルスレッドの Redis コアに対してマルチスレッドでなんかコソコソやることもできますし、流行りのディープ・ネットを実装することも出来ます。そのような夢と希望に満ち溢れた機能なはずなのですが、あんまり盛り上がっていないような雰囲気を感じています。というわけで、この記事では現在作られている Redis モジュールを紹介することでモジュール開発を盛り上げられたらなと思っています。

ざっくり分類:

- [データ構造系](#data-structure)
- [検索系](#search)
- [セキュリティ系](#security)
- [圧縮系](#compression)
- [機械学習系](#machine-learning)
- [その他](#misc)

## <a name="data-structure">データ構造系</a>

### <a name="redis-graph">[Redis graph](https://github.com/swilly22/redis-module-graph)</a>

つい先日終了した、第一回 Redis モジュールハッカソン第三位になったモジュールで、グラフデータベースを Redis 上に構築するものです。グラフのノードは Redis のハッシュで、`GRAPH.ADDEDGE` コマンドでノード間のリレーションを指定してグラフを構築します。
その後、`GRAPH.QUERY` コマンドでノードの検索と属性の取得をすることができます。クエリの文法は Neo4j の Cypher をベースにしたものになっています。Cypher のほんの一部しか実装されてないそうですが、現状でも結構遊べそうです。

Redis の積み上げてきたものをうまく活用しつつ、その上で発展性のありそうな機能を提供していて個人的に結構好きなモジュールです。

### <a name="redablooms">[redablooms](https://github.com/RedisLabsModules/redablooms)</a>

[Deblooms](https://github.com/bitly/dablooms) というブルームフィルタライブラリを Redis で扱えるようにしたモジュールです。
基本的に `SBF.ADD` コマンドで要素を追加して、`SBF.CHECK` コマンドで判定を行います。
Redis に入っていたら手軽に使えて嬉しいですね。

### [ReDe](https://github.com/TamarLabs/ReDe)

データの「脱水」(dehydration) をするためのモジュールのようです。ここでいう脱水というのは開発者が提唱してる概念らしいですが、要はキューに入れられたデータを遅延させることのようです。
具体的には `REDE.PUSH` コマンドでデータを TTL 付きでキューにプッシュして、`REDE.POLL` コマンドで TTL を過ぎたデータが取得できます。
ただぱっと見、普通の双方向リストを使って実装されているようで、効率はあまり良くなさそうに思いました。。

### [redis-wavelettree](https://github.com/saidie/redis-wavelettree)

私が作ったウェーブレット木を実装したモジュールです。まだまだ開発半ばという感じで、メモリ効率も良くないです。
将来的には動的ウェーブレット木を実装したいと思っていますが、論文を読み込んでいる途中で完成にはまだしばらくかかりそうです。

### [RedisModuleTimeSeries](https://github.com/saginoam/RedisModuleTimeSeries)

Redis モジュールハッカソン第二位になった、時系列データを取り扱えるモジュールです。`TS.CREATE` コマンドでタイムビンを指定して時系列データを初期化し、`TS.INSERT` でデータを挿入して、`TS.GET` で特定の期間の統計量 (合計、平均、データ点数) を取得する感じです。
ただ、統計量の算出は当該期間のタイムビンを全件なめているので、データ数が多くなった時に結構辛くなりそうに思いました。事前にスキーマを指定しておけば、JSON を投げることもできるのが便利かもしれません。

## <a name="search">検索系</a>

### [reji](https://github.com/akobozev/reji)

このモジュールは Redis 内の JSON 文字列データに対して、RDBMS で言うところのインデックスのようなものを作成できるモジュールです。
現状では、JSON としてパースできる全ての文字列タイプの KEY に対してインデックスを作成します (将来的にプレフィックスなどで対象を限定できるようになるようです)。インデックスを `REJI.CREATE` コマンドで作成した後に、新しい JSON を格納しつつインデックスを更新するには `REJI.PUT` コマンドを使う必要があります。インデックスにはユニーク制約を課すことも可能で、違反した場合は `REJI.PUT` が失敗します。最終的に `REJI.GET` コマンドで作成したインデックスを使って効率的に JSON レコードを列挙することができます。

### [RediSearch](https://github.com/RedisLabsModules/RediSearch)

転置インデックスを用いた全文検索機能を提供するモジュールです。
`FT.CREATE` コマンドでインデックスを構築した後、`FT.ADD` コマンドでドキュメントを追加して、`FT.SEARCH` コマンドで検索をすることができます。フィールドの重み付けやフレーズ検索、[snowball](http://snowballstem.org/) を用いた語幹処理も行うことができます。

## <a name="security">セキュリティ系</a>

### <a name="redis-cell">[redis-cell](https://github.com/brandur/redis-cell)</a>

Redis モジュールハッカソンで優勝したモジュールです。このモジュールは Redis で実装されることの多い(らしい)、レートリミット機能を提供するものです。[Generic cell rate アルゴリズム](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm) と呼ばれるものを実装しているらしいです。
提供するコマンドは `CL.THROTTLE` だけで、例えば、「同一 IP からのリクエストを 60 秒に 30 回かつ同時リクエスト数を 15 回に制限する」みたいなことを実現できます。

また、このモジュールは Rust の FFI を使って書かれているのも興味深いです (そのうち Rust でのRedis モジュールの作り方などを記事にするかもしれません)。

### [Redis password module](https://github.com/RedisLabsModules/password)

パスワードを安全に格納するためのモジュールです。
`PASSWORD.SET` コマンドで、パスワード文字列に bcyprt をかけて文字列型として格納します。さらに、`PASSWORD.CHECK` コマンドで格納されたパスワードと一致するかをチェックすることができます。また、文字列の代わりにハッシュを用いる `PASSWORD.HSET` / `PASSWORD.HCHECK` もあります。
これらのコマンドは `MONITOR` されないようになっているので、その点も安心です。

### [pam_auth](https://github.com/RedisLabsModules/pam_auth)

既存の Redis の `AUTH` コマンドを用いた認証の代わりに PAM を使った認証を提供するモジュールです。
Redis モジュールの PoC として作られただけなので、現段階では実用にはならないですが、一定需要があるんじゃないかと思います。

## <a name="compression">圧縮系</a>

### [redis-zstd-module](https://github.com/chadnickbok/redis-zstd-module)

つい数ヶ月前に Facebook が OSS 化した [Zstandard](https://github.com/facebook/zstd) ライブラリを用いて、文字列を圧縮して格納するモジュールです。`SET`/`GET` に対応した、`zstd.SET`/`zstd.GET` コマンドが提供されています。また、辞書を用いた圧縮に対応した `zstd.DICTSET`/`zstd.DICTGET` も用意されています。
SET 系のコマンドでは、`BLPOP` などのようにクライアントをブロックしつつ圧縮処理を別スレッドで走らせて完了後にクライアントに返すようになっているので、大きいデータの圧縮時にも他のリクエストがブロックしないようになっています。

## <a name="machine-learning">機械学習系</a>

### [neural-redis](https://github.com/antirez/neural-redis)

一部でバズってたのでご存知の方も多いとは思いますが、一応紹介しておきます。Redis の生みの親である antirez 氏がモジュールのサンプルとして作った多層パーセプトロンです。コマンドは結構いろいろありますが、基本的なものは以下です。

- `NR.CREATE`: ネットワークを構築する
- `NR.OBSERVE`: 教師データを与える (訓練用とテスト用に勝手に振り分け)
- `NR.TRAIN`: 別スレッドで学習を行う。上記テストデータを使って過学習しそうになったら止まる
- `NR.RUN` / `NR.CLASS`: 未知入力に対する出力を得る。それぞれ回帰と識別用。学習中でも使える

Redis に格納するのはネットワークの重みや勾配情報、教師データあたりのようです。別スレッドで計算を進めておけるあたりが素敵ですね。

### [Redis-ML](https://github.com/redislabsmodules/redis-ml)

機械学習のモデルを提供する Redis モジュールです。現状では以下のモデルが利用可能なようです。

- ランダムフォレスト (識別と回帰)
- 線形回帰
- ロジスティック回帰
- (なぜか) 行列演算

今後に期待でしょうか。

## <a name="misc">その他</a>

### <a name="redisex">[RedisEx](https://github.com/RedisLabsModules/redex)</a>

既存の Redis のデータ型に対して、いろんな便利コマンドを提供してくれるモジュールです。例えば、

- `PKEYS`: 正規表現にマッチする KEY を列挙する
- `CHECKAND`: 文字列型に対するコマンドを等値条件付きにできる
- `LMPOP`: リストから複数個ポップする
- `MSISMEMBER`: ある要素が複数のセットのいずれかに含まれているかを判定する
- `ZUNIONTOP`: 複数の順序付きセットの和集合を取って、スコアの低い順から `k` 個取得する

などがあります。

### <a name="redis-module-shm">[redis-module-shm](https://github.com/edgarsi/redis-module-shm)</a>

Redis クライアントとサーバが同一ホストにある際に、共有メモリを使ってやり取りできるようにするかなりクレイジーなモジュール(?)です。ベンチマークでは UNIX ソケットよりもかなり速いです。
Redis モジュールはサーバーのコネクション周りの挙動をどうこうすることはできないため、このモジュールを使うためにはサーバ起動時に `read()` や `write()` を上書きしたオブジェクトファイルを `LD_PRELOAD` で差し込む荒業が必要です。。また、クライアント側は `hiredis` を fork した [hiredis-shm](https://github.com/edgarsi/hiredis-shm) を使う必要があります。
モジュールが提供するコマンドは `SHM.OPEN` で、これを呼び出した後はクライアントととのやり取りが共有メモリ経由になります。

モジュール機能追加されて喜んでたら、根本から全部ひっくり返すような荒業が出てきてウケました。

## [pyrecks](https://github.com/doronsadeh/pyrecks)

Redis サーバに入っている Python で Python コードを実行することの出来るモジュールです。
Python のコード片を文字列型として Redis に格納して、`PYLD.pyrun` コマンドで実行することができます。
格納したコード片から直に Redis を触ることはできませんが (もちろん Redis クライアントで繋ぐことは出来る)、返り値で次に実行するコード片のキー名を返すことで処理をチェーンさせることができます。

--------

以上。大した数がないと思って手当たり次第書いてみたら結構長くなってしまいました。

まだ正式リリースもされてないというのもあってか、モジュールの完成度は高くないものが多いです。とはいえ、便利そうなモジュールは既にいくつか出てきています。
まだ Redis モジュールを開発している人は少ないので、将来メジャーになるモジュールを書くなら今がチャンスだと思います！