Redisとは
特徴
非RDB(Relational Database)。マシンのメモリ上へデータを保存する。
※対比として、RDBであるMySQLではファイルへデータを保存している。
メリット
- メモリ上でデータを扱うため処理が早い(HDDとメモリでは数万倍の差がある)
- すべての処理が完了しないとロールバックされる機能がデフォである
- 直列処理であるため、データの不整合は発生しない
- 複数のDBを持てる
デメリット
- マシン上のメモリの消費が大きく、データの保存量上限は少ない
- メモリ上限に達すると指定したルールに従ってデータが削除される
- マシンが落ちたときデータが揮発してしまう
- シングルスレッドでの動作であり、並列処理ができない & 1コアしか使えないためCPUのコア数を上げても無駄
- マスタ-スレーブ構成が可能だが、非同期であるため不整合が発生する可能性あり
データの揮発対策
2パターンある
スナップショットからの復旧
「Redis Database」というファイルへRedisのスナップショットを作成できる、これを定期的に行うことである程度の防止が可能である。
直前のデータは復旧不可。
AOF
DB操作のログからデータをバックアップする方法。
ログを取る分、DB操作時の実行速度は落ちるが、直前のデータは復旧できる。
用途
- セッション管理: RDSでセッション情報を扱うより高速に動作させられる
- Webページのキャッシュ
- リアルタイム性を求められる機能(ランキングとか): リスト化した状態で保存して随時ソートを行う
類似のソフトウェアMemcached
うーん。目立ったメリットもないので、Redisにしとけばまぁ間違いなさそうである。
参考
利用手順
当然だが、Redisを利用するには、そのサーバーとクライアントソフトウェアが必要。
docker-composeでローカル開発環境を構築する際の手順
Redisサーバー
docker-compose.yml
# redisコンテナはデフォルトで0〜15のインデックスのDBが作成され、リッスンポート6379で起動し、
# /dataに60秒に1回スナップショット(Redis Databaseファイル)を作成するようになっている
redis:
container_name: redis
# dockerホスト起動時にコンテナが起動するよう設定
restart: always
image: "redis:latest"
ports:
- "6379:6379"
volumes:
- "./data/redis:/data"
Redisクライアント
gem(redis)のセットアップ
①Gemfileに記載
gem 'redis'
②インストール
bundle install
③Redisクライアントの設定
③-1. 各RAILS_ENVごとに切り替えるため、config/settings/<環境名>.yml に以下を記載する
# 例. development.yml で ローカルのredisサーバーへ接続する
redis:
host: redis
port: 6379
db: 0 # DBのindex. Redisはデフォで16のDBを持ち、0〜15のindexがある。
③-2. config/initializers/redis.rbを作成し、以下のように設定しておく
# RAILS_ENV別のredisクライアント設定読み込み
redis = Settings.redis
# redisクライアントのインスタンスを生成してRedis.currentでアクセスできるようにする
Redis.current = Redis.new(
host: redis.host,
port: redis.port,
db: redis.db
)
④動作確認
# 前提: redisコンテナが起動している
# 1. railsサーバーが起動しているコンテナへログイン
docker exec -it <railsコンテナID> bash
# 2. rails console起動
bundle exec rails c
# 3. 適当なキー、値をセットしてみる
Redis.current.set("a", "b")
Redis.current.get("a")
=>
"b" と表示されれば、Redisサーバー & Redisクライアントともに正常に動作している
参考
利用方法
redisクライアントのインスタンスに対してキー・バリューのset系メソッド(データの書き込み), get系メソッド(データの読み込み)を実行する、というだけ。
ただし、バリューは以下5つのデータ型が取れるが、それぞれで使うべきメソッドが違う。
String型
参考: リファレンス
# 保存されるデータ型のイメージ
{'key': "value"}
# set
## 期限なし
Redis.current.set('en', 'Hello')
## 期限あり(秒で設定)
Redis.current.setex('en', 10, 'Hello')
# get
Redis.current.get('en')
=> "Hello" ※setexでセットした場合は、指定した秒数後はnilで返ってくる。
List型
先頭 or 末尾にデータの追加が可能.
# 保存されるデータ型のイメージ
{"key": ["value1", "value2",...]}
# set
## 末尾追加
Redis.current.rpush("en", "Hello")
## 先頭追加
Redis.current.lpush("en", "Hello2")
# get
## i番目の値を取得
Redis.current.lindex("en", 0)
=>"Hello2"
## リストのi番目からj番目までの値を配列で取得(先頭インデックスは0)
Redis.current.lrange("en", 0, 1)
=>["Hello2", "Hello"]
Set型
valueの配列に同じ値の要素は登録不可能同。
順不同で保存。
集合演算メソッドが利用できる、というより集合演算が目的っぽい
# 保存されるデータ型のイメージ
{"key": ["value1", "value2",...]}
# set
## 追加
Redis.current.sadd("en", "Hello")
Redis.current.sadd("en", "Hello2")
Redis.current.sadd("en2", "Hello2")
Redis.current.sadd("en2", "Hello3")
# get
## 全要素を取得
Redis.current.smembers("en")
=>["Hello2", "Hello"]
## 和を出す
Redis.current.sunion("en", "en2")
=>["Hello2", "Hello3", "Hello",]
SortedSet型
ソート済みセット型。同じ値は登録不可能。
配列要素は「スコア」でソートされ、「順位」を持つ。スコア/順位で範囲検索が可能。
# 保存されるデータ型のイメージ
{"key": [["value1", score1], ["value2", score2]]}
# set
Redis.current.zadd("score_ranking", 10, "user_id_1")
Redis.current.zadd("score_ranking", 500, "user_id_2")
Redis.current.zadd("score_ranking", 600, "user_id_3")
Redis.current.zadd("score_ranking", 1, "user_id_4")
# get
## 特定要素の順位を取得
Redis.current.zrank("score_ranking", "user_id_3")
=>3
## スコア上位iからjまでの値を取得。iは0始まり
Redis.current.zrevrange("score_ranking", 0, 2)
=>["user_id_3", "user_id_2", "user_id_1"]
## スコア上位iからjまでの値とスコアを取得。iは0始まり
Redis.current.zrevrange("score_ranking", 0, 2, with_scores: true)
=> [["user_id_3", 600.0], ["user_id_2", 500.0], ["user_id_1", 10.0]]"user_id_3"], [500, "user_id_2"], [10, "user_id_1"]]
Hash型
文字列型のキー & バリューのハッシュ。キーでの検索は可能だが、値での検索は不可能。
RDBっぽい管理方法。
参考: リファレンス
# 保存されるデータ型のイメージ
{"key": {"value_key": value_value}}
# 1つのカラム・値で構成されるレコードをset,getする例
## set
### すでに「"record"」が存在する場合は上書きされる
Redis.current.hset("record", "column_1_key", "column_1_value")
### すでに「"record"」が存在する場合は上書きされない
Redis.current.hsetnx("record", "column_1_key", "column_1_value")
## get
Redis.current.hget("record", "column_1_key")
=>
"column1_value"
# 複数のカラム・値で構成されるレコードをset,getする例
## set
Redis.current.hmset("record",
[
"column1_key", "column1_value",
"column2_key", "column2_value",
]
)
## get
Redis.current.hmget("record", ["column_1_key", "column_2_key"])
=> ["column_1_value", "column2_value"]
参考
応用編
ActiveRecordなどのオブジェクトをString型にしてRedisへ保存する場合には、
- Marshal.dump(<オブジェクト>)
- Marshal.load(<オブジェクト>)
を使うといい感じにいける。
キー: "key", 値: ActiveRecord型 でset, getする例
# set
user = User.first
Redis.current.set("key", Marshal.dump(user))
# get
user_str = Redis.current.get("key")
Marshal.load(user_str)
メモ
Redisクライアントのインスタンスを保存するようのクラスメソッド「Redis.current」がgem 「redis」に存在するので、各RailsソースにまたがってRedisを利用する際にはこれを使うべき
参考