AND OR  

このページではインメモリKVS(Key-Value Store)のRedisに関するメモを書いています。
ブログでもまとめてあるので参考までに。
Redisの監視/分析系ツールまとめ
インメモリKVSのRedisについて
Redis AS3クライアント



  • 環境
    CentOS 5.8 (x86_64)
    Redis 2.2.8, 2.6.10
    Rubyクライアント redis-rb
    Pythonクライアント redis-py
    PHPクライアント predis

Redis data types (型について)

Redisは基本の文字列型の他にリストやセット、ハッシュなどの様々な型があるが、それぞれの型に対するコマンドがatomicに動作するという特徴を持つ (参照: Command reference – Redis)。

String (文字列)

基本となる文字列型。整数文字列をセットしてインクリメント/デクリメント操作をすると内部で整数値(符号付き64bit値)として扱われる。また、バイナリセーフなのでJPEGイメージなどどんな種類のデータでも保存可能。v2.2以降ではLRU (Least Recently Used)をサポートしているのでmemcachedのようにキャッシュ用途としても利用可能。

List (リスト)

リスト型。複数のプロセスから1つのリストにどんどん値をpushしていっても安心で、rpush/lpushなどのList型のコマンドはatomicな操作になっている。

  • RPOPLPUSH(srckey, dstkey)
    List型のコマンドを別途紹介。このコマンドは srckey に対応するリストの末尾要素を削除して、その要素を dstkey に対応するリストの先頭にpushする。ひとつのリストをバックアップとして使ってメッセージキューを安全に実装するときなどに使える。また、srckey と dstkey が同じ場合はデータがローテーションする。

Set/Sorted Set (集合/ソート済み集合)

Setは順不同の集合型。和集合や積集合などの各種集合演算が利用可能で、メンバの重複を許可しない。また、Sorted Setでは任意のスコアで値がソートされる。

Hash (ハッシュ)

Hash型。ハッシュ形式のデータを保存できる。

Hash型では効率的にメモリを使用するために設定ファイルでパラメータを指定できるようになっている。要調査。

# Hashes are encoded in a special way (much more memory efficient) when they
# have at max a given numer of elements, and the biggest element does not
# exceed a given threshold. You can configure this limits with the following
# configuration directives.
hash-max-zipmap-entries 512
hash-max-zipmap-value 64

Persistence (永続性)

Redisはインメモリで動作するが、非同期でディスクにもデータを書き出すため永続性を備えている(データファイルはRDBと呼ばれている)。ディスクに書き出すタイミングは設定ファイル(redis.conf)で指定可能。デフォルトでは以下のように設定されている。

## 以下をコメントアウトするとディスクには一切書き出されない
## その場合はmemcachedと同様にインメモリKVSとして振る舞う
save 900 1    ## 1回更新があったら900秒(15分)後
save 300 10    ## 10回更新があったら300秒間(5分)後
save 60 10000    ## 10000回更新があったら60秒間(1分)後

rdbcompression yes  ## ダンプファイルをLZFで圧縮するかどうか
dbfilename dump.rdb    ## ダンプファイル名
dir ./    ## ファイルが書き出される場所

このようにRedisの永続化機能はスナップショットを書き出す方式なので、もしRedisサーバが落ちたときは最新のデータが失われる可能性がある。これを防ぐために append only file と呼ばれるファイルに更新コマンドを書き出すこともできる。

append only file

デフォルトでは無効になっているので利用するために設定ファイルを編集する。

appendonly yes

これだけ。デフォルトのファイル名は appendonly.aof になっている。ここで、set mykey foo を実行したときのappend only fileの内容は以下のようになっている。このようにプロトコルがシンプルなので簡単に読める($の後ろの数字はバイト数)。

*2
$6
SELECT
$1
0
*3
$3
set
$5
mykey
$3
foo

同期には fsync() を使っている。どのタイミングで同期するのかもredis.confで指定可能。

# appendfsync always  ## すべての更新コマンド実行時に同期、遅いが安全
appendfsync everysec  ## 毎秒同期、最悪で1秒間でデータが失われる可能性がある、デフォルト
# appendfsync no  ##データの同期はOSに任せる、安全ではないが高速

Replication (レプリケーション)

Redisはマスター/スレーブのレプリケーションをサポートしている。利用するのは簡単で、スレーブサーバのRedisの設定ファイルに以下の項目を追加するだけ。

slaveof 127.0.0.1 6379  ## slaveof という項目にマスターのIPアドレスとポート番号を指定

マスターの設定ファイル内で bind の項目が 127.0.0.1(localhost)に設定されている場合はlocalhostからしか接続できなくなるので、この設定を修正する。

bind 0.0.0.0  ## あるいは bind の項目自体を削除する

ログでは以下のように書き出されている。

## マスター側ログ
[15876] 12 Jun 15:52:33 * Slave ask for synchronization
[15876] 12 Jun 15:52:33 * Starting BGSAVE for SYNC
[15876] 12 Jun 15:52:33 * Background saving started by pid 23083
[23083] 12 Jun 15:52:33 * DB saved on disk
[15876] 12 Jun 15:52:33 * Background saving terminated with success
[15876] 12 Jun 15:52:33 * Synchronization with slave succeeded

## スレーブ側ログ
[23082] 12 Jun 15:52:33 * Connecting to MASTER...
[23082] 12 Jun 15:52:33 * MASTER <-> SLAVE sync started: SYNC sent
[23082] 12 Jun 15:52:33 * MASTER <-> SLAVE sync: receiving 34 bytes from master
[23082] 12 Jun 15:52:33 * MASTER <-> SLAVE sync: Loading DB in memory
[23082] 12 Jun 15:52:33 * MASTER <-> SLAVE sync: Finished with success

Hashing (データ分散)

Redisクライアントでは Consistent Hashing によって複数サーバにデータを振り分けている(対応していないクライアントライブラリもある)。ここではRubyクライアントライブラリ(redis-rb)を使って確認。(MRI 1.9.2p0 (2010-08-18 revision 29036))

  • 出力結果
    localhost:6379 => key12, key10, key11, key13, key6
    localhost:6380 => key20, key7
    localhost:6381 => key19, key5, key14, key15, key16, key17, key18
    localhost:6382 => key2, key3, key4, key8, key1, key9

Pipelining (パイプライニング)

Redisではパイプライニングにも対応している。複数のコマンドをサーバーの応答を待たずに一括で送信できる。

ベンチマークをとってみる。

  • 結果
    without pipelining 2.295884 seconds  ## パイプライニング無し
    with pipelining 0.443958 seconds  ## パイプライニング有り
    パイプラインを使用したほうが5倍ほど速い。

Pub/Sub

RedisではPublish-Subscribeモデルのメッセージング機能をサポートしている(Publish–subscribe pattern)。

  • subscriber.rb
  • 動作確認

Lua Scripting

Redis v2.6からLuaインタプリタが組み込まれ、サーバサイドでLuaスクリプトを実行することができるようになった。

EVAL

EVAL script numkeys key [key ...] arg [arg ...]

EVALコマンドは引数に渡した文字列をLuaスクリプトとして実行(評価)する。numkeys には操作するkeyの数を指定する。keyの操作が必要ないスクリプトの場合は0を指定。

redis-cli コマンドの ''--eval オプション''を使えば外部スクリプトファイルを読み込んで実行できる。

または、以下のようにシェルでファイルの中身を展開して渡す方法もある。

Luaスクリプト内でRedisの各コマンドを呼ぶことができる。Luaから使えるAPIは以下の2つ。

  • ''redis.call()''
  • ''redis.pcall()''

この2つの違いはエラー時の挙動。エラー発生時、redis.call() はスクリプトを終了し、redis.pcall() はエラーを補足してスクリプトを継続させる。

KEYS配列は操作対象となるkeyが格納されている配列。上記例では'foo'というkeyに対してINCRコマンドを呼んで値をインクリメントしている。また、更新する値を指定する場合はKEYSと併せてARGV配列も使う。このARGV配列には操作するkeyに対する値(value)が格納される。

using modules

Redis v2.6.10に組み込まれているLua処理系では以下のモジュールが利用できる。

  • base
  • table
  • string
  • math
  • debug
  • cjson (3rd party)
  • cmsgpack (3rd party)

ここではいくつかのモジュールを紹介。

  • tableモジュール
    テーブル操作の為のユーティリティモジュール。 返される文字列は "5 > 4 > 3 > 2 > 1" となる。
  • cjsonモジュール
    JSONデータを扱うためのモジュール。
  • cmsgpackモジュール
    MessagePackデータを扱うためのモジュール。

EVALSHA

作成したLuaスクリプトをRedisサーバに登録する。あらかじめサーバ側に登録しておけばスクリプトの転送コストを削減することができる。

## Luaスクリプトのサーバへの登録、スクリプトのSHA1ハッシュダイジェストが返される
SCRIPT LOAD script
 
## 登録されているLuaスクリプトを実行、第一引数にはスクリプト本体ではなくSHA1ハッシュ文字列を指定
EVALSHA sha1 numkeys key [key ...] arg [arg ...]

HyperLogLog

HyperLogLogとはデータのcardinality(異なり数、種類の数)を高速に推定するアルゴリズムで、高速かつ省メモリで実行できる。

Redis 2.8.9における HyperLogLog 関連のコマンドは以下の3つ。プレフィックスの'PF'は HyperLogLog アルゴリズムの考案者である Philippe Flajolet 氏に敬意を表して付けたとのこと。

  • PFADD
  • PFCOUNT
  • PFMERGE

Pythonクライアントライブラリ(redis-py)からも簡単に利用できる。

Job Queue

Resque

バックエンドにRedisを利用したRuby製ジョブキュー。Githubでの利用実績がある。

  • Job
    $ chmod +x job.rb
    $ ./job.rb  ## ジョブをエンキュー
  • Worker
    ワーカー用のRakefile等は公式のデモ(https://github.com/defunkt/resque.git)から借りてきた。
    $ QUEUE=default VERBOSE=true rake resque:work
    * * * Starting worker
    * * * got:  (Job{default}  |  Demo::Job  | ["param"])
    processed a job!
    * * * done:  (Job{default}  |  Demo::Job  |  ["param"])

Pyres

ResqueのPythonクローン。公式サイト

  • インストール
    導入はpipからできる。
    $ pip installl pyres
    workerスクリプト(pyres_worker)もいっしょにインストールされる。
  • Job
    Resqueと同じようにジョブの処理は perform メソッドに実装する
  • Worker
    パッケージに同梱されている pyres_worker を使うと楽。
  • pyres_manager
    prefork型のworkerも提供されている。こちらの方が処理効率が良いのでオススメ。

Client Library

ここではPythonおよびPHPクライアントライブラリの使い方も載せておく。

Python (redis-py)

  • インストール
    pip install redis
  • サンプル
    String/List/Set/Hash型の操作を行うPythonサンプルコード。

PHP (predis)

predisは名前空間の機能を用いているため、PHP5.3以降でないと動作しないので注意。

  • インストール
    githubからライブラリファイルをダウンロードして include_path の通ったディレクトリに配置するだけ。
    nrk/predis · GitHub
  • サンプル
    String/List/Set/Hash型の操作を行うPHPサンプルコード。ここではPHP5.4の配列の短縮構文を使っているので注意([]構文)。

Sentinel

Redis本家プロジェクトで開発されている、Redisサーバの死活監視/通知および自動フェイルオーバー機能を提供する管理サーバ(redis-sentinel)。v2.4.16または2.6.0-rc6以降のバージョンから利用可能になった。

configuration

## /etc/redis/sentinel.conf

# port <sentinel-port>
port 26379
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster db0 6379 2
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 5000
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 900000
# sentinel can-failover <master-name> <yes|no>
sentinel can-failover mymaster yes
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

各設定項目について。Sentinelによるクラスタ監視はいわゆるQuorumベース投票の方式を採る。複数のSentinelがMasterを監視していて、その内しきい値数以上のSentinelがMasterのダウンを検知したらフェイルオーバー処理を開始する。

monitorMasterのホストとポートおよび状態が''ODOWN(objectively down)''に移行するための定足数(quorum)
down-after-millisecondsMaster/Slaveのダウン検知後、状態が''SDOWN(subjectively down)''に移行するまでの時間(ms)
failover-timeoutフェイルオーバー処理のタイムアウト(ms)
can-failoverフェイルオーバー処理が実行可能か(yes/no)
parallel-syncsSlaveをMasterに昇格させた後、いくつのSlaveと同期させるか

SentinelはMasterとSlave両方の死活監視をしてくれるが、状態が''ODOWNに遷移するのはMasterのみ''なので注意。can-failover を no にして起動したSentinelプロセスはフェイルオーバー処理自体は行わず(他のSentinelに任せる)、自身はダウン検知と投票処理のみを行う。

test

Sentinel起動前にレプリケーションが動作していることを確認。

続けて3つのSentinelプロセスを立ち上げる。

ログにて各SlaveおよびSentinelと接続したことを確認。

[19069] 14 May 16:30:31.909 * +slave slave db1:6379 db1 6379 @ mymaster db0 6379
[19069] 14 May 16:30:31.909 * +slave slave db0:6380 db0 6380 @ mymaster db0 6379
[19069] 14 May 16:30:38.320 * +sentinel sentinel db0:26380 db0 26380 @ mymaster db0 6379
[19069] 14 May 16:30:39.655 * +sentinel sentinel db1:26379 db1 26379 @ mymaster db0 6379

また、Sentinel起動後はINFOコマンドでSentinel関連の情報を確認ができる。

フェイルオーバー処理の動作確認の為、ここでMasterを落とす。

ログにてフェイルオーバー処理の進捗を確認。

[19069] 14 May 16:30:51.170 # +sdown master mymaster db0 6379
[19069] 14 May 16:30:52.386 # +odown master mymaster db0 6379 #quorum 2/2
[19069] 14 May 16:30:52.386 # +failover-triggered master mymaster db0 6379
[19069] 14 May 16:30:52.386 # +failover-state-wait-start master mymaster db0 6379 #starting in 13910 milliseconds
[19069] 14 May 16:31:06.379 # +failover-state-select-slave master mymaster db0 6379
[19069] 14 May 16:31:06.480 # +selected-slave slave db1:6379 db1 6379 @ mymaster db0 6379
[19069] 14 May 16:31:06.480 * +failover-state-send-slaveof-noone slave db1:6379 db1 6379 @ mymaster db0 6379
[19069] 14 May 16:31:06.583 * +failover-state-wait-promotion slave db1:6379 db1 6379 @ mymaster db0 6379
[19069] 14 May 16:31:06.901 # +promoted-slave slave db1:6379 db1 6379 @ mymaster db0 6379
[19069] 14 May 16:31:06.902 # +failover-state-reconf-slaves master mymaster db0 6379
[19069] 14 May 16:31:06.991 * +slave-reconf-sent slave db0:6380 db0 6380 @ mymaster db0 6379
[19069] 14 May 16:31:07.294 * +slave-reconf-inprog slave db0:6380 db0 6380 @ mymaster db0 6379
[19069] 14 May 16:31:08.316 * +slave-reconf-done slave db0:6380 db0 6380 @ mymaster db0 6379
[19069] 14 May 16:31:08.417 # +failover-end master mymaster db0 6379
[19069] 14 May 16:31:08.417 # +switch-master mymaster db0 6379 db1 6379
[19069] 14 May 16:31:08.548 * +slave slave db0:6380 db0 6380 @ mymaster db1 6379
[19069] 14 May 16:31:09.068 * +sentinel sentinel db0:26380 db0 26380 @ mymaster db1 6379
[19069] 14 May 16:31:12.258 * +sentinel sentinel db1:26379 db1 26379 @ mymaster db1 6379

SentinelがMasterのダウンを検知すると状態がSDOWNに、quorumパラメータで指定した数のSDOWNが揃うと次はODOWNへと遷移、その後フェイルオーバー処理が開始される。最後に新しいMasterとSlaveで正常稼働していることを確認。

Sentinel API

SentinelはいくつかのAPIを提供している。

その他はブログエントリーを参照のこと。
Redisの監視/分析系ツールまとめ

Configuration

Redisサーバの設定項目について注意点などを整理する。

  • ''maxmemory/maxmemory-policy''
    maxmemory には Redisサーバがデータ保存領域として利用するメモリ量を指定する。単位は "100m" (MB)や "2g" (GB)のように指定可能。maxmemory-policy にはデータ量が maxmemory で設定した値に達した際にRedisサーバにどういう挙動をさせるかを指定する。
    設定値説明
    volatile-lruexpireが設定されているキーを対象にLRU(Least Recentry Used)で削除。(デフォルト)
    allkeys-lru全てのキーを対象にLRUで削除。
    valatile-randomexpireが設定されているキーを対象にランダムで削除。
    allkeys-random全てのキーを対象にランダムで削除。
    valatile-ttlexpireが設定されているキーを対象に期限切れが近い順に削除。
    noevictionキーを一切削除しない。書き込みは以後エラーとなる。
  • ''appendfsync''
    appendfsync はデータをバッファキャッシュからディスクに書き出すための fsync(2) を呼ぶタイミングを指定する。
    設定値説明
    alwaysクエリ毎にfsyncする。データの信頼性は増すが性能は落ちる。
    everysec毎秒fsyncする。(デフォルト)
    noRedisからfsyncせず、ディスクへのflushはOSに任せる。
  • ''auto-aof-rewrite-percentage/auto-aof-rewrite-min-size''
    AOFに記録されるのは実行されたRedisのコマンドをそのまま記録しているが、長く運用しているとファイルサイズがかなり肥大化する。そのためRedisにはAOFを書き換えてサイズを小さくする機能を備えている。前回書き換えた時のファイルサイズの auto-aof-rewrite-percentage で指定した割合まで、現在のAOFのサイズが大きくなった時に書き換え処理が実行される。ただし、auto-aof-rewrite-min-size で指定したサイズ以下の場合は書き換え処理は実行されない。
  • ''slowlog-log-slower-than''
    MySQLにおけるスロークエリと同様。コマンド実行が slowlog-log-slower-than で指定した時間(マイクロ秒)以上かかったコマンドをスローログとして記録する。