前回の(1)はこちらから。
Redisのデータ型
次に、Redisの一番の特長であるデータ型について詳しく見ていきましょう。
文字列型
文字列型はRedisで扱うことのできる最もシンプルなデータ型です。バイナリセーフですので、画像を保存する、MessagePackでシリアライズしたデータを保存するといった用途にも使えます。
ただし、日本語の扱いには注意が必要です。日本語を扱うようなPerlスクリプトを書いている場合、スクリプト内にuse utf8
プラグマを宣言していると思います。そのような場合、日本語の文字列をそのまま保存しようとすると文字化けしてしまいます。次のコード例のように、文字列のエンコードとデコードを忘れないようにしてください。
リスト型
リスト型は複数の値を順番に並べて、インデックス番号でアクセスできるようにした型です。Perlにおける配列と似たようなデータ型ですが、Redisのリスト型は「連結リスト」というデータ構造で実装されていることに注意してください。先頭や末尾への追加や削除といった操作は高速に行えますが、その代わりにインデックス番号でのアクセスは少し苦手です。
リスト型のジョブキューとしての応用
筆者の携わったサービスでは、リストの先頭や末尾への操作が高速である特長を活かして、リスト型を簡易的なジョブキューとして用いていました。
ジョブキューとして利用する場合、ブロックするコマンドがあるというのもRedisの利点です。たとえば、BRPOP
コマンドはRPOP
コマンドと同様にリストの末尾から値を1つ取り出すコマンドですが、取り出せる値がないときには、リストに値が追加されるのを待ち受けてブロックします。BRPOP
コマンドを用いることで、ジョブキューにジョブが投入されるのを待つ処理を簡単に書くことができます。
BRPOP
コマンドの簡単なサンプルを用意しました。worker.pl
はジョブのワーカです。ジョブキューにジョブが投入されるのを待ち、そのジョブを実行します。今回の例ではジョブの内容を画面に出力します。enqueue.pl
はジョブを投入するプログラムです。
では、worker.pl
を実行してみましょう。この時点ではジョブキューに何も入っていないので、画面には何も表示されず、ジョブの待ち状態になります。
次に、ジョブを投入してみましょう。別の端末を開いて、enqueue.pl
を実行します。
ジョブが投入されたので、ワーカは待ち状態から抜け、ジョブを実行します。worker.pl
を実行した端末に受け取ったジョブの内容が表示されるはずです。
BRPOP
コマンドの動作イメージはつかめたでしょうか。この例ではワーカが1つだけでしたが、worker.pl
をたくさん起動してからジョブを投入したり、worker.pl
とenqueue.pl
の実行順を変えたりなどして、実行結果を観察するとより理解が深まるでしょう。
ハッシュ型
ハッシュ型はPerlにおけるハッシュ変数にあたる型で、文字列をキーとする配列です。キーに対応する値には文字列が使用できます。
セット型
セット型は値の集合を扱うための型です。セット型に保存されている値は順序を持たず、同じ要素を重複して保存することはできません。値が集合に含まれているかを高速に判定したい場合に使います。
同じ要素が重複しないことを利用して、ユニークな要素数(たとえば毎日のユニークユーザー数)を調査するという用途にも使えます。
ただし、セット型を使えば異なり数を正確にカウントできますが、ユニークな要素数が多い場合、大量のメモリが必要になってしまいます。得られるのが近似値でよい場合は、HyperLogLogのほうが有用かもしれません。詳しくはコマンドリファレンスを参照してください。
集合の中からランダムに要素を取得するSRANDMEMBER
コマンドも便利です。ゲームではマッチング処理などでランダムな要素取得を多用しますが、通常のRDBでは難しい処理です。そのような処理も簡単に行えるのがRedisの利点です。
次のプログラムはSRANDMEMBER
コマンドの利用例です。one、two、threeの3つの要素が入った集合から、ランダムに1つ選びます。
ソート済みセット型
ソート済みセット型はセット型と同様に集合を扱う型ですが、それぞれの値にスコアを付けることによって、順序付けできるのが特長です。要素の順位を取得したり、あるスコアの範囲にいくつ値が含まれるかを調べたり、といった操作を高速に行えます。
Luaスクリプト
Redisにはデータ保存機能だけでなく、スクリプト言語Luaの実行環境が組み込まれています。この機能により任意のプログラムをRedis上で実行できます。
利点
Perlを使えば複雑なデータ処理も簡単に書くことができるのに、なぜRedisにプログラムを実行する機能が付いているのでしょうか。
それは、アトミックな処理を書きやすいという理由からです。たとえばWebアプリケーションでは、複数のリクエストを同時に処理することが一般的です。そのため、注意してプログラミングしないと、更新処理が複数同時に走ってしまいデータを壊してしまう可能性があります。Redisでは同時に実行されるスクリプトは1つだけに制限されているため、そういったことを気にしないで済みます。
Luaスクリプトの実行
Luaスクリプトの実行にはEVAL
コマンドを利用します。たとえばSET
コマンドを実行するLuaスクリプトを実行してみましょう。
EVAL
コマンドの引数の「1」は、引数にキーがいくつ含まれるかを表しています。この例の場合は「key」の1つだけなので「1」を指定しています。
Luaスクリプトのキャッシュ
EVAL
コマンドを使った方法では毎回スクリプトをRedisに転送するため、大きなスクリプトを転送するとネットワーク帯域を多めに使ってしまいます。Redisにはスクリプトをキャッシュしておく機能があるので、それを利用してみましょう。SCRIPT LOAD
コマンドでスクリプトを保存したあと、EVALSHA
コマンドで呼び出せます。
しかし今度は、スクリプトがすでにキャッシュされていた場合にスクリプトが再転送されるので、効率が悪くなってしまいます。EVAL
コマンドのドキュメントではこの問題を解決するために、「EVALSHA
を実行してみてNOSCRIPT
のエラーが出たら、EVAL
を実行する」という方法が紹介されています。EVAL
コマンドはスクリプトの実行と同時にキャッシュも行うので、初回エラーになっても次回からはEVALSHA
が成功するようになります。
この処理を簡単に行えるよう筆者が開発したのが、Redis::Scriptモジュールです。Redis::Scriptを利用すると必要なときにだけスクリプトの転送が行われるため、効率的にスクリプトを利用できます。
<続きの(3)はこちら。>
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT