Redis に入門する

どうやらアプリケーションのキャッシュの用途のために Redis なるデータベースがよく使われるらしい。ってことで調べて動かしてみたメモ。

Redis とは

まずはざっとググってみた。公式サイトWikipedia の説明によると Redis には以下の特徴があるらしい。

  • key-value 型のデータベースである。キャッシュを実現するためによく用いられる。
  • 読み書きするデータがメモリ上に展開される in-memory database としての機能を提供している。もちろん、設定により様々なタイミングでストレージと同期できる。
    • 場合によってはメモリめっちゃ食いそう
  • データベース上のデータを操作するには特定のデータ構造に対する operation を実行する。SQL のような明示的なクエリを発行する方式ではない。
    • このあたりは後で動かしながらイメージを掴みたい。
  • Linux 上で動かすことが推奨されている。

Redis を動かしてみる

なんとなく概要をつかんだところで getting started を見ながら環境構築してゆく。

まずは インストラクション 通りに Ubuntu 上で apt-get install redis を実行する。無事インストール完了。

コマンドラインから redis-cli を起動して以下のようにコマンドを実行してみる。

$ redis-cli
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> set name t-keita
OK
127.0.0.1:6379> get name
"t-keita"
127.0.0.1:6379> exit

PING って打つと PONG って返してくれるらしい。これは疎通確認に使う。その後 set コマンドで name という key をセットして、get コマンドで key に対応する value を取得できている。なるほど。

ちなみに exit してからもう一度以下のように get を実行してみる。

$ redis-cli
127.0.0.1:6379> get name
"t-keita"

前回のセッションで設定した name の値が保持されていることが分かる。つまり set した値がストレージ上に保存されているんだけど、どこに保存されてるんだろう。

ちなみに Getting Started にある "Securing Redis" セクションの記載によると、Redis を起動するとサーバを起動し 6379 番ポートで処理を受け付けている。このサーバは、デフォルトではあらゆるアクセスを受け付けているのでセキュリティ的に危ない。少なくともファイアウォールの設定をして、外部からの直接的な命令の実行は避けるべきとのこと。もちろん、必要に応じてアクセス元に対して制約をかけたり認証を設けるべきである。そのための設定ファイルやオプションが提供されている。

データ構造いろいろ

次に Redis でサポートされているデータ構造をざっと眺める。Redis data types tutorial に記載されているサンプルコードを見てゆく。

  • get, set コマンドで String 型の値の取得や更新ができる。
  • mset, mget コマンドで複数の key-value ペアを扱える。
  • exists コマンドでキーの存在確認ができる。
  • expire コマンドでそのエントリーの存在期限を設定できる。これはキャッシュの管理に便利そう。ある程度古くなったら削除するみたいな。
  • rpush, rpop で List 型の value への要素の追加や削除ができる。lrange で指定した範囲の値を取得できる。おそらく lrange の l は left ではなく list のこと。
  • brpop は blocking operation であり rpop できるようになるまで n 秒間待つ。リストの更新内容をリアルタイムにキャッチアップしたいときに使えそう。
  • hget, hset コマンドで Hash 型の値の取得や更新ができる。ハッシュマップなので O(1) のアクセスができる。JSON オブジェクトみたいなものを操作するときに便利そう。

あとは Set 型とかもあるが、データ構造や操作についてイメージがつかめたのでここでいったん終わり。

要するに Redis では value として一般的なプログラミング言語でサポートされているようなデータ構造(文字列、リスト、連想配列など)がサポートされていて、それに対して predefined な operation を作用させることでデータベースの状態を更新できる。この操作が atomic になっているので便利なわけだ。

プログラムから Redis を動かしてみる

つぎにプログラムから Redis を動かしてみる。

Redis にアクセスするため様々なプログラミング言語に対してライブラリが 提供されている。言語はなんでもよいんだが、Rust 向けのライブラリである Redis-rs をクライアントとして使ってみる。

GitHub リポジトリREADME.md を見ながら以下の操作を試してみた。

まずは cargo の依存関係に以下を追加する。

[dependencies]
redis = "0.22.1"

そして以下のようなプログラムを書いてみた。Redis とのコネクションを確立して name に対応する値を get するだけ。

use redis::Commands;

fn get_value_from_redis(name: &str) -> redis::RedisResult<String> {
    let clinet = redis::Client::open("redis://127.0.0.1/")?;
    let mut con = clinet.get_connection()?;
    con.get(name)
}

fn main() {
    match get_value_from_redis("name") {
        Ok(value) => {
            println!("Got the value {}", value);
        }
        Err(_) => {
            println!("err");
        }
    }
}

"get" みたいな operation を示す文字列を Redis に投げるのではなく、redis-rs が提供する get 関数を叩けばよい。

以下のようにビルドして実行してみるとちゃんとデータベースから値を取得できていることが分かる。

$ cargo build
$ ./target/debug/redis_sample 
Got the value t-keita

Redis がサポートする各 operation に対応する Rust の関数が提供されているので、アプリケーションとデータベースのシームレスな接続ができているように思われる。

環境構築にあたり特につまることがなかったのもグッド。

気になったことを調べる

ここまでのチュートリアルで Redis の動作に対するイメージが湧いたので、最後に気になったことを追加で調べて終わりにしたい。

Redis のデータはストレージ上のどこに保存されている?

Redis は in-memory データベースであるが、もちろんストレージ上にデータを保存できる。しかし Redis を起動したときにはすでにデータベースに接続されており、データベースの内容が保存されている場所がユーザからは隠蔽されている。いったいどこに保存されているんだ。

ググってみると、Redis の設定ファイルは /etc/redis/redis.conf にあるらしく、この中に以下のような作業ディレクトリを指定している箇所がある。

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir /var/lib/redis

自分の環境では作業ディレクトリとして /var/lib/redis が指定されているようで、確かに以下のように dump.rdb というファイルが存在している。

$ sudo ls /var/lib/redis/
dump.rdb

このファイルの中身はバイナリだが、xxd コマンドなどで覗いてみると確かにデータベースに保存した内容が格納されているように見える。おそらくこれが正解。

どんなプロトコルを使って Redis とクライアントは通信している?

コマンドラインから起動する redis-cli を見る限り、命令は 127.0.0.1:6379 に投げられているように見える。接続先を IP アドレスとポート番号で指定できるのはよいとして、その上でどんなプロトコルを使って通信が行わているのか気になる。

ググったところ、RESP protocol という独自の通信プロトコルを定義しているらしい。仕様が 公式ページ に思いっきり載っていた。RESP は TCP を前提に定義しているらしく、TCP/IP に従う Application Layer として RESP が定義されている感じか。データベースを作るために独自の通信プロトコルを定義するとは気合いが感じられる。

1つのマシンに複数の Redis インスタンスを保持することは可能か?

ここまでの話で、データが格納されるストレージの情報はクライアントからは隠蔽されているように思われる。たとえばアプリケーションに応じて使用するデータベースインスタンスを切り替えたりするような用途を想定していないように見える。OS とデータベースが一対一対応するようなイメージ。この理解は正しそうか?

ググってみたところ、普通に port を分ける形で複数の Redis インスタンスを起動できるらしい。このページ とかに設定方法が書いてある。port ごとに設定ファイルを作るだけで別々の Redis インスタンスが起動する。

しかしながら、おそらく1つのマシンで複数の Redis インスタンスを起動するような使い方は意図されていないように思われる。Scaling with Redis Cluster のページなどを見る限り、Redis は horizontal scaling に重きを置いており、Redis をホストするコンテナをたくさん作ってデータベースの処理負荷を分散させるようなユースケースに強みがありそう。そのため1つの Redis インスタンスは1つのアプリケーションさえ想定できれば十分なんだろう。知らんけど。

以上、Redis についてざっと調べて動かしてみたメモでした。Redis の公式ページのドキュメントが非常に充実していて人気がある理由も納得。

おしまい。