Redis 公式ドキュメント まとめ
Last updated
Last updated
Redis の公式ドキュメントを一読しておこうと思い、読んでまとめてみました。( 2018/05/06 時点)
ただし、Redis 4.0 については、公式ドキュメントに一部内容が含まれますが、RDB, AOF や Docker/NAT サポート辺り等、その他複数機能強化や変更点がありますので、詳細は、下記リリースノートを参照していただければと思います。 https://raw.githubusercontent.com/antirez/redis/4.0/00-RELEASENOTES
別途、Redisの機能のまとめ記事等は以下のURLを参照いただければと存じます。
URL: https://redis.io/documentation
URL: https://redis.io/commands
以下、全206コマンド
URL: https://redis.io/topics/pipelining
Redis はパイプライニングをサポート
メモリを使ってキューにリクエストを格納。
複数のコマンドを1回のread()。複数のリプライを1回のwrite()でまとめる。
スクリプトでは、サーバサイドでの処理により効果的
ループバックインターフェイスでさえ、busy loopが遅くなることがある理由は、インターフェースのバッファにコマンドがあっても、サーバからの読み取りのためにカーネルがスケジューラでサーバプロセスを稼働させていないことがある場合があるから。
Rubyのサンプルコード
URL: https://redis.io/topics/pubsub
サブスクライブされたクライアントは、コマンドを発行するべきではない。
上記コマンドで SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE, PUNSUBSCRIBE, PING, QUITは除く
redis-cliではサブスクライブモードではコマンドを受け付けず、Ctrl-Cによるモード終了のみ受け付ける。
PSUBSCRIBE, PUNSUBSCRIBE で正規表現のパターンマッチングによる複数操作も可能
サンプルコード: https://gist.github.com/pietern/348262
References
Publish/Subscribe messaging paradigm: https://redis.io/topics/protocol
URL: https://redis.io/commands/eval
EVAL, EVALSHAはLuaインタプリタを使ってスクリプトを評価
redis.call()とredis.pcall()の違いは、前者がエラーをraiseし、コマンド呼び出し元へエラーを返すためにEVALを強制実行するのに対して、後者は、エラーを捕捉しエラーを表すLuaテーブルを返す。
RedisとLuaの型の変換
Redis: integer <-> Lua: Number
Redis: bulk reply <-> Lua: string
Redis: multi bulk reply <-> Lua: table
Redis: status <-> Lua: statusを含む1つのokフィールドを持ったテーブル
Redis: error <-> Lua: statusを含む1つのoerrフィールドを持ったテーブル
Redis: Nil bulk, Nil bulk reply <-> false boolean type
Redis: 1 <- Lua: boolean true
Luaには数字の型はnumbersしかなく、Redisでは整数に変換されるので、小数を持つ時は文字列として変換する
Lua配列でnilを持つことは、セマンティスク上難しく、見つけた時は停止する。
Redisの型を返すヘルパー関数
redis.error_reply(error_string)
redis.status_reply(status_string)
スクリプトーの Atomicity
MULTI / EXECと似たセマンティクス
遅いスクリプトがあると、つっかえるので実行時は他のクライアントがコマンドを実行していないことを確認。
BandwidthとEVALSHA
Redisは、内部キャッシュ機構により、スクリプト実行毎にコンパイルする必要はないが、追加の帯域幅は最善ではないこともある。
特殊コマンドを使ったものやredis.conf経由のコマンドでは、インスタンスごとに異なる実装であったり、デプロイが難しかったり、完全なセマンティクスがよくわからないといった理由で問題になることがある。
EVALSHAでは、サーバがSHA1ダイジェストにマッチするスクリプトを覚えていたら実行し、なかったら、特殊なエラーでEVALを代わりに実行する。
Script cache semantics
スクリプトのキャッシュの削除は、SCRIPT FLUSH コマンド
Redisの再起動でもキャッシュは消える
クライアントは、Redisインスタンスが再起動していないことを接続が維持しているか、INFOコマンドのrunidフィールドを確認することで確認できるが、実用的には、管理者が上記コマンドを実行していないかの確認で十分。
スクリプトコマンド
SCRIPT FLUSH
SCRIPT EXISTS sha1 sha2 ... shaN
SCRIPT LOAD script
SCRIPT KILL
Scripts as pure functions, Replicating commands instead of scripts
スクリプトは、スレーブに対してレプリケーションする際に、各コマンドではなくスクリプトそのものをレプリケーションしていたが、必ずしも有効とは限らないため、3.2からは可能なようにした。
コマンドによるレプリケーションは、 redis.replicate_commands()
で有効にする
同じ引数で同じRedsi writeコマンドを評価しないといけないという制約。
実行結果を毎回同じにするために、以下のスクリプト中の math.randomseed(tonumber(ARGV[2]))
のようにシードを与えるテクニックがある。
Selective replication of commands
レプリケーション対象を redis.set_repl()
で制御できる。
redis.set_repl(redis.REPL_ALL) : デフォルト
redis.set_repl(redis.REPL_AOF)
redis.set_repl(redis.REPL_SLAVE)
redis.set_repl(redis.REPL_NONE)
Global variables protection
Redisスクリプトでは、Luaの状態へデータ流出を防ぐためにグローバル変数が使うことができない。
もし、呼び出し間で状態共有するなら、Redisのキーを代わりに使う。
Using SELECT inside scripts
Redis 2.8.11までは、Luaスクリプトによって選べれたデータベースは、呼び出しスクリプトへ現在のデータベースとして移動する。
Redis 2.8.12からはスクリプト内でしか変更されず、スクリプトを呼び出しているクライアントによってデータベースが修正されることはない。
Redisのレプリケーションレイヤーで互換性がないのでバグの原因となりうる。
Available libraries
以下のLuaライブラリが利用可能。
base lib.
table lib.
string lib.
math lib.
struct lib.
cjson lib.
cmsgpack lib.
bitop lib.
redis.sha1hex function.
redis.breakpoint and redis.debug(Redis Lua debugger)
以下の外部ライブラリも利用可能。
struct
CJSON
cmsgpack
bitop
bit.tobit
bit.tohex
bit.bnot
bit.band
bit.bor
bit.bxor
bit.lshift
bit.rshift
bit.arshift
bit.rol
bit.ror
bit.bswap
redis.sha1hex
Emitting Redis logs from scripts
Luaスクリプト中から redis.log(loglevel,message)
関数で Redisログファイルへ書き出し。
上記 loglevel は以下の値を設定
redis.LOG_DEBUG
redis.LOG_VERBOSE
redis.LOG_NOTICE
redis.LOG_WARNING
Sandbox and maximum execution time
スクリプトは、外部システムへはアクセスせず、タイムアウトがある。(デフォルト5秒)
redis.conf や CONFIG GET / CONFIG SET コマンドで、lua-time-limitの値を設定・確認。
タイムアウトが過ぎてもAtomicに矛盾が内容にするため、自動的には終了しない。
タイムアウトを過ぎると、他のクライアントからコマンドを受け付けるが、BUSYエラーでリプライを返す。SCRIPT KILLかSHUTDOWN NOSAVEコマンドしか受け付けない。
EVALSHA in the context of pipelining
パイプライニング中であってもEVALSHAを呼び出すと、コマンドの実行順序を保証するため、EVALSHAがNOSCRIPTエラーを返すと、後のコマンドは再実行されない。
以下の対処法
パイプライン中ではEVAL関数を常に使う
全コマンドをためて、パイプラインに流し、EVALコマンドの確認をして、SCRIPT EXISTSコマンドで全スクリプトが定義されているかを確認する。もし、全てが定義されていなければSCRIPT LOADコマンドで読み込み、EVALSHAかEVALコールを使う。
Debugging Lua scripts
Redis 3.2からネイティブのLuaデバッギングを利用できる。
URL: https://redis.io/topics/ldb
開発環境で利用し、本番環境では利用しないほうが良い。
synchronous debugging modeでは、Redis serverのブロッキングにつながるので注意(redis-cliで--ldb-sync-modeオプションでリモートデバッグ時)
Dynamic breakpoints
redis.breakpoint()
で可能。
Logging from scripts
redis.debug()で出力
引数が複数ある時はカンマ区切り
Inspecting the program state with print and eval
printコマンドで現在のコールフレームから変数の値参照
evalコマンドで現在のコールフレーム外からの変数の値参照
URL: https://redis.io/topics/memory-optimization
Special encoding of small aggregate data types
Redis2.2からは、多くのデータ型で一定サイズまではよりスペースが少なくなるように最適化されている。
HashやList、Setはただのinteger空鳴り、Sorted Setsの場合は、ある要素数、やサイズまでは、最大10倍メモリ効率の良い方法でエンコーディングしている。
上記挙動はAPIの観点から意識する必要がない。
redis.confで以下の変数の設定
hash-max-zipmap-entries 512 (hash-max-ziplist-entries for Redis >= 2.6)
hash-max-zipmap-value 64 (hash-max-ziplist-value for Redis >= 2.6)
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
set-max-intset-entries 512
Memory allocation
Redisはキーを削除しても杖にOSへメモリを解放しない。
インスタンスに5GBのデータがあり、2GB相当のデータを削除してもResident Set Size(RSSは、5GBのまま
Redisのユーザーメモリは3GBのまま
稼働しているアロケータがメモリをすぐに解消しないから。
ただし、メモリの自由なチャンクは使いまわす。
そのため、フラグメンテーション率の値はあてにならない。
maxmemoryを設定しないと、メモリ割り当てをどこまででも行なうので、設定することを推奨。
maxmemory-policyオプションもある。
URL: https://redis.io/commands/expire
EXPIRE key seconds
タイムアウトの値は、 DEL, SET, GETSET, *STORE 系コマンドで設定できる。
INCR, LPUSHやHSETコマンド等はタイムアウトの値は更新されない。
PERSISTでタイムアウトをクリア
EXPIRE/PEXPIREで0以下のタイムアウト
EXPIREAT/PEXPIREATで過去のタイムアウト
Expire accuracy
Redis2.4では秒以下での誤差。2.6ではミリ秒以下での誤差。
Expires and persistence
キー失効期限にはUnixタイムスタンプを使っているため、時間同期していないインスタンス間でRDBファイルを移動すると、おかしなことになる。
How Redis expires keys
passiveとactiveな方法の2種類ある。
passiveな方法: クライアントがアクセスした際にタイムアウトだと分かった時
activeな方法: Redisは定期的にキーの中からランダムに幾つかのキーをテストし、expire setかテストする。失効していたら、キー空間から削除する。
How expires are handled in the replication link and AOF file
キー失効時、AOFファイルや全スレーブにはDELオペレーションが同期される。
URL: https://redis.io/topics/lru-cache
Eviction policies
maxmemory-policy で以下の種類のものを定義
noeviction
allkeys-lru
volatile-lru
allkeys-random
volatile-random
volatile-ttl
volatile-*は、evict対象するキーがないとき、noevictionのように振る舞う。
Approximated LRU algorithm
RedisのLRUは、メモリをたくさん使うので、厳密ではない。
いくつかのキーをサンプルとして取ってきて、最も良いものをevictの対象とする。
Redis3.0からはmaxmemory-samplesで値を定義できるようになった。
The new LFU mode
Redis4.0からはLFU(Least Frequently Used) eviction modeが以下のパラメータで使えるようになった。
volatile-lfu
allkeys-lfu
Approximate counting algorithmに従ってカウント。
lfu-log-factor, lfu-decay-time でチューニングも可能。
REFERENCES
Approximate counting algorithm: https://en.wikipedia.org/wiki/Approximate_counting_algorithm
URL: https://redis.io/topics/transactions
Transactions
Redisでのトランザクションは、MULTI, EXEC, DISCARD, WATCHコマンドが基本
ロールバックには対応していない。
文法ミス時しかコマンドは失敗しないから
内部的に簡素で速くした結果、機能を持たない。
Optimistic locking using check-and-set
WATCHコマンドを変数に対して行なうと、楽観的ロックで、check-and-set (CAS) の挙動をする。
EXECを条件付きで行なう。
Redis scripting and transactions
Redis2.6でスクリプトが導入され、機能が重複しているので、こっちを使ったほうが良さそう。
URL: https://redis.io/topics/mass-insert
Use the protocol, Luke
以下のように実行する。
スクリプトも利用できる。
Redis2.6より前は以下のようにして実行できたが、Netcatはデータが全部送られるとかは確認しない。
URL: https://redis.io/topics/partitioning
Partitioning basics
range partitioning: オブジェクトのレンジをインスタンスへマッピング。別途テーブルを持つデメリット。
hash partitioning
consistent hashing: hash partitioningの発展版
Different implementations of partitioning
どこでパーティショニングを行なうか
Client side partitioning
Proxy assisted partitioning : (ex) Twemproxy(RedisやMemcachedのプロキシ)
Query routing : ランダムなインスタンスへクエリーを送り、そこから適切なノードへ転送する。 ex. Redisクラスターは、クライアントとハイブリッドな形で行われ、実際にリダイレクトするのはクライアント。
Disadvantages of partitioning
以下の操作を伴うときには不向き
複数のキーを伴う操作、トランザクション
パーティショニングの粒度がキー
データ処理が複雑な時
キャパシティの追加、削除
Data store or cache?
データストアとして利用するときは、keys-to-nodes mapは固定でないといけないので、ノード数は固定でないといけない。
キャッシュ利用時は、consistent hashingで簡単にスケールアップ/ダウンができる。
Redisクラスターは、ノード数変化のキーのリバランスができる。
Presharding
ノードの追加、削除は難しいので、最初からたくさんノードを用意しておけばいい。
Implementations of Redis partitioning
Redis Cluster
Twemproxy : 複数プロキシを利用できるのでSPOFにはならない。
consistent hasing対応クライアント
References
twemproxy (nutcracker) : https://github.com/twitter/twemproxy
[release] Redis 3.0.0 is out. : https://groups.google.com/forum/#!msg/redis-db/dO0bFyD_THQ/Uoo2GjIx6qgJ
Twemproxy, a Redis proxy from Twitter: http://antirez.com/news/44
redis-rb: https://github.com/redis/redis-rb
Predis: https://github.com/nrk/predis
Clients: https://redis.io/clients 言語別Redis対応クライアント ★5
URL: https://redis.io/topics/distlock
Distributed locks with Redis
異なるプロセスが相互排他的に共有リソースにアクセスする時に分散ロックは便利
実装
Redlock-rb (Ruby implementation).
Redlock-py (Python implementation).
Aioredlock (Asyncio Python implementation).
Redlock-php (PHP implementation).
PHPRedisMutex (further PHP implementation)
cheprasov/php-redis-lock (PHP library for locks)
Redsync.go (Go implementation).
Redisson (Java implementation).
Redis::DistLock (Perl implementation).
Redlock-cpp (C++ implementation).
Redlock-cs (C#/.NET implementation).
RedLock.net (C#/.NET implementation). Includes async and lock extension support.
ScarletLock (C# .NET implementation with configurable datastore)
node-redlock (NodeJS implementation). Includes support for lock extension.
Safety and Liveness guarantees
安全性
生存特性A: デッドロックフリー
生存特性B: フォールトトレランス
Why failover-based implementations are not enough, Correct implementation with a single instance
リソースがないか確認してから値をセットすると、フェイルオーバー時も大丈夫
Luaスクリプトでは以下のように実装する。
DELは安全ではないので、各ロックにランダム文字列で署名をつける
/dev/urandomから20バイトの文字列
/dev/urandomでRC4のシード
ミリ秒のunix timeとクライアントIDの連結
The Redlock algorithm
レプリケーションはなく、マスターが複数ある
アルゴリズム
現在の時間をミリ秒で取得
全インスタンスで同じキー名のランダムの値を使って、順番にロックを取得を試みる。
ロック設定時にtotal lock auto-release timeと比較するためにタイムアウトを使う。
大半のインスタンスでロックを取得でき、全体の経過時間がlock validity timeより短ければ、ロック取得できたとみなす。
Liveness arguments
以下の3つの情報を元にsystem livenessを判断
ロックの自動リリース
ロック未取得時、もしくは作業集竜王子にロックを除去するか
ロックのリトライ時、大半のロックの取得より大きい時間を空けるか
Making the algorithm more reliable: Extending the lock
デフォルトでは、小さいlock validity timesを使用できるが、Luaスクリプトを奥襟、全他インスタンスでキーのTTLの延長を行なうことができる。
ロックの再取得だけを考慮すればいい。試行回数の最大は制限しておくべき。
References
How to do distributed locking - Martin Kleppmann's blog: http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
Is Redlock safe?: http://antirez.com/news/101
URL: https://redis.io/topics/notifications
キースペース通知は、Pub/Subチャンネルを、ある方法でRedisのデータセットに影響を与えるイベントを受信するように購読するようにできる。
設定は、redis.confかCONFIG SETコマンドでnotify-keyspace-eventsを使う。以下の値を設定。
全許可
URL: https://redis.io/topics/indexes
Turning multi dimensional data into linear data
Redis geo indexing APIはGeo hashを使って軽度と緯度からインデックスの場所へSorted setを使っている。
Limits of the score
Sorted set要素のスコアは double precision integersなので、-9007199254740992 and 9007199254740992(-/+ 2^53)までしか扱うことができない。
それより大きい数は、lexicographicalインデックスと呼ばれる インデックスで可能。
Lexicographical indexes
Sorted setで同じスコアのデータを比較するとき、memcmp()関数でバイナリデータとして文字列を比較して、辞書的にソートされる。
ZRANGEBYLEX and ZLEXCOUNTコマンドは事象的にレンジをクエリーしたりカウントする
基本的にb-treeデータ構造に等しい。
Normalizing strings for case and accents
大文字、小文字等のブレを標準化するためには、term:frequencyの形式ではなく、normalized:frequency:originalの形式で保存する。
Using numbers in binary form
小数の数の保存にはメモリをたくさん使うため、例えば128bitのバイナリとして保存する方法がある。
ビッグエンディアンのフォーマットで保存する。Redisがmemcmp()関数で文字列を比較することによる。
Updating lexicographical indexes
インデックスのリビルド等は遅くて大変なので、メモリをうかうか割にハッシュのオブジェクトIDと現在のインデックスの値をハッシュのマッピングをする方法がある。
Representing and querying graphs using an hexastore
関係性を表すには、以下のように登録すれば良い
検索するなら、ZRANGEBYLEXが使える。\xffは末尾に255という値をセットしていることを表すもの。
Multi dimensional indexes
多次元インデックスを表し、それぞれの次元で範囲指定したいとき
多次元のインデックスを表すために、数字をinterleaveする。
x,y座標の数字を表す時
それぞれ以下のようにx,yのbitを交互に並べる。
Rubyのコードの実装例
Multi dimensional indexes with negative or floating point numbers
マイナスの値を表すには、オフセットの値を受かってunsigned integersで表す。
浮動小数点を表すには、点の後の保持したい数をべき乗の値に掛け合わせる
Non range indexes
SetsやListsでもインデックスを実現でき、よく使われるが、常に実現できるものではない。
Setデータで、SRANDMEMBERを使う時、プロパティがあるかないかのチェックは必要。
Listデータで、追加するとき、RPOPLPUSHで順番の並び替えが必要。RSSの用途には良いかも
capped listでインデックスが可能。LPUSHで追加して、LTRIMでトリミング
Index inconsistency
インデックスを数ヶ月、数年間更新したままにするのは難しい
リクエストされたときにデータを修正する方法
SCAN系コマンドで、増分的にスクラッチでインデックスの確認、更新、リビルドを行う方法
Redis 4.0からモジュールプログラミングの機能追加
URL: https://redis.io/topics/modules-intro
Loading modules
ローディングされたモジュール
アンロード
The simplest module you can write
Module initialization
新規コマンド作成のためのプロトタイプ
Setup and dependencies of a Redis module
新規 Redis モジュール作成のために、redismodule.h をコピーしてくる
Passing configuration parameters to Redis modules
Working with RedisModuleString objects
Calling Redis commands
Working with RedisModuleCallReply objects.
Releasing call reply objects : RedisModule_FreeCallReply
エラーを返す: RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err);
Arity and type checks
Low level access to keys
Getting the key type
Creating new keys
Deleting keys: RedisModule_DeleteKey(key);
Managing key expires (TTLs)
Obtaining the length of values: size_t len = RedisModule_ValueLength(key);
String type API: int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str);
既存の文字列へのアクセスは、DMA (direct memory access)を使用
List type API
Set type API
Sorted set type API
RedisModule_ZsetAdd
RedisModule_ZsetIncrby
RedisModule_ZsetScore
RedisModule_ZsetRem
Sorted Set のイテレータ
RedisModule_ZsetRangeStop
RedisModule_ZsetFirstInScoreRange
RedisModule_ZsetLastInScoreRange
RedisModule_ZsetFirstInLexRange
RedisModule_ZsetLastInLexRange
RedisModule_ZsetRangeCurrentElement
RedisModule_ZsetRangeNext
RedisModule_ZsetRangePrev
RedisModule_ZsetRangeEndReached
Hash type API
RedisModule_HashSet
RedisModule_HashGet
Replicating commands
影響がないか確認
どのコマンドをレプリケーションするべきか指定可能
Automatic memory management : RedisModule_AutoMemory(ctx);
以下を自動でメモリ管理
キーのクローズ
リプライの解放
RedisModuleString オブジェクトの解放
Allocating memory into modules
Pool allocator
Writing commands compatible with Redis Cluster
module.c の実装を参考にコマンドを書く。
URL: https://redis.io/topics/modules-native-types
Redis の元々ある型は、/modules/hellotype.c
で見ることができる。
Registering a new data type
rdb_load, rdb_save, aof_rewrite, digest, free, mem_usage は、全て以下のプロトタイプのコールバック関数
Ok, but why modules types require a 9 characters name?
RDB ファイルは、以下のようなキーバリューのペアの列
[1 byte type] [key] [a type specific value]
Setting and getting keys
RedisModule_OnLoad()関数で新たな型を登録後、以下のように実装
鍵が空かといったチェックも必要
鍵の型が正しいことを確認できた場合、以下のように処理
Free method
実装例
RDB load and save methods
以下のような独自の型の場合
以下のようにラッパー関数を実装
AOF rewriting : void RedisModule_EmitAOF(RedisModuleIO *io, const char *cmdname, const char *fmt, ...);
Handling multiple encodings
Allocating memory : RedisModule_Alloc()関数
malloc() のようなライブラリを使っている場合、以下の様なマクロを定義して置き換える方法もある。
URL: https://redis.io/topics/modules-blocking-ops
Blocking commands in Redis modules
コマンドでは、BLPOP, BRPOP
実験的な API で、REDISMODULE_EXPERIMENTAL_API を定義している場合に使うことが出来る
How blocking and resuming works.
以下の関数で、クライアントはブロック状態/アンブロック状態
コマンド実装例
クライアントへ何を返すべきか分かるように、threadmain 関数を以下のように改良
Aborting the blocking of a client
int RedisModule_AbortBlock(RedisModuleBlockedClient *bc);
以下のようにして使う。この場合、スレッドを作成しようとしてもエラーを返す。
Implementing the command, reply and timeout callback using a single function
実装例
References
Neural Redis: https://github.com/antirez/neural-redis
URL: https://redis.io/topics/modules-api-ref
Redis モジュール API リファレンス ★5
URL: https://redis.io/topics/data-types-intro
Redis keys
キーが長すぎる(1024バイト等)のは、メモリ効率や検索のコスト的にも良くない。SHA1等でハッシュ化するのも良い。
キーが短すぎるのも読みにくさが悪ければ良くない。
スキーマに遵守してみる
キーの最大サイズは512MB
Redis Strings
最大512MB
SET コマンドで既にキーが存在している場合は失敗にする
INCR, INCRBY, DECR, DECRBYはレースコンディションの心配
GETSETを使えば、新たな値を設定し、古いキーを返す
MSETやMGETで一気に値の取得、設定をできる。
Altering and querying the key space
EXISTSやDEL
TYPEコマンドでも確認できる。
Redis Lists
RedisのListsはLinked list
大きい要素の集合の真ん中のデータに速くアクセスするときが大事。場合によっては他のデータ構造を検討する。
Blocking operations on lists
LPUSHやRPOPでもしリストが空だったら待機してリトライをするというポーリングの挙動をする。
BRPOPやBLPOPやリストが空の場合、ブロックし、設定したタイムアウト後利用可能になる。
RPOPLPUSHやBRPOPLPUSHと言ったコマンドもある。
Bitmaps
空間効率が良くて高速
最大512MBまでで、2^32の異なるbitまでの値に適している
SETBITとGETBITでデータの取得、設定
操作は、BITOP,BITCOUNT,BITPOSで行なう。
HyperLogLogs
確率的なデータ構造で、ユニークな物の数を概算する。
正確さの代わりにメモリ効率がいい。
PFADDとPFCOUNTを使う。
URL: https://redis.io/topics/twitter-clone
ログイン処理の実装
ログイン時の実装
ツイート投稿時
ZRANGEBYLDEXのデモ
URL: http://autocomplete.redis.io/
URL: https://redis.io/topics/data-types
Data types
Strings : 文字列型の長さは最大で 512MB, バイナリセーフ(どんなデータも含めることができる ex. JPEG画像、シリアライズされた Rubyオブジェクトなど)
Lists : 最大長 2^32 - 1(4294967295 約4億), O(N)
Sets : 最大長 2^32 - 1(4294967295 約4億), O(1)
Hashes : 最大長 2^32 - 1(4294967295 約4億)
Sorted sets
Bitmaps and HyperLogLogs
URL: https://redis.io/topics/faq
Redisのメモリ使用について
redis-benchmarkを使ってランダムデータセットを生成し、INFO memoryコマンドでメモリ状況を確認
64bitシステムは32bitシステムより同じキーでもメモリを使うが、少なくとも大きなサーバでは64bitの方がいい。もしくはシャーディング。
Redisがメモリを使い切ったときの挙動
LinuxカーネルのOOMキラーに殺されるか、エラーでクラッシュするか、遅くなり始める。
最近のOSのmalloc()でNULLを返すものは珍しいので、大抵はサーバはスワッピングし始めてデグレードする。
Background saving fails with a fork() error under Linux even if I have a lot of free RAM!
echo 1 > /proc/sys/vm/overcommit_memory すれば良い。
Redisのスキーマを保存するバックグラウンドは、forkのcopy-on-writeセマンティクスに依存しており、Redis forkは正確に親のコピーとなっている、子プロセスがディスク上にdumpを取る。
copy-on-writeセマンティクスのために、親プロセスと子プロセスは、一部領域を共有しており、もし内容に変更があるとこちらの領域が重複して、多めにメモリを使う。
On-disk-snapshotsはAtomic
Redis4.0からバックグラウンドでのオブジェクトのDelete処理はマルチスレッドに対応しているが、それ以外はシングルスレッド。そもそもRedisでそんなにCPUがボトルネックになることは少なく、メモリやネットワークがたいてい問題になる。
Redisは最大2^32個のキーまで持つことができて、1インスタンス辺り、2億5千万のキーまで持つことが出来る。
スレーブとマスターのキーの数が違うことがある
マスターはRDBファイルを生成して、スレーブと同期を取るが、RDBファイルには、マスターで既に失効したキーは含まれないが、メモリにはまだいる。そして後にメモリはreclaimされる。
INFOコマンドやDBSIZEで確認
RedisはREmote DIctionary Serverの略
Redisは元々LLOOGG(https://github.com/antirez/lloogg )をスケールさせるために始められた
URL: https://redis.io/topics/rediscli
コマンドライン使用方法
他のプログラムから実行する方法
stdinで取り込む方法
テキストを展開してパイプでredis-cliで渡す方法
同じコマンドを連続して実行
キーの値の変化やINFOコマンドの内容を確認したいときなどに便利
-r and -i オプション
無限に繰り返す時は -1 オプション
CSV 出力 : --csv オプション
Lua スクリプトを実行 : --eval オプション
インタラクティブモード
接続、再接続の処理
CONNECT hostname port で接続
切断が検知された場合は自動で復旧
編集、履歴、補完
linenoise line editing library(https://github.com/antirez/linenoise )を使用
同じコマンドを繰り返し実行
help コマンド
カテゴリ種類: @generic, @list, @set, @sorted_set, @hash, @pubsub, @transactions, @connection, @server, @scripting, @hyperloglog.
ターミナルスクリーンのクリア : clear コマンド
操作の特殊モード
継続的なstatsモード
-i オプションでインターバルを指定
サイズの大きいキーのスキャン
キーリストの一覧
--scan オプションを使用
--pattern '-11' のようにパターン検索も可能
Pub/Subモード
Redis で実行されたコマンド監視
レイテンシー監視
最大や平均を調べるには、--latency-history オプション
-i オプションでインターバルを指定できる
--latency-dist オプションで異なるASCII文字を有効化
--intrinsic-latency オプションで本来のRedisのレイテンシー計測(Redisサーバ上での実行が必要)
RDBファイルのリモートバックアップ
スレーブモード : --slave オプション
LRU シミュレーションを実行
--lru-test オプション
テストでのキーの総数の指定とmaxmemoryの指定が必要(maxmemoryを指定しないと全キーをメモリに載せるので100%使い切る可能性)
URL: https://redis.io/topics/config
Redis コンフィギュレーション
redis.conf に以下のように設定 : keyword argument1 argument2 ... argumentN
文字列に空白を含めたい場合は、以下のサンプルのようにクォートで囲めばよい。
設定ファイル
Redis 4.0: https://raw.githubusercontent.com/antirez/redis/4.0/redis.conf
Redis 3.2: https://raw.githubusercontent.com/antirez/redis/3.2/redis.conf
Redis 3.0: https://raw.githubusercontent.com/antirez/redis/3.0/redis.conf
Redis 2.8: https://raw.githubusercontent.com/antirez/redis/2.8/redis.conf
Redis 2.6: https://raw.githubusercontent.com/antirez/redis/2.6/redis.conf
Redis 2.4: https://raw.githubusercontent.com/antirez/redis/2.4/redis.conf
コマンドラインから引数を渡す
コマンドラインから渡す引数のフォーマットは、頭に -- が必要な点を除けば redis.conf ファイルと同じフォーマットとなっている。
サーバー動作中の Redis コンフィグレーションの変更
CONFIG SETやCONFIG GET
再起動で設定内容は消える。
Redis 2.8からの CONFIG REWRITE コマンドでredis.confと設定内容が異なる部分を書き換える
項目が存在せずデフォルト値のままになっているものは追加しない。設定ファイル内のコメントは保持される。
Redis をキャッシュとして設定する
もし Redis を単純なキャッシュ用途で設計しており、すべてのキーに TTL を設定する場合には、その代わりに以下のような設定を利用してもよい。 (この例では最大メモリを 2MB としている)
URL: https://redis.io/topics/replication
Replication
Redisのレプリケーションは非同期
Redis4.0から全てのスレーブは同じ状態
レプリケーション時は persistence を off にしておく。
もし、off にできない場合は、再起動を自動でしないようにしておく。
そうでないと、マスターが再起動してデータが消えると、スレーブまで消えて危険。
Cf. Redis Sentinel
レプリケーションの際には、"Replication ID, offset" の情報が必要
スレーブがマスターに再接続する際には、 PSYNC コマンド
SYNC コマンドは古く新しいRedisインスタンスには使われていないが、互換性のためにある。
ディスクレスレプリケーション
full resynchronizationはディスク上にRDBファイルを保存して、それを読み込んでスレーブへデータを送っていた
Redis 2.8.18 からは子プロセスが、RDBファイルを直接スレーブへ送るようになった
設定
slaveof 192.168.1.1 6379 コマンドのようにして有効
パラメータ repl-diskless-sync と repl-diskless-sync-delay
Read-only スレーブ
Redis 2.6からslave-read-onlyオプションで有効
rename-commandコマンドでコマンドを無効にすることでセキュリティの向上
マスターを認証するためのスレーブの設定
redis-cli で config set masterauth
設定ファイルで masterauth
Redis2.8からマスターにアタッチされるスレーブの最低台数を指定できるようになった
以下のパラメータ
min-slaves-to-write
min-slaves-max-lag
Redisのスレーブがマスターへ毎秒pingを送り、マスターは、最後にpingを受けた時間を記録しておく。
失効したキーのレプリケーションの扱い
マスターがキーが失効したときは、スレーブへDELコマンドが送られる
master-driven 失効の時は、スレーブの内部クロックに従って削除される
Luaスクリプト実行中は、キーは失効せず、スクリプトはスレーブへも送られる
DockerやNATでのレプリケーション設定
IPアドレスが変わってしまうので以下のように設定
slave-announce-ip 5.5.5.5
slave-announce-port 1234
レプリケーションの状態確認
INFO replication
ROLE : マスターやスレーブの状態、オフセット、接続されているスレーブの一覧等
再起動やフェイルオーバー後の部分的な再同期
Redis 4.0から利用可能
SHUTDOWN コマンドでRDBファイルを保存して終了する。upgradeの際に便利
URL: https://redis.io/topics/persistence
AOF や RDB で永続化。AOF単体は推奨せず、今後は2つは1つの永続化モデルへ統合予定。
ログの書き換え
Redis2.4から BGREWRITEAOF コマンドでバックグランドでAOFについて、複数処理をまとめたりといった再構築をすることができるようになった
redis-check-aof --fix
で破損ファイルの修正
diff -u
コマンドで2つのファイルの比較
DR にあまりお金をかけられない場合もあるかもしれないが、以下の方法でコストが抑えられる。
S3 に gpg -c
コマンドで暗号化して RDB スナップショットの保存
小さい VPS へ SCP でスナップショットを転送
URL: https://redis.io/topics/admin
Redis セットアップのヒント
Redis のデプロイにあたっては、Linuxオペレーティングシステムを推奨する。Redis は OS X でも十分にテストされているし、FreeBSD や OpenBSD でもテストに時間をかけている。しかしながら主だったストレステストはすべて Linux 上で行っているし、多くのプロダクション環境は Linux で動いている
Linux カーネルの overcommit memory 設定は 1 とするように。この設定を反映するには、vm.overcommit_memory = 1 を /etc/sysctl.conf に追記し、sysctl vm.overcommit_memory=1 を実行するか再起動する。
メモリ利用量およびレイテンシの観点で悪影響があるので、Linux カーネルの transparent huge pages 機能は無効化する。以下のコマンドを実行して無効化すること: echo never > /sys/kernel/mm/transparent_hugepage/enabled
幾らかのスワップ領域をセットアップすること(メモリ領域と同じだけのスワップ領域をお勧めする)。Linux環境でスワップ領域がないのに Redis がとても多くのメモリを必要とした場合、Redis が Out of memory でクラッシュするか、Linux カーネルの OOM killer によってプロセスは強制終了されるだろう。
正しい maxmemoryパラメータを設定することで、メモリが足りなくなった時にエラーを通知させる。
もし非常に書き込みが多いアプリケーションで Redis を利用している場合、RDBファイルへの書き込みや AOFログの再構成は 最大で利用メモリの 2倍のメモリが必要になるケースがある。このとき追加で必要になるメモリは、ファイルへの保存中に書き込みリクエストが行われたときに変更されたメモリ領域に比例する。つまり、書き込みが行われたキー(あるいは集約されたアイテム)数に比例する。この点を考慮してメモリ領域をサイジングする必要がある。
daemontools を利用しているときは daemonize no を設定する。
永続化(persistent)を無効化している場合であっても、レプリケーションを利用しているのであれば Redis は RDBファイルへの書き込みを行う。新しいディスクレスのレプリケーションを利用する場合はこの限りではないが、現時点でこの機能は実験的なものとして実装されている。
レプリケーションを利用している場合、マスターで永続化を有効化するか、クラッシュ時などに自動的に再起動しないようにしておく。スレーブはマスターの正確なコピーなので、もしマスターが再起動してメモリ上のデータが空になってしまった場合、スレーブも同じ状態になりデータが消えてしまう。
デフォルトで Redis は認証機能を持たず、すべてのネットワークインターフェイスをリッスンする。Redis がインターネットや任意の攻撃者からアクセス可能である場合、これは大きなセキュリティリスクになる。どれくらい危険かは、例 this attack を見てほしい。そのほか、Redis をセキュアにするための情報として security page や quick start を参照すること。
EC2 上で Redis を稼働させる
PV ベースではなく、HVM ベースのインスタンスを利用する。
古いインスタンスファミリーを使わないこと。例えば PV の m1.medium ではなく、HVM で m3.medium を使う。
Redis の永続化は EC2 EBS ボリューム で利用するが、EBS ボリュームはときどきレイテンシが高くなることがあるので、その点には注意すること。
もしマスターとスレーブの同期に何らかの問題があった場合には、新しいディスクレスレプリケーションを試してもよい
ダウンタイムなしで Redis インスタンスのアップグレードや再起動を行う
Redis は非常に長い期間、プロセスとして稼働できるよう設計されている。利便性のため、多くの設定値は再起動なしで CONFIG SET command によって適用することができる。
Redis 2.2 からは、AOF から RDB スナップショット永続化への切り替えなどといった変更も、再起動なしで行うことができる。CONFIG GET * コマンドの結果も確認してほしい。
しかしながら、再起動は時折ではあるものの定期的に行う必要が出てくる。それは Redis を新しいバージョンにアップグレードするためであったり、CONFIG コマンドでサポートされない設定の変更が必要になる場合などである。
以下の手順は、ダウンタイムを回避するための非常に一般的なものである。
新しい Redis 用のインスタンスを立ち上げ、現在稼働している Redis のスレーブにする。たいてい別のサーバーを立ち上げることになると思うが、十分なメモリ領域があるならば同じサーバー内で 2つの Redis を起動してもよい。
もし単一のサーバーを使っているのであれば、マスターとは異なるポートを設定してスレーブを起動すること。そうでなければマスターのポート設定と干渉するので起動できない。
初回のレプリケーションの同期が完了するまで待つ(スレーブ側のログファイルを確認する)。
INFO コマンドで、マスターと同じだけのキーがスレーブにあることを確認する。redis-cli でスレーブが意図した通りに動作していること、コマンドに応答することを確認する。
CONFIG SET slave-read-only noでスレーブへの書き込みができるようにする。
すべてのクライアントが新しい Redis を利用するように変更する(それはつまりスレーブ)。
マスターがクエリを受け取らなくなったことを確認したら(MONITOR commandで確認できる)、SLAVEOF NO ONEコマンドでスレーブをマスターに昇格させ、それまでのマスターを停止する。
もしRedis Sentinel あるいは Redis Clusterを利用している場合、最も簡単にバージョンのアップグレードを行う方法はスレーブを順次アップグレードしていき、手動のフェイルオーバによってマスターをアップグレードされたものに切り替え、最終的に残りのスレーブもすべてアップグレードさせていくという方法になる。
注意すべき点として、Redis クラスター 4.0 と Redis クラスター 3.2 はクラスターバスのプロトコルレベルで互換性がないので、この場合は全台の再起動が必要になるということだ。
URL: https://redis.io/topics/security
一般的な Redis のセキュリティモデル
Redis はセキュリティを高める目的では最適化されておらず、専らパフォーマンスと単純化に比重を置いている。
ネットワークセキュリティ
redis.confファイルに以下のような bind の設定を含めることで、単一のインターフェイスのみ利用するよう設定することが可能だ。
bind 127.0.0.1
保護モード
バージョン 3.2.0 からはデフォルト設定で起動したとき(すべてのインターフェイスをバインドした場合)、かつパスワードを設定していないのであれば、特別な保護モードになる。
このモードでは Redis はループバックインターフェイスからのクエリにのみ応答し、外部からのリクエストにはエラーを返す。
認証の失敗
Redis ではアクセスコントロールを実装していませんが、redis.confファイルを編集することで認証のレイヤを設けることができます。
この認証レイヤが有効化されている場合、Redis は未認証のクライアントからのクエリを拒否します。クライアントは AUTHコマンドとパスワードによって認証を行うことができます。
AUTHコマンドは他のコマンドと同様に平文で送信されるため、ネットワークレイヤで盗聴が可能な攻撃者に対しては有効な防御ではありません。
データ暗号化のサポート
Redis は暗号化をサポートしません。信頼される組織だけがインターネットなどを経由してアクセスできるように実装したい場合、SSLプロキシなど追加のレイヤーを実装して防護すべきです。spiped をお勧めします。
特定のコマンドを無効化する
Redis では特定のコマンドを無効化したり、推測が難しい名前にリネームすることができます。これによって、クライアントから一部のコマンドだけしか使えないようにする、といったことも可能です。
リネーム: rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
無効化 : rename-command CONFIG ""
外部からの不正な入力による攻撃
Redis はハッシュ関数において実行毎に疑似ランダムなシード値を用いています。
Redis は SORTコマンドを qsort アルゴリズムで実装しています。現時点で、このアルゴリズムはランダマイズされておらず、そのためインプットを注意深く選択することで、計算量は最悪ケースで二次関数的に増大するでしょう。
エスケープと NoSQLインジェクション
Redis のプロトコルは文字列のエスケーピングを想定しておらず、通常のクライアントライブラリではインジェクションは不可能です。このプロトコルは固定長の文字列を使っていて、完全にバイナリセーフです。
References
Spiped: http://www.tarsnap.com/spiped.html
A few things about Redis security: http://antirez.com/news/96
URL: https://redis.io/topics/encryption
Spiped が有効なケースが多い
URL: https://redis.io/topics/signals
SIGTERM の処理
SHUTDOWN コマンドと似たような処理
Lua スクリプトによってブロックされている場合は時間がかかることがあるので、SCRIPT KILL
でスクリプトを終了できる。
RDB ファイルの保存に失敗した時は、Redisサーバは稼働し続ける。
Redis 2.6.11以降は、新たに SIGTERM や SHUTDOWN がない限り、シャットダウンしようとしない
Redis のクラッシュ
以下のシグナルの場合
SIGSEGV
SIGBUS
SIGFPE
SIGILL
AOF ファイル書き換えを実行している子プロセスが殺された時は AOF ファイルが捨てられ、再度トリガーされる。
Redis 2.6.10 以降、SIGUSR1
コマンドで、エラーなしに、子プロセスを殺すことができる。
URL: https://redis.io/topics/clients
コネクションの確立について
新規のコネクションが受け入れられた場合、以下のようなオペレーションが実行される。
Redis は多重化(multiplexing)およびノンブロッキング I/O であるため、クライアントのソケットがノンブロッキングのステートとなる。
読み込み可能なファイルイベントが作成され、ソケットで新しいデータが受信され次第、Redis がクエリを受け取ることができる。 クライアントが初期化されたのち、Redis は並行して扱えるクライアント数が上限に達していないかどうかを確認する(次のセクションで説明するが、これは maxclientsとして設定される)。
その時点で接続しているクライアントの数が最大に達していて接続を受け入れできない場合、Redis はクライアントにその旨のエラーを返し、即座にコネクションを閉じる。
クライアントの順序について
順序はクライアントソケットファイルのデスクリプター番号と、カーネルがレポートするイベントの並びによって決まる。つまり順序は不定であることを考慮しなくてはならない。
クライアントソケットから新しく読みだすたびに、単一の read()システムコールを行う。もし複数のクライアントが接続していて、そのうちの幾つかが高い頻度でクエリしている場合にも、他のクライアントが影響を受けたりレイテンシが悪化するといったことはない。
しかし、新しいデータが読みだされたとき、その時点でバッファ上にあるデータはシーケンシャルに実行される。これによって局所性が向上し、改めてプロセス時間が必要なクライアントの存在を確認する必要がなくなる。
クライアントの最大数
Redis 2.4 では並列で処理できるクライアントの最大数はハードコードされていた。
Redis 2.6 では、この上限値は動的になった。デフォルトは 10000クライアントであり、Redis.conf に maxclients が設定されていれば、その値が使われる。
しかしながら、Redis はカーネル上でオープン可能なファイルデスクリプターの最大数を確認する(ソフトリミットが確認される)。もし Redis で処理したい最大クライアント数より、このリミット(内部的に Redis が利用するファイルデスクリプター数の 32 を考慮した上で)が小さかった場合、Redis は現在の OS上で実際に扱える値を上限値として設定する。
以下のようなコマンドで現在のセッションおよびシステム単位の設定を変更することができます。
アウトプットバッファの上限
ハードリミット、ソフトリミット
ハードリミットは固定値で、この値に到達した時点でコネクションは即座に閉じられます。
ソフトリミットは時間によって計算され、例えば 10秒あたり 32MB であれば、アウトプットバッファが 32MB を 10秒間超えていたときにコネクションが閉じられる。
クライアントによって異なるデフォルトリミットが設けられている。
通常のクライアントは、デフォルトリミットが 0 であり、つまりリミットは存在しない。ほとんどの一般的なクライアントはブロッキングされる実装、つまり 1つのコマンドを送ったら応答を待ってから次のコマンドを送るようになっている。したがって、このようなクライアントであればコネクションを閉じる必要はない。
Pub/Subクライアントはデフォルトでハードリミットが 32MB、ソフトリミットが 60秒あたり 8MB となっている。
スレーブはデフォルトでハードリミットが 256MB、ソフトリミットが 60秒あたり 64MB となっている。
クエリ―バッファのハードリミット: 1GB
クライアントのタイムアウト
直近のバージョンのデフォルトでは、Redis はコネクションを閉じることは無い。長期間アイドル状態であってもコネクションは永続的に保持される。
redis.confもしくは単純に CONFIG SET timeout <value>
で変更できる。
このタイムアウトは通常のクライアントのみに適用され、Pub/Subクライアントには適用されない。なぜならば Pub/Sub に関するコネクションは プッシュ型 であるからだ。
クライアントのコマンド: CLIENT LIST
コマンド
TCPキープアライブ
直近のバージョン(3.2以降)では、デフォルトで TCPキープアライブ(SO_KEEPALIVEソケットオプション)が 300秒で有効化されている。このオプションは死んだピアを検出することに役立つ
URL: https://redis.io/topics/sentinel
Redis Sentinel Documentation
高可用性 を提供
機能
監視
通知
自動フェイルオーバー
設定プロバイダー
Sentinel の実行
デフォルトで TCP の26379番ポートで稼働
最低3台のSentinelインスタンスが必要で、それぞれ失敗条件が独立している必要あり(異なる AZ 等)
Sentinel の設定 : sentinel.conf, SENTINEL SET
コマンド
設定例
sentinel monitor
quorum とはマスターが到達不能になったときに同意する Sentinel の数
sentinel <option_name> <master_name> <option_value>
down-after-milliseconds
parallel-syncs
Docker や NAT を使った場合の問題
Sentinel auto-discovery は、IP アドレスとポートのマッピングの対応関係が分からなくなるため作用しない
ただし、Dockerでフォワーディングされたポートやポートがリマップされた NATでは1:1で対応関係がある場合のみ、以下のように対応関係を設定することで利用可能
sentinel announce-ip
sentinel announce-port
マスターの INFO コマンド中のスレーブのリスト
クイックチュートリアル
マスターの状態について Sentinel に聞く
その他以下の様なコマンド
現在のマスターの IP アドレス取得
フェイルオーバーをテスト
以下を実行後、現在のマスターの IP アドレス取得して確認する
Sentinel API
Sentinel コマンド
PING
SENTINEL masters
SENTINEL master
SENTINEL slaves
SENTINEL sentinels
SENTINEL get-master-addr-by-name
SENTINEL reset
SENTINEL failover
SENTINEL ckquorum
SENTINEL flushconfig
ランタイムで Sentinel を再設定
SENTINEL MONITOR
SENTINEL REMOVE
SENTINEL SET
Pub/Sub Messages
SUBSCRIBE, PSUBSCRIBE (PUBLISH は使うことが出来ない)
フォーマット: @
Sentinel と Redis の認証
マスターの requirepass, スレーブの masterauth
requirepass の設定: sentinel auth-pass
Sentinel 利用にあたってはクライアント側のサポートも必須
発展
SDOWN と ODOWN
Subjectively Down condition (SDOWN) : ローカルの状態
Objectively Down condition (ODOWN) : quorum パラメータの対象によるマスターの監視
フェイルオーバー時におけるマスターとなるスレーブ選出
マスターからの切断時間
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state 以上の時間(INFO コマンドで確認)
スレーブプライオリティ
処理されたレプリケーションのオフセット
Run ID(辞書順)
TILT モード
Redis はコンピュータ時間に依存しているが、こちらが原因で思いがけない挙動になることを防止するために保護モードになる。
References
Guidelines for Redis clients with support for Redis Sentinel : https://redis.io/topics/sentinel-clients
URL: https://redis.io/topics/benchmarks
Redis のパフォーマンスへ影響を与える要因
ネットワーク帯域幅、レイテンシー
予め PING で確認しておくよい
Redis はよく、CPUの制約より、ネットワークによるスループットの制限を先に受ける
高いスループットをのために 10 Gbit/s NIC や TCP/IP bonding で複数の 1 Gbit/s NICs も考えてみると良い。
CPU
AMD Opteron CPU は Nehalem EP/Westmere EP/Sandy Bridge Intel CPUs の半分ぐらいのパフォーマンスになることもよくある
RAM のスピードとメモリの帯域幅
仮想化の有無
TCP/IP loopback(デフォルト), unix domain sockets
unix domain socketsはTCP/IP loopbackより50%パフォーマンスが良くなることがある。
パイプライニング使用時、unix domain sockets のメリットは減る
イーサネットを使っている時、パイプライニングを使ったコマンドの集約化は特に有効
複数 CPU ソケットサーバでは、NUMA 設定やプロセスの場所に依存
クライアント接続数
NIC 周りの設定
Rx/Tx NIC キューと CPU コアの間でアフィニティを設定し、RPS (Receive Packet Steering)をアクティベートすることで、ベストなスループットが達成できる。
プラットフォーム
メモリアロケータの種類(libc malloc, jemalloc, tcmalloc)等で挙動が変わる
INFO コマンドの mem_allocator フィールドで確認可能
考慮事項
できれば別々のハードウェアで実行
ログレベルを warning や notice にしておく。生成されたログはリモートのファイルシステムに置かない。
ベンチマークの結果を変えるモニタリングツール(MONITOR コマンド)は使わない。INFO コマンドを定期的に取得する
ベンチマーク結果
以下の環境で実験
Intel(R) Xeon(R) CPU E5520 @ 2.27GHz (パイプライニング有り)
Intel(R) Xeon(R) CPU E5520 @ 2.27GHz (パイプライニング無し)
Linode 2048 instance (パイプライニング有り)
Linode 2048 instance (パイプライニング無し)
その他ベンチマーク
memtier_benchmark from Redis Labs : https://github.com/redislabs/memtier_benchmark
rpc-perf from Twitter : https://github.com/twitter/rpc-perf
YCSB from Yahoo @Yahoo : https://github.com/brianfrankcooper/YCSB
References
On Redis, Memcached, Speed, Benchmarks and The Toilet : http://oldblog.antirez.com/post/redis-memcached-benchmark.html
Redis VS Memcached (slightly better bench) - dormando ★5 !! : https://dormando.livejournal.com/525147.html
An update on the Memcached/Redis benchmark : http://oldblog.antirez.com/post/update-on-memcached-redis-benchmark.html
hiredis : https://github.com/redis/hiredis
URL: https://redis.io/topics/releases
リリースサイクル
unstable
development
frozen
release candidate
stable
URL: https://redis.io/topics/ARM
Redis4.0から、一般的な ARM、特にラズベリーパイを Linux/x86アーキテクチャと同様にサポート
Redis における /proc/cpu/alignment 要件
ARM における Linux では、アラインメントが適切でない場合にトラップしてカーネル内部で修正し、プログラムが SIGBUS を発する代わりに実行を続けられるようになっています。
Redis 4.0 以降のバージョンではこの問題に対応済みであり、カーネルで特定の設定を持つ必要はなくなりました。
アラインメントを修正する機能がカーネル側で無効になっていても、Redis は問題なく動作します。
URL: https://redis.io/topics/problems
レイテンシー問題
URL: https://redis.io/topics/latency
クラッシュ問題によるデバッグ
URL: https://redis.io/topics/debugging
RAMの故障
redis-server --test-memory コマンドでビルトインのメモリでテスト(memtest86を使用)
CHANGELOG : Known Issues等もここで確認
3.0 : https://raw.githubusercontent.com/antirez/redis/3.0/00-RELEASENOTES
2.8 : https://raw.githubusercontent.com/antirez/redis/2.8/00-RELEASENOTES
2.6 : https://raw.githubusercontent.com/antirez/redis/2.6/00-RELEASENOTES
Linux関連のバグ(Redisに影響を与えた) : 多くはEC2の文脈
Ubuntu 10.04: https://blog.librato.com/posts/2011/5/16/ec2-users-should-be-cautious-when-booting-ubuntu-1004-amis
Ubuntu 10.10: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/666211
URL: https://redis.io/topics/latency
チェックリスト
サーバをブロックするようなスローコマンドが稼働していないか
EC2ユーザーは、HVMベースの最近のEC2インスタンス(m3.medium等)を使っているか。そうでないとfork()が遅い
transparent huge pagesをカーネルで無効化しているか
echo never > /sys/kernel/mm/transparent_hugepage/enabled で無効化してプロセス再起動
仮想マシンを使っている場合、Redisに関係ない intrinsic latency がないか。
./redis-cli --intrinsic-latency 100 で最小のレイテンシー(ベースライン)を確認してみる。
サーバ側で実行する必要がある。
Latency monitorを有効化して使ってみる。
耐久性とレイテンシー/パフォーマンスはトレードオフ
AOF + fsync always
AOF + fsync 1秒ごと
AOF + fsync 1秒ごと + no-appendfsync-on-rewrite オプションをyes
AOF + fsync never
RDB
レイテンシー計測 : redis-cli --latency -h host
-p port
ネットワークによるレイテンシー
手に入るならVMより物理マシン
コネクションはできるだけ長く維持
クライアントがサーバより同じホストにあるなら、Unixドメインソケットを使う
集約コマンド(MSET/MGET)や変数的なパラメータでコマンドを使う。パイプライニングより。
一連のラウンドトリップよりは、パイプライニング
名前のパイプライニングに向いていない時はLuaサーバサイドスクリプト
Linuxなら、以下の様な値でレイテンシーが上がる可能性はある。
process placement (taskset)
cgroups
real-time priorities (chrt)
NUMA configuration (numactl)
Redisは1つのプロセスが全てのクライアントのリクエストをマルチプレキシングという技術を使って処理している。
バックグラウンドのI/O操作の実行にはスレッド複数使う。
スローコマンドにより生成されるレイテンシー
SORT, LREM, SUNIONなどのコマンドで発生することがある。
GET, SET, LPUSHは一定時間で実行されるコマンドなので問題にはならない。
KEYSコマンドも原因になるがこちらはデバッグ目的で使うべき。
forkにより生成されるレイテンシー
RDBの生成やAOFの書き換え有効時にバックグラウンド処理がforkされる。
Xenはforkが遅かったが、最近のHVMインスタンスは改良された
BGSAVEやINFOコマンドのlatest_fork_usecで測定結果が見られる。
スワッピングによるレイテンシー
AOFやディスクI/Oによるレイテンシー
AOFはwrite()とfdatasync()の2つのシステムコールを基本的に使う
遅いシステムコールの調査
失効によるレイテンシー
同時に失効するキーが多いとレイテンシーの原因になる
Redis software watchdog
プロダクション環境でのデバッグに、2.6で試験的に導入されたRedis software watchdogが使える。
以下のコマンドで有効化。終わったら値を0に設定する。
サーバのブロックで例えば、DEBUG SLEEPコマンドが使われる。
URL: https://redis.io/commands/slowlog
パラメータ slowlog-log-slower-than と slowlog-max-len がある
SLOWLOG GET 10
のように直近N件のエントリを確認
SLOWLOG LEN
で現在のスローログの長さ
SLOWLOG RESET
でスローログをリセット
URL: https://redis.io/topics/latency-monitor
Redis 2.8.3から Latency Monitoring が導入
以下のようにして有効化しておく
LATENCY コマンド
LATENCY LATEST
LATENCY HISTORY event-name
LATENCY RESET [event-name ... event-name]
LATENCY GRAPH event-name
LATENCY HISTORY
LATENCY DOCTOR
URL: https://redis.io/topics/debugging
RedisのプロセスIDを取得して、gdbデバッグ
スタックトレースとプロセッサーのレジスター取得
Redisは-O2の最適化オプションがされているがデバッグの時は大変になるので外す。
screen/tmuxの使用を推奨
References
Power Sessions with Screen: https://www.linuxjournal.com/article/6340
Redis DBグループ: https://groups.google.com/forum/#!forum/redis-db
URL: https://redis.io/topics/cluster-tutorial
Redis Cluster TCP ポート
Redis は、通信用ポートに通常 6379番ポート, クラスターバスポートに16379番ポート(オフセット +10000)の2つを空ける必要がある。
Redis Cluster と Docker
Redis クラスターを Docker に対応させるためには、host ネットワーキングモード(Docker の --net=host オプション)で稼働する必要があり。
Redis Cluster data sharding
Redis クラスターはconsistent hashing を使わないで、hash slot と呼ばれるシャーディングを採用
Redis クラスターには、16,384の consistent hashing があり、CRC16 で 16,384 のモジュロを取る。
3ノードの例
Node A contains hash slots from 0 to 5500.
Node B contains hash slots from 5501 to 11000.
Node C contains hash slots from 11001 to 16383.
Redis Cluster consistency guarantees
Redis は強一貫性を保証していない
必要であれば、WAIT コマンドで実装することで、同期書き込みをできる
Redis Cluster configuration parameters: redis.conf のパラメータ
cluster-enabled <yes/no>
cluster-config-file : ユーザーではなく、Redis クラスターノードで自動保存に使われる設定ファイル
cluster-node-timeout
cluster-slave-validity-factor
cluster-migration-barrier
cluster-require-full-coverage <yes/no>
Creating and using a Redis Cluster
クラスターの作成
redis-trib のコマンドが便利
gem install redis
でインストール
Redis ソースコードの src/ 以下にある
create-cluster のコマンド
起動、作成、停止を一括で処理
utils/ 以下にある
コマンド
create-cluster start
create-cluster create
create-cluster stop
クラスターのクライアント実装
redis-rb-cluster
redis-py-cluster
Predis
Jedis
StackExchange.Redis
thunk-redis
redis-go-cluster
redis-cli
Resharding the cluster
redis-trib
ユーティリティで、 ./redis-trib.rb reshard 127.0.0.1:7000
redis-trib
ユーティリティは、管理者サポートのみ
シャーディングでターゲットIDを調べるために以下のコマンド
リシャーディングの終わりにクラスターのヘルス状態確認
Scripting a resharding operation
リシャーディングは、以下のコマンドで実行
A more interesting example application
一貫性のテスト
フェイルオーバーのテスト
以下の構成
以下のコマンドでフェイルオーバー
手動フェイルオーバーは、 CLUSTER FAILOVER
コマンド
何も問題を起こさなくても強制的にフェイルオーバーを実行
CLUSTER NODES
コマンドの出力結果
Node ID
ip:port
flags: master, slave, myself, fail, ...
if it is a slave, the Node ID of the master
Time of the last pending PING still waiting for a reply.
Time of the last PONG received.
Configuration epoch for this node (see the Cluster specification).
Status of the link to this node.
Slots served...
ノードの追加
redis-trib
は、CLUSTER MEET コマンドやクラスターの状態確認を行なう。
マスター
レプリカ
CLUSTER REPLICATE コマンドで以下のようにしても追加することができる
ノード削除
ノード ID 取得
実際に削除
レプリカマイグレーション
他のマスターへ、レプリカをマイグレーションすることができる。
Redis クラスターのノードのアップグレード
スレーブ
停止してアップデートしたバージョンで再起動
再接続の際は異なるスレーブへ
マスター
CLUSTER FAILOVER
コマンドでマスターをスレーブへ降格させて行なう
Redis クラスターのマイグレーション
クライアントを停止
各マスターで BGREWRITEAOF コマンドで AOF ファイルを作成
スレーブを持たない、複数マスターを持つ Redis クラスターを作成
クラスターを停止して、AOF ファイルでデータ復旧
新しい AOF ファイルで、クラスターを再起動
redis-trib fix
コマンドで、各ノード hash slot に従ってキーをマイグレーションするようにクラスターを修正
最後にクラスターがokか redis-trib check
コマンドで確認
クライアントから接続
URL: https://redis.io/topics/cluster-spec
プロトコルにおけるクライアントとサーバーの役割
クラスターノードはリクエストをプロキシすることができないので、-MOVED や -ASK といったエラーを用いて他のノードにリダイレクトされます。
クライアントは理論的にはすべてのノードに自由にリクエストを行ってよく、必要に応じてリダイレクトを受け取るので、クラスターの状態を持つ必要はない。しかし、クライアント側でキーとノードのマップをキャッシュさせることができるならば、性能を改善することができるでしょう。
書き込み安全
Redis クラスタはノード間で非同期レプリケーションを行い、最後のフェイルオーバによって暗黙的なマージが行われます。
多数派のパーティションに対する書き込みが失われるシナリオの例
マスターとスレーブの間は非同期のレプリケーションとなっているため、書き込みはマスターに到達したものの、マスターがクライアントに応答を返した時点でスレーブに書き込みが伝達されていないケースが起こりうる。
クライアントが古いルーティングテーブルに沿って、クラスターにおいて(新しいマスターの)スレーブになる処理が完了しないうちに、古いマスターに対し書き込みを行う
可用性
Redisクラスターのレプリカマイグレーションのおかげで、レプリカは孤立したマスター(レプリカを持たないもの)に移行できるようになり、現実世界におけるクラスタの可用性は改善しました。
パフォーマンス
Redis クラスターのノードはコマンドを他のノードに転送しませんが、その代わりにキーに対して割り当てられたノードに関する情報を返し、クライアントをリダイレクトさせます。
結果としてクライアントはクラスターの最新の状態と、各キーに対応するノードの情報を得ることになり、適切なノードに対してコマンドを送り、操作を行います。
非同期レプリケーションを採用しているため、ノードは他のノードが書き込みを受信するのを待ちません(WAITコマンドを明示的に実行した場合を除く)
また、複数のキーを扱うコマンドが近くのキーのみで動作するので、リシャーディングを除いて、データがノード間で移動することはありません。
通常の操作は単一の Redisインスタンスの場合とほとんど同様に扱われます。これはつまり、N個のマスターノードで構成される Redisクラスターと、単一の Redisインスタンスを N個直列に並べてスケールアウトさせた構成は同等のパフォーマンスを発揮できるということです。同時に、クライアントは各ノードと永続的な接続を維持するように動作するため、クエリの通信はほとんどのケースで 1回の往復で済むでしょう。そのため、レイテンシは、単体つまりスタンドアローンで動作する Redisノードの場合と比べても遜色ないと言えます。
マージ操作を回避すべき
Redis におけるバリューはしばしば大きなサイズとなり、数百万という要素についてリストしたり並べ替えたりという操作も多く行われます。データタイプは複雑ですが、それぞれに意味があります。これらのバリューを転送したりマージすることは、大きなボトルネックになる可能性があります。
Redisクラスターの主な構成概要
キーの分散モデル
キー空間はハッシュされて 16384スロットに分割されるので、最大でクラスターは 16384ノードのマスターを持つことができます(しかし、ノード数は最大でも 1000程度までにしておくことをお勧めする)。
クラスターが安定した状態であれば、ひとつのスロットは単一のノードで処理されます(しかしながら、ネットワークの分断や障害の発生時、古いデータの読み込みを許容できるのであれば、読み込みが継続できるようひとつ以上のスレーブによって置き換えることができます)。
HASH_SLOT = CRC16(key) mod 16384
CRC16 は以下のような仕様です。
Name: XMODEM (also known as ZMODEM or CRC-16/ACORN)
Width: 16 bit
Poly: 1021 (That is actually x^16 + x^12 + x^5 + 1)
Initialization: 0000
Reflect Input byte: False
Reflect Output CRC: False
Xor constant to output CRC: 0000
Output for "123456789": 31C3
キーのハッシュタグ
スロットに関するハッシュの計算には、ハッシュタグを実装するために例外が設けられています。ハッシュタグは複数のキーを同じハッシュスロットに配置するために用いられます。これは、Redisクラスターで複数のキーに対する操作を実装するためのものです。
キーが "{...}" という文字列を含む場合、スロットは { と } の間の文字列だけを使ったハッシュによって算出されます。
複数{}がある場合は、最初の方
Ruby
C
クラスターノードの属性
すべてのノードはそれぞれ異なった名前を持ちます。ノードの名前は 160ビットのランダムな数字によって 16進数で割り当てられ、ノードがはじめに起動したときに計算されます( /dev/urandom を使います )。 ノードは設定ファイルに ID を保存し、設定ファイルをシステム管理者が削除するか、あるいはCLUSTER RESET コマンドによってハードリセットされるまで、その後ずっと同じ ID が使われます。
ノードの ID はクラスターの中でノードを特定するためのものです。 ノードは ID を変えることなく、IPアドレスを変化させることができます。クラスターはバス上でゴシッププロトコルを用いて、IP やポート、構成の変化を検出します。
ID が各ノードに割り当てられた唯一の情報というわけではありませんが、常に一定の値を示すものは他にありません。各ノードは後述するように幾つかの情報も持ちます。いくつかは、特定のノードに関する詳細に関するもので、クラスタの中で一貫した値となっています。その他、ノードの監視情報などは各ノードがローカルに保持します。
すべてのノードは、クラスター内で認識している他のノードに関する情報も持ちます。ノードID、IPアドレスやポート、フラグセット、slave フラグの場合はどれがマスターか、最後に ping された時間および pong を受け取った時間、現在のエポック設定(これはあとで説明します)、接続の状態、最終的に割り当てられたスロットなどです。
CLUSTER NODESコマンドはどのノードにも送ることができ、クラスターの状態と、クエリを受けた各ノードが持っているローカルな情報を返します。
クラスターのトポロジー
Redisクラスターはフルメッシュ構造であり、TCPコネクションを用いて各ノードはそれぞれ他のすべてのノードに接続します。
ノードは通常通りに動いているときに多数のメッセージを交換することを避け、代わりにゴシッププロトコルで構成をアップデートする仕組みを用いるので、フルメッシュのコネクションを保持していても、交換されるメッセージが指数関数的に増大することはありません。
ノードのハンドシェイク
ノードはクラスターバス上のポートで常にコネクションを受け入れていて、たとえ通信先のノードが信頼されていないものであったとしても、ping を受け取れば応答を返す。
しかし、パケットを受信したノードがクラスターに所属していない場合、そのパケットは破棄されます。
既存のグラフ構造にノードを追加した場合、自動的にすべてのノードが接続された結果が得られることを意味しています。システム管理者によって信頼関係が確立される必要がありますが、クラスターは他のノードを自動検出することができます。
この仕組みはクラスターをより堅牢にする上、IPアドレスの変更やネットワークに関連したイベントによって異なる Redisクラスターが混在してしまうことも防ぎます。
ノードは以下の方法でのみ他のノードをクラスターの一部として扱います。
MEET メッセージを表明すること。このメッセージは PING と同じようなものではあるものの、受け取ったノードは、このメッセージによってクラスターの一部であることを認識する。ノードはシステムの管理者が以下のコマンドを実行したときにのみ MEETメッセージを送出する。
CLUSTER MEET ip port
とあるノードを考えた時そのノードは、すでに信頼済みのノードによってゴシップ(訳注: P2P におけるピア間の通信)された場合、クラスターの一部として登録される。つまり、例えば A が B を認識していて、B が C を認識しているとき、B はゴシップメッセージを A と C に送信する。このとき A は C をネットワークの一部として認識し、C に接続を試みる。
リダイレクションとリシャーディング
MOVED リダイレクション
Redisクライアントは、スレーブノードも含めてすべてのノードにクエリを送出することができます。ノードはクエリを分析し、処理できるもの(クエリが単一キーか、あるいは複数キーだがすべてが同じスロットにある)ならば、キーを処理すべきノードを探します。
もしスロットがそのノードに割り当てられていた場合、クエリはそのまま処理されます。そうでない場合は内部でノードのマッピングを確認し、以下のような MOVED エラーを返します。
クライアントは、必須ではないものの、スロット3999 が 127.0.0.1:6381 に割り当てられているということを覚えておくべきです。それによって新しいコマンドを実行する必要が出てきたとき、キーをハッシュしてスロットを計算するだけでよく、正しいノードを選択できる確率が高まるでしょう。
加えて、MOVED のリダイレクトが発生した時に CLUSTER NODES あるいは CLUSTER SLOTSコマンドでクライアント側のクラスターレイアウトを丸ごとリフレッシュすることも考えられます。リダイレクトが発生するときには 1つではなく複数のスロットが再割り当てされたと考えられますので、速やかに情報を更新することはほとんどの場合でベストな戦略です。
クライアントは後述するように -ASKリダイレクションを正しく取り扱わなくてはいけません。そうでない場合、Redisクラスターに対応したクライアントとは言えません。
クラスターのオンライン再構成
以下のサブコマンド
CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
CLUSTER SETSLOT slot NODE node
CLUSTER SETSLOT slot MIGRATING node
CLUSTER SETSLOT slot IMPORTING node
ASKリダイレクション
MOVED が永続的に異なるノードでスロットが提供されることを示すものであり、その次のクエリは指定されたノードに送出されるべき
ASK は指定されたノードに次のクエリを送るというだけものです。
基本的に ASKING コマンドは IMPORTING になっているスロットへのクエリに対し、1度きりのフラグという性質を持ちます。
クライアントにおける初回のコネクションとリダイレクションの扱い
クライアントはスマートに処理を行うため、スロットの構成を記憶しておくべきでしょう。
間違ったノードにアクセスした場合、リダイレクトが返ってくるだけなので、クライアント側から見て情報をアップデートするトリガーとして考えるべきです。
ここでクライアントは MOVEDリダイレクションが返ってきたスロットだけを更新することもできます。ただし、たいていの場合は複数のスロットが一度に変更されます
CLUSTER SLOTS と呼ばれ、スロットの範囲、その範囲に割り当てられたマスタおよびスレーブノード、といった情報を配列で返します。
複数のキーに関する操作
ハッシュタグのおかげで、クライアントは複数のキーを扱うことができます。
MSET {user:1000}.name Angela {user:1000}.surname White
リシャーディング中であっても指定したすべてのキーが同じノード上(移動元、移動先、どちらでも)に存在していた場合は、利用することができます。
複数のキーを考えた時に、いくつかのキーが存在しない、あるいはリシャーディング中で移動元と移動先に分離している場合、-TRYAGAINエラーが返されます。クライアントは時間を置いてリトライするか、エラーを返すことになります。
スレーブノードを用いて読み込みをスケールさせる
READONLY はスレーブノードに対し、クライアントが書き込みを行わないこと、多少古いデータであっても許容できるということを伝えます。
ハートビートとゴシップメッセージ
毎秒、ノードはランダムに幾つかのノードを選び、ping を行います。つまり各ノードから送出
このメッセージを少なくする方法はいくつかありますが、現在までに帯域幅に関する問題は報告されていないため、簡単で直接的なデザインになっています。
ハートビートパケットの中身
共通のヘッダーは以下
ノードID、これは 160ビットの疑似ランダムな文字列でノードが作られたときに一度だけ生成され、以後は不変の値
分散アルゴリズムをマウントするための currentEpoch と configEpoch フィールド(これについては次の章で説明します)。ノードがスレーブの場合、configEpoch はマスターの configEpoch と一致します。
ノードのフラグ。スレーブ、マスター、あるいは単体ノードなのか
ノードが持っているスロットのマッピング。もしスレーブならば、そのスロットのマッピングはマスターが持っているものを意味する。
送出元の TCPポート( Redis がクライアントのコマンドを受け取るポート。クラスターバス上のポートは 10000 を足す )
送信者から見た時のクラスターの状態(ダウンもしくはOK)
スレーブのとき、送信側ノードにおけるマスターの ID
ping と pong のパケットはゴシップセクションも持ち合わせている
ゴシップセクション
ノードID
ノードの IP とポート
ノードのフラグ
ゴシップセクションでは、送信したノードから見た他のノードの状態などを受け取ります。これは障害の検知や他のノードをクラスター内で検出するために用いられます。
障害時の挙動
障害を検出するためのフラグは 2種類あり PFAIL と FAIL です。
PFAIL は 障害の可能性がある ことを示し、未知の障害を意味します。
とあるノードから見たときに特定のノードが NODE_TIMEOUT の時間、疎通しないことが確認できると、PFAIL フラグを付与します。
他のノードに関する PFAIL フラグそれ自体については、すべてのノードが持ち合わせている情報ですが、スレーブの昇格を行う根拠にするには少し足りません。
ノードがダウンしていると判断するためには、PFAIL から FAIL への変化が必要です。
FAIL はノードが障害になっており、決められた時間において多数のマスターから確認が行われた結果です。
PFAIL もしくは FAIL が多数派であり、NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT だけの時間が経過している
再構成の挙動、伝播、およびフェイルオーバについて
クラスターの epoch
複数のノードが競合する情報を発信してしまったときに、どの情報が最も最新のものなのか判断することができる
currentEpoch は 64ビットの符号なし整数
他のノードからパケットを受け取るたび、送信元の epoch(クラスターバスメッセ―ジのヘッダーに含まれる)が手元の epoch より大きければ、currentEpoch を受け取った epoch で更新します。
この仕組みにより、すべてのノードがいずれは最新の configEpoch に追いつき、同意するということになります。
構成における epoch
各マスターはスロット群のマッピングとは別に、ping/pong パケットを用いて自身の configEpoch も配布しています。
スレーブもまた configEpoch を ping/pong パケットで配布しますが、その中身はマスターのものです。ただしこれにより、他のインスタンスから見て古い状態になっていることが検知できます(マスターノードは古い状態のノードには投票しません)。
configEpoch の変更を受け取ると、その値は永続的なものとして node.conf に保存されます。これは currentEpoch に関しても同様です。これらの 2つの値は fsync-ed でノードが次の動作に移る前にディスクに書き込まれます。
スレーブの選挙と昇格
スレーブが選ばれるための最初のステップは、currentEpoch カウンターを加算し、マスター群に投票を依頼することです。
投票はスレーブによって依頼され、FAILOVER_AUTH_REQUEST パケットですべてのマスターに同報されます。そのあと NODE_TIMEOUT の 2倍の時間だけ応答を待ちます(ほとんどの場合は 2秒です)。
マスターが一度投票すると、FAILOVER_AUTH_ACK で応答を返し、以降は NODE_TIMEOUT * 2 の時間、同じマスターに関連するスレーブには投票しません。この間は、同じマスターに関連する他の承認リクエストには応答しないということです。
スレーブの順位
マスターが FAIL 状態になるとすぐ、スレーブは選挙に備えて短い待機時間をとります。この時間の計算式は以下です。
DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + SLAVE_RANK * 1000 milliseconds.
待機時間は、クラスター内で FAIL 状態が伝播するのを待つためのもの
ランダムな遅延時間を設定するのは、まったく同じタイミングで選挙が開始することを防ぐためです。
SLAVE_RANK はスレーブの順位で、マスターからレプリケートされたデータの処理量に関するものです。スレーブは、マスターが障害になったとき(ベストエフォートの)順位を決めるためにメッセージを交換します。 もっとも最新のデータを持っているスレーブが 0位、その次が 1位、といった形です。 この方法では、もっとも最新のデータを持っているスレーブが、まず選挙を試行します。 順位の並びは厳密なものではありません。
いずれかのスレーブが選挙で選ばれたときは、既存のマスターよりも高い値の configEpoch を持ちます。スレーブは ping/pong パケットでマスターになったことを伝播し、configEpoch とともに割り当てられたスロットを伝えます。
スロット構成の伝播
スロットの割り当てに関する構成は、以下のように伝播します。
ハートビートのメッセージ。ping/pong パケットの送り元は、常にスロットに関する情報も送ります(スレーブの場合は、そのマスターに関する情報を送ります)。
UPDATEメッセージ。すべてのハートビートパケットは送信元の configEpoch および割り当てられたスロットの情報を含むので、受信側で情報が古いことを検知すると新しい情報を返し、アップデートを促します。
ノードは、スロットテーブルを更新するために幾つかのルールに沿って動作
ルール 1: もしスロットが未割り当て(つまり NULL)であり、いずれかのノードが割り当てを要求しているとき、その割り当て要求に沿って自分が持っている情報を更新します。
ルール 2: もしスロットがマスターにすでに割り当てられているときに、別のノードがより大きな値の configEpoch を主張しているとき、スロットのテーブルを新しいノードで更新します。(フェイルオーバの後勝ち)
レプリカ移行のアルゴリズム
スレーブの構成に関しては、epoch 値などでバージョン管理する必要が無い
configEpoch の競合を解決するアルゴリズム
自身が辞書的な並びにおいてより小さいノードID を持つとき、currentEpoch に 1 を加え、それを新しい configEpoch とする
同じ configEpoch を持つ幾つかのノードが存在した場合、一番大きなノードID を持つノードを除いて、順次ノードID が加算されていきます。
ノードのリセット
コマンド
CLUSTER RESET SOFT
CLUSTER RESET HARD
動作
ノードがスレーブのとき、マスターに変更し、その上でデータ領域を破棄します。ノードがマスターでありデータを含む場合、リセット操作はキャンセルされます
すべてのスロットが解放され、フェイルオーバの状態をリセットします
他のすべてのノードにおいて、ノードテーブルから除去されます。これにより、他のノードから認識されなくなります
(ハードのみ) currentEpoch や configEpoch 、 lastVoteEpoch をすべて 0 にします
(ハードのみ) ノードID をランダムに変更し、新しいものとします
データが空ではないマスターノードはリセットすることができません(通常は、他のノードにリシャーディングするでしょう)。しかし、特殊な条件下でどうしても実行する必要がある場合(例えばクラスターを作り直すために壊すときなど)は、FLUSHALL でデータをすべて消し、その上でリセットを行うようにします。
クラスターからノードを取り除く
CLUSTER FORGET <node-id>
コマンド
指定されたノードID をテーブルから削除する
同じノードID からの再登録を 60秒間、拒否する
ANSI C での CRC16 の実装
URL: https://github.com/soundcloud/roshi
Redis ベースの Go で実装されたタイムスタンプが付与されたイベント向けの大規模な CRDT セットの実装
URL: https://redis.io/topics/rdd
RDD1 -- Redis Design Drafts : https://redis.io/topics/rdd-1
RDD2 -- RDB version 7 info fields : https://redis.io/topics/rdd-2
Redis DB: https://groups.google.com/forum/#!forum/redis-db
RDD のためのアイデアは Google Group で話す
URL: https://redis.io/topics/protocol
Redis クライアントはサーバとRESP (REdis Serialization Protocol)プロトコルで通信
ネットワークプレイヤー
TCP 6379番ポート
RESPはTCPに特化したものではないが、Redisの文脈では、TCPのみ。
リクエスト/レスポンスモデル
パイプライニング、Pub/Subの例外を除く
RESP protocol description
RESP は異なるデータ型をシリアライゼーション
データ型は最初のバイトで以下のようになる
Simple Strings: "+"
Errors: "-"
Integers: ":"
Bulk Strings: "$"
Arrays: "*"
"\r\n" (CRLF)で終了
RESP Simple Strings : "+OK\r\n" , 最小限のオーバーヘッドで、ノンバイナリセーフな文字列
RESP Errors : "-Error message\r\n"
RESP Integers : ":1000\r\n"
RESP Bulk Strings : "$6\r\nfoobar\r\n" , 512 MBまでのバイナリセーフな文字列
RESP Arrays: "*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
Null elements in Arrays
最初の要素が Null
2番目の要素がNull
["foo",nil,"bar"]
Sending commands to a Redis Server
キー名: mylist にリスト長を取得するためにクライアントから LLEN mylist コマンドを実行するときの例
実際に送るものは、*2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r
となる。
URL: https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format
High Level Algorithm to parse RDB
URL: https://redis.io/topics/internals
Redis Internals documentation
Redis 2.2では20,000行程度のシンプルなコード
Redis dynamic strings
URL: https://redis.io/topics/internals-sds
Redis の文字列は、sds.c で実装
sdshdr は、以下のように定義
sdsnewlen 関数で Redis 文字列生成
Redis Virtual Memory : https://redis.io/topics/internals-vm
Redis 2.0 までの説明
Redis Event Library
https://redis.io/topics/internals-eventlib
https://redis.io/topics/internals-rediseventlib
Redis は独自のイベントライブラリを実装。ae.c で確認することができる。
以下の様なデータ構造
以下の様な関数を定義
aeCreateEventLoop
aeCreateTimeEvent
aeCreateFileEvent
aeProcessEvents
processTimeEvents
URL: https://www.cheatography.com/tasjaevan/cheat-sheets/redis/
Notes: Redisコマンドのチートシート ★5
URL: https://redis.io/topics/whos-using-redis
Twitter, GitHub, Weibo, Pinterest, Snapchat, Craigslist, Digg, StackOverflow, Flickr 等
Redis Labs Company Blog | Redis Labs: https://redislabs.com/resources/blog/
GitHub Gists(antirez): https://gist.github.com/antirez
Redis Documentation (Japanese Translation) - Redis Documentation (Japanese Translation): http://redis-documentasion-japanese.readthedocs.io/ja/latest/