情報畑でつかまえてロゴ
本サイトは NTTテクノロスが旬の IT をキーワードに
IT 部門が今知っておきたい最新テクノロジーに関する情報をお届けするサイトです

KVSに無限の可能性を与える!? Redisコマンド拡張機能「Redis modules」の紹介

こんにちは!はじめまして! 中村です。 OSSが好きなバックエンドエンジニアです。 ところでみなさん、SQLは得意ですか? 私は苦手です。JOINが出てきたら焦ります。 そんな私が今回紹介するOSS製品はこちら「Redis」です。

はじめに

こんにちは!はじめまして!
中村です。 OSSが好きなバックエンドエンジニアです。

ところでみなさん、SQLは得意ですか?
私は苦手です。JOINが出てきたら焦ります。
そんな私が今回紹介するOSS製品はこちら「Redis」です。

RedisはインメモリKVSのOSS製品です。 いわゆるNoSQLと呼ばれるジャンルで私のようなSQL苦手エンジニアでもデータにシンプルにアクセスすることができます。 類似製品としてmemcachedが挙げられますが、RedisではKey-Valueとしてのシンプルな使い勝手の中にも数種類のValueタイプがあり、オブジェクトの格納や範囲検索などデータへのさまざまなアプローチができる点や永続化機能が備わっている点が特徴となっています。

そんなRedisはKVS製品の中でもダントツの人気を誇っており( DB-Engines 参照)、 Twitter や GitHub 等での利用や、 Amazon Web Services(AWS) や Microsoft Azure におけるキャッシュサービスとしての提供など多くの利用実績があります。 主な用途としてはデータベースの一次キャッシュやメッセージングのブローカーが挙げられますが、 高速にデータアクセスが可能なことからビッグデータやIoTの分野においても注目されています。
Redisの想定されるユースケースとして、データ分析・検索基盤では「 Redis - Logstash - Elasticsearch 」のような構成となり、Redisはブローカーとしての役割を持つと考えられます。 また、ストリーミングデータ処理基盤では「 Apache Kafka - Apache Storm - Redis」のような構成となり、Redisはデータストアとしての役割を持つと考えられます。

前置きが長くなりましたが、本記事では2017年7月にGA版がリリースされた最新バージョン「Redis4.0」の新機能の中でも私が一番気になった Redis Modules について紹介します。
※本記事ではRedisの基本的な使用方法については紹介しません。 使用方法やModules 以外の機能についてはRedisコミュニティページを参照してください

Redis Modulesとは?

Redis Modules は、Redisの機能拡張をコアとは分離したレイヤで実装することを可能にする仕組みです。 開発者は実装したモジュールをRedisにロードすることで機能拡張を実現できます。 また、Redisに対して新たなデータ型を追加することが可能となっています。

モジュールはC言語やC++などで実装します。 redismodule.hと呼ばれるヘッダファイルをインポートすることでRedis Modules で提供されている各種APIを利用することが可能になります。

モジュール作ってみよう

まずはシンプルなモジュール(Hello world)の作り方を見てみましょう。 サンプルコードを以下に示します。

#include "/opt/redis/src/redismodule.h" // ...(1)
#include <stdlib.h>

int Helloworld_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { // ...(2) if(RedisModule_Init(ctx, "helloworld", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) { // ...(3) return REDISMODULE_ERR; }
if (RedisModule_CreateCommand(ctx, "HELLO", Helloworld_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR) { // ...(4) return REDISMODULE_ERR; }
return REDISMODULE_OK; }
int Helloworld_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { // ...(5) RedisModule_ReplyWithSimpleString(ctx, "Hello redis world!"); // ...(6)
return REDISMODULE_OK; }

ポイント(1)~(6)を見ていきましょう。

(1)

#include "/opt/redis/src/redismodule.h"

ヘッダファイル(redismodule.h)を読み込みます。 これによりモジュール用の各種APIを呼び出すことが可能になります。

(2)

int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
...
}

RedisModule_OnLoad関数はモジュールのロード時に実行される関数です。 本関数内でモジュールの初期化処理やコマンド登録処理を実行します。
1つ目のパラメータctxはモジュールのコンテキストへのポインタです。Redis Modules APIで提供されているほとんどの関数で本コンテキスト情報を1つ目のパラメータとして利用します。
2つ目、3つ目のパラメータはモジュールのロード時に渡されるパラメータへのポインタとパラメータ数です。今回のサンプルではパラメータを利用していないので詳細は割愛します。

(3)

if(RedisModule_Init(ctx, "helloworld", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}

RedisModule_Init関数はモジュールの初期化を実行します。 本関数はRedisModule_OnLoad関数の最初で呼び出される必要があります。
1つ目のパラメータにはRedisModule_OnLoadのパラメータctxを渡します。
2つ目、 3つ目のパラメータには管理情報となるモジュール名とバージョン情報を指定します。
4つ目のパラメータについてはRedis Modules APIのバージョンを指定しますが、現時点ではREDISMODULE_APIVER_1のみが指定可能です。

(4)

if (RedisModule_CreateCommand(ctx, "HELLO", Helloworld_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}

RedisModule_CreateCommand関数はコマンドの登録を実行します。
2つ目のパラメータにはコマンド名を指定します。今回は"HELLO"としています。
3つ目のパラメータにはコマンド処理を実装している関数名を指定します。今回はHelloworld_RedisCommand関数で実装しています。
4つ目~7つ目のコマンド実行時のフラグ情報やパラメータの位置情報を指定します。さまざまなフラグを指定することができますが、今回のサンプルでは詳細は割愛します。

(5)

int Helloworld_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { ...(5)
...
}

Helloworld_RedisCommand関数はオリジナルの関数です。コマンド処理の実体であり、(4)のRedisModule_CreateCommand関数のパラメータで指定されます。
2つ目、3つ目のパラメータはコマンド実行時に渡されるパラメータへのポインタとパラメータ数です。今回のサンプルではパラメータを利用していないので詳細は割愛します。

(6)

RedisModule_ReplyWithSimpleString(ctx, "Hello redis world!");

今回のサンプルでは、コマンドを実行すると"Hello redis world!"と返すだけの実装としています。 クライアントに対して単純な文字列を返すためにRedisModule_ReplyWithSimpleString関数を使用しており、2つ目のパラメータに返却したい文字列を指定します。

モジュール実行してみよう

作成したモジュールを実際にRedisに登録してコマンドを実行してみましょう。 作成したファイルを含めて以下の構成を前提とします。

/opt/redis (Redisインストールディレクトリ)
├ src
└ redismodule.h
├ modules (独自に作成)
└ helloworld.c (前述のサンプルプログラム)

モジュールプログラムをコンパイルして共有ライブラリにした後、MODULE LOADコマンドを実行してモジュールを登録します。

$ cd /opt/redis
$ gcc --shared -fPIC modules/helloworld.c -o modules/helloworld.so -I modules
$ src/redis-cli MODULE LOAD /opt/redis/modules/helloworld.so

モジュールが登録されているかどうかはMODULE LISTコマンドで確認できます。

$ src/redis-cli MODULE LIST
1) 1) "name"
2) "helloworld"
3) "ver"
4) (integer) 1

(3)のRedisModule_Init関数で指定したとおり、モジュール名:helloworld 、モジュールバージョン:1 で登録されていることがわかりますね。

実際にコマンドを実行してみます。

$ src/redis-cli HELLO
Hello redis world!

(5)(6)のHelloworld_RedisCommand関数で実装した通り、"Hello redis world!"が返ってきました!

モジュールの削除はMODULE UNLOADコマンドを使用します。 パラメータとしてMODULE LISTで確認したモジュール名を指定します。

$ src/redis-cli MODULE UNLOAD helloworld

おわりに

本記事ではモジュールプログラムの基本的な実装方法やモジュールの登録・削除方法について紹介しました。 次回はより実践的なモジュールとして、Redisのデータスペースにアクセスする方法について紹介したいと思います。
Redis Modules に関する情報はRedis コミュニティのRedis Modules紹介ページも参照してみてください。

NTTテクノクロスでは、今回紹介したRedis以外にも多くのOSS製品に対する調査や検証を行っています。 OSS製品を導入したいけど使いこなせていない...、OSSに興味はあるけどどれを選べばいいかわからない...といった方に向けたサポートソリューションも提供していますので、 気になった方はぜひ問い合わせてください!

またNTTテクノクロスでは、Redisの想定されるユースケース内で紹介したElastic製品についてもサポートを提供しています。 Elastic社の製品を活かしたソリューションにも取り組んでいますので、こちらもぜひ問い合わせてください!

おまけ

ここまでに紹介した各種APIについて詳細情報を記載していますので実際に使用されるときに参考にしてください。

RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)

本関数は以下の3つのパラメータを持ちます。

パラメータ解説
RedisModuleCtx *ctx モジュールのコンテキストへのポインタです。Module APIの多くの関数で本ポインタを渡します。
RedisModuleString **argv モジュールロード時に渡されるパラメータへのポインタです。パラメータは複数保持することが可能なため配列として渡されます。パラメータは特別な型「RedisModuleString」として渡され、提供されるAPIによりパラメータ文字列を取得することが可能です。
int argc モジュールロード時に渡されるパラメータ数です。

RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver)

本関数は以下の4つのパラメータを指定して実行します。

パラメータ解説
RedisModuleCtx *ctx RedisModule_OnLoad関数のパラメータであるctxを指定します。
const char *name モジュール名を指定します。ここで指定した名前が後述するモジュール一覧で表示されます。サンプルでは"helloworld"としています。
int ver モジュールのバージョンを指定します。バージョンもモジュール一覧で表示されます。
int apiver 利用するRedis ModulesのAPIバージョンを指定します。現時点で定義されているAPIバージョンはREDISMODULE_APIVER_1のみです。

関数呼び出し時にAPIバージョンが誤っていたり、モジュール名が登録済みだったりと問題が発生した場合、本関数はREDISMODULE_ERRを返します。 REDISMODULE_ERRが返却された場合は、RedisModule_OnLoad関数でも即座にエラー(REDISMODULE_ERR)を返却するべきとされています。

RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep)

本関数は以下の7つのパラメータを指定して実行します。

パラメータ解説
RedisModuleCtx *ctx RedisModule_OnLoad関数のパラメータであるctxを指定します。
const char *name コマンド名を指定します。実際にコマンド実行時に指定する文字列となります。サンプルでは"HELLO"としています。
RedisModuleCmdFunc cmdfunc コマンド処理を実装している関数名を指定します。サンプルでは"Helloworld_RedisCommand"としており、実装内容については後述します。
const char *strflags コマンドフラグ(配列)を指定します。本記事では詳細は割愛しますが、サンプルではreadonly(コマンドはデータスペースを変更しない)を指定しています。
int firstkey コマンド実行時に渡されるパラメータのKEYの開始位置を指定します。コマンドがパラメータとしてKEYを必要とする場合は"1"、必要としない場合は"0"を指定します。サンプルでは"0"を指定しており、KEYを必要としないことを意味しています。
int lastkey コマンド実行時に渡されるパラメータのKEYの終了位置を指定します。コマンドがパラメータとしてKEYを1つ必要とする場合は"1"、2つ必要とする場合はパラメータリストの最後のKEYの位置、無制限のKEY数を受け取る場合は"-1"、必要としない場合は"0"を指定します。
int keystep コマンド実行時に渡されるパラメータのKEYの間隔を指定します。例として、MSETのようなKEYとVALUEを交互に指定するコマンドの場合は"2"、MGETのようなKEYのみを指定するコマンドの場合は"1"を指定します。

モジュール間でコマンド名の衝突する問題が発生した場合、本関数はREDISMODULE_ERRを返します。 REDISMODULE_ERRが返却された場合は、RedisModule_OnLoad関数でも即座にエラー(REDISMODULE_ERR)を返却するべきとされています。

MyCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)

本関数は以下の3つのパラメータを持つ必要があります。

パラメータ解説
RedisModuleCtx *ctx モジュールのコンテキストへのポインタです。
RedisModuleString **argv コマンド実行時に渡されるパラメータへのポインタです。パラメータは複数保持することが可能なため配列として渡されます。
int argc コマンド実行時に渡されるパラメータ数です。

RedisModule_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg)

本関数は以下の2つのパラメータを指定して実行します。

パラメータ解説
RedisModuleCtx *ctx Helloworld_RedisCommand関数のパラメータであるctxを指定します。
const char *msg 返信する文字列を指定します。

参考

※文中に記載した製品名などの固有名詞は、一般に該当する会社もしくは組織の商標または登録商標です。

連載シリーズ
テクノロジーコラム
著者プロフィール
中村 恭平
中村 恭平

NTTテクノクロス株式会社
クラウド&セキュリティ事業部
第二事業ユニット