Redis 公式ドキュメント まとめ

Redis本を執筆しました。こちらも是非合わせてご覧ください。

---------------------------------------------------------------------------------------------------------------------------------------------------------

Redis の公式ドキュメントを一読しておこうと思い、読んでまとめてみました。( 2018/05/06 時点)

ただし、Redis 4.0 については、公式ドキュメントに一部内容が含まれますが、RDB, AOF や Docker/NAT サポート辺り等、その他複数機能強化や変更点がありますので、詳細は、下記リリースノートを参照していただければと思います。 https://raw.githubusercontent.com/antirez/redis/4.0/00-RELEASENOTES

別途、Redisの機能のまとめ記事等は以下のURLを参照いただければと存じます。

Redis Documentation

  • URL: https://redis.io/documentation

Programming with Redis

Command reference

  • URL: https://redis.io/commands
  • 以下、全206コマンド
APPEND
AUTH
BGREWRITEAOF
BGSAVE
BITCOUNT
BITFIELD
BITOP
BITPOS
BLPOP
BRPOP
BRPOPLPUSH
CLIENT KILL
CLIENT LIST
CLIENT GETNAME
CLIENT PAUSE
CLIENT REPLY
CLIENT SETNAME
CLUSTER ADDSLOTS
CLUSTER COUNT-FAILURE-REPORTS
CLUSTER COUNTKEYSINSLOT
CLUSTER DELSLOTS
CLUSTER FAILOVER
CLUSTER FORGET
CLUSTER GETKEYSINSLOT
CLUSTER INFO
CLUSTER KEYSLOT
CLUSTER MEET
CLUSTER NODES
CLUSTER REPLICATE
CLUSTER RESET
CLUSTER SAVECONFIG
CLUSTER SET-CONFIG-EPOCH
CLUSTER SETSLOT
CLUSTER SLAVES
CLUSTER SLOTS
COMMAND
COMMAND COUNT
COMMAND GETKEYS
COMMAND INFO
CONFIG GET
CONFIG REWRITE
CONFIG SET
CONFIG RESETSTAT
DBSIZE
DEBUG OBJECT
DEBUG SEGFAULT
DECR
DECRBY
DEL
DISCARD
DUMP
ECHO
EVAL
EVALSHA
EXEC
EXISTS
EXPIRE
EXPIREAT
FLUSHALL
FLUSHDB
GEOADD
GEOHASH
GEOPOS
GEODIST
GEORADIUS
GEORADIUSBYMEMBER
GET
GETBIT
GETRANGE
GETSET
HDEL
HEXISTS
HGET
HGETALL
HINCRBY
HINCRBYFLOAT
HKEYS
HLEN
HMGET
HMSET
HSET
HSETNX
HSTRLEN
HVALS
INCR
INCRBY
INCRBYFLOAT
INFO
KEYS
LASTSAVE
LINDEX
LINSERT
LLEN
LPOP
LPUSH
LPUSHX
LRANGE
LREM
LSET
LTRIM
MEMORY DOCTOR
MEMORY HELP
MEMORY MALLOC-STATS
MEMORY PURGE
MEMORY STATS
MEMORY USAGE
MGET
MIGRATE
MONITOR
MOVE
MSET
MSETNX
MULTI
OBJECT
PERSIST
PEXPIRE
PEXPIREAT
PFADD
PFCOUNT
PFMERGE
PING
PSETEX
PSUBSCRIBE
PUBSUB
PTTL
PUBLISH
PUNSUBSCRIBE
QUIT
RANDOMKEY
READONLY
READWRITE
RENAME
RENAMENX
RESTORE
ROLE
RPOP
RPOPLPUSH
RPUSH
RPUSHX
SADD
SAVE
SCARD
SCRIPT DEBUG
SCRIPT EXISTS
SCRIPT FLUSH
SCRIPT KILL
SCRIPT LOAD
SDIFF
SDIFFSTORE
SELECT
SET
SETBIT
SETEX
SETNX
SETRANGE
SHUTDOWN
SINTER
SINTERSTORE
SISMEMBER
SLAVEOF
SLOWLOG
SMEMBERS
SMOVE
SORT
SPOP
SRANDMEMBER
SREM
STRLEN
SUBSCRIBE
SUNION
SUNIONSTORE
SWAPDB
SYNC
TIME
TOUCH
TTL
TYPE
UNSUBSCRIBE
UNLINK
UNWATCH
WAIT
WATCH
ZADD
ZCARD
ZCOUNT
ZINCRBY
ZINTERSTORE
ZLEXCOUNT
ZRANGE
ZRANGEBYLEX
ZREVRANGEBYLEX
ZRANGEBYSCORE
ZRANK
ZREM
ZREMRANGEBYLEX
ZREMRANGEBYRANK
ZREMRANGEBYSCORE
ZREVRANGE
ZREVRANGEBYSCORE
ZREVRANK
ZSCORE
ZUNIONSTORE
SCAN
SSCAN
HSCAN
ZSCAN

Using pipelining to speedup Redis queries

  • URL: https://redis.io/topics/pipelining

  • Redis はパイプライニングをサポート

  • メモリを使ってキューにリクエストを格納。

  • 複数のコマンドを1回のread()。複数のリプライを1回のwrite()でまとめる。

  • スクリプトでは、サーバサイドでの処理により効果的

  • ループバックインターフェイスでさえ、busy loopが遅くなることがある理由は、インターフェースのバッファにコマンドがあっても、サーバからの読み取りのためにカーネルがスケジューラでサーバプロセスを稼働させていないことがある場合があるから。

  • Rubyのサンプルコード

    r = Redis.new
    r.pipelined {
        10000.times {
            r.ping
        }
    }

Pub/Sub

  • 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

EVAL

  • 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])) のようにシードを与えるテクニックがある。
RandomPushScript = <<EOF
    local i = tonumber(ARGV[1])
    local res
    math.randomseed(tonumber(ARGV[2]))
    while (i > 0) do
        res = redis.call('lpush',KEYS[1],math.random())
        i = i-1
    end
    return res
EOF

r.del(:mylist)
puts r.eval(RandomPushScript,1,:mylist,10,rand(2**32))
  • 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デバッギングを利用できる。

Redis Lua scripts debugger – Redis

  • 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コマンドで現在のコールフレーム外からの変数の値参照

Special encoding of small aggregate data types

  • 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オプションもある。

EXPIRE – Redis

  • 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オペレーションが同期される。

Using Redis as an LRU cache

  • 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

Transactions

  • 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でスクリプトが導入され、機能が重複しているので、こっちを使ったほうが良さそう。

Redis Mass Insertion – Redis

  • URL: https://redis.io/topics/mass-insert

  • Use the protocol, Luke

    • 以下のように実行する。
    $ cat data.txt | redis-cli --pipe
    
    SET Key0 Value0
    SET Key1 Value1
    ...
    SET KeyN ValueN
    
    • スクリプトも利用できる。
    $ ruby proto.rb | redis-cli --pipe
    
    def gen_redis_proto(*cmd)
        proto = ""
        proto << "*"+cmd.length.to_s+"\r\n"
        cmd.each{|arg|
            proto << "$"+arg.to_s.bytesize.to_s+"\r\n"
            proto << arg.to_s+"\r\n"
        }
        proto
    end
    
    puts gen_redis_proto("SET","mykey","Hello World!").inspect
    
    (0...1000).each{|n|
        STDOUT.write(gen_redis_proto("SET","Key#{n}","Value#{n}"))
    }
    
    • Redis2.6より前は以下のようにして実行できたが、Netcatはデータが全部送られるとかは確認しない。
    (cat data.txt; sleep 10) | nc localhost 6379 > /dev/null
    

Partitioning: how to split data among multiple Redis instances.

  • 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

Distributed locks with Redis

  • 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

    • リソースがないか確認してから値をセットすると、フェイルオーバー時も大丈夫
    SET resource_name my_random_value NX PX 30000
    
    • Luaスクリプトでは以下のように実装する。
    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0	
    end
    
    • 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

Redis Keyspace Notifications

  • URL: https://redis.io/topics/notifications
  • キースペース通知は、Pub/Subチャンネルを、ある方法でRedisのデータセットに影響を与えるイベントを受信するように購読するようにできる。
PUBLISH __keyspace@0__:mykey del : Key-space 通知 -> チャンネルは、キーイベントを受信 
PUBLISH __keyevent@0__:del mykey : Key-event 通知 -> チャンネルは、キー名を受信
  • 設定は、redis.confかCONFIG SETコマンドでnotify-keyspace-eventsを使う。以下の値を設定。
K     Keyspace events, published with __keyspace@<db>__ prefix.
E     Keyevent events, published with __keyevent@<db>__ prefix.
g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
$     String commands
l     List commands
s     Set commands
h     Hash commands
z     Sorted set commands
x     Expired events (events generated every time a key expires)
e     Evicted events (events generated when a key is evicted for maxmemory)
A     Alias for g$lshzxe, so that the "AKE" string means all the events.
  • 全許可
$ redis-cli config set notify-keyspace-events KEA
$ redis-cli --csv psubscribe '__key*__:*'

Secondary indexing with Redis

  • 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の形式で保存する。
ZADD myindex 0 banana:273:Banana
  • Using numbers in binary form

    • 小数の数の保存にはメモリをたくさん使うため、例えば128bitのバイナリとして保存する方法がある。
    • ビッグエンディアンのフォーマットで保存する。Redisがmemcmp()関数で文字列を比較することによる。
  • Updating lexicographical indexes

    • インデックスのリビルド等は遅くて大変なので、メモリをうかうか割にハッシュのオブジェクトIDと現在のインデックスの値をハッシュのマッピングをする方法がある。
MULTI
ZADD myindex 0 0056:0028.44:90
HSET index.content 90 0056:0028.44:90
EXEC
  • Representing and querying graphs using an hexastore

    • 関係性を表すには、以下のように登録すれば良い
    ZADD myindex 0 spo:antirez:is-friend-of:matteocollina
    
    • 検索するなら、ZRANGEBYLEXが使える。\xffは末尾に255という値をセットしていることを表すもの。
    ZRANGEBYLEX myindex "[spo:antirez:is-friend-of:" "[spo:antirez:is-friend-of:\xff"
    1) "spo:antirez:is-friend-of:matteocollina"
    2) "spo:antirez:is-friend-of:wonderwoman"
    3) "spo:antirez:is-friend-of:spiderman"
    
  • Multi dimensional indexes

    • 多次元インデックスを表し、それぞれの次元で範囲指定したいとき
    • 多次元のインデックスを表すために、数字をinterleaveする。
    • x,y座標の数字を表す時
    x = 75  -> 001001011
    y = 200 -> 011001000
    
    • それぞれ以下のようにx,yのbitを交互に並べる。
    000111000011001010:75:200
    
    • Rubyのコードの実装例
def spacequery(x0,y0,x1,y1,exp)
    bits=exp*2
    x_start = x0/(2**exp)
    x_end = x1/(2**exp)
    y_start = y0/(2**exp)
    y_end = y1/(2**exp)
    (x_start..x_end).each{|x|
        (y_start..y_end).each{|y|
            x_range_start = x*(2**exp)
            x_range_end = x_range_start | ((2**exp)-1)
            y_range_start = y*(2**exp)
            y_range_end = y_range_start | ((2**exp)-1)
            puts "#{x},#{y} x from #{x_range_start} to #{x_range_end}, y from #{y_range_start} to #{y_range_end}"

            # Turn it into interleaved form for ZRANGEBYLEX query.
            # We assume we need 9 bits for each integer, so the final
            # interleaved representation will be 18 bits.
            xbin = x_range_start.to_s(2).rjust(9,'0')
            ybin = y_range_start.to_s(2).rjust(9,'0')
            s = xbin.split("").zip(ybin.split("")).flatten.compact.join("")
            # Now that we have the start of the range, calculate the end
            # by replacing the specified number of bits from 0 to 1.
            e = s[0..-(bits+1)]+("1"*bits)
            puts "ZRANGEBYLEX myindex [#{s} [#{e}"
        }
    }
end

spacequery(50,100,100,300,6)
  • 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 modules API

Redis 4.0からモジュールプログラミングの機能追加

Introduction to Redis modules

  • URL: https://redis.io/topics/modules-intro

  • Loading modules

loadmodule /path/to/mymodule.so
MODULE LOAD /path/to/mymodule.so

ローディングされたモジュール

MODULE LIST

アンロード

MODULE UNLOAD mymodule
  • The simplest module you can write
#include "redismodule.h"
#include <stdlib.h>

int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
    RedisModule_ReplyWithLongLong(ctx,rand());
    return REDISMODULE_OK;
}

int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
    if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
        == REDISMODULE_ERR) return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"helloworld.rand",
        HelloworldRand_RedisCommand) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    return REDISMODULE_OK;
}
  • Module initialization
int RedisModule_Init(RedisModuleCtx *ctx, const char *modulename,
                     int module_version, int api_version);

int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *cmdname,
                              RedisModuleCmdFunc cmdfunc);

新規コマンド作成のためのプロトタイプ

int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
  • Setup and dependencies of a Redis module

    • 新規 Redis モジュール作成のために、redismodule.h をコピーしてくる
  • Passing configuration parameters to Redis modules

loadmodule mymodule.so foo bar 1234
  • Working with RedisModuleString objects
const char *RedisModule_StringPtrLen(RedisModuleString *string, size_t *len);
RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len);
void RedisModule_FreeString(RedisModuleString *str);
# 文字列の例
RedisModuleString *mystr = RedisModule_CreateStringFromLongLong(ctx,10);


# 数の例
long long myval;
if (RedisModule_StringToLongLong(ctx,argv[1],&myval) == REDISMODULE_OK) {
    /* Do something with 'myval' */
}
  • Calling Redis commands
RedisModuleCallReply *reply;
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
- フォーマット指定子
	- c -- Null terminated C string pointer.
	- b -- C buffer, two arguments needed: C string pointer and size_t length.
	- s -- RedisModuleString as received in argv or by other Redis module APIs returning a RedisModuleString object.
	- l -- Long long integer.
	- v -- Array of RedisModuleString objects.
	- ! -- This modifier just tells the function to replicate the command to slaves and AOF. It is ignored from the point of view of arguments parsing.
  • Working with RedisModuleCallReply objects.
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) {
    long long myval = RedisModule_CallReplyInteger(reply);
    /* Do something with myval. */
}


size_t reply_len = RedisModule_CallReplyLength(reply);


long long reply_integer_val = RedisModule_CallReplyInteger(reply);


RedisModuleCallReply *subreply;
subreply = RedisModule_CallReplyArrayElement(reply,idx);


size_t len;
char *ptr = RedisModule_CallReplyStringPtr(reply,&len);


RedisModuleString *mystr = RedisModule_CreateStringFromCallReply(myreply);
- Valid reply types
	- REDISMODULE_REPLY_STRING Bulk string or status replies.
	- REDISMODULE_REPLY_ERROR Errors.
	- REDISMODULE_REPLY_INTEGER Signed 64 bit integers.
	- REDISMODULE_REPLY_ARRAY Array of replies.
	- REDISMODULE_REPLY_NULL NULL reply.
  • Releasing call reply objects : RedisModule_FreeCallReply
    • エラーを返す: RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err);
RedisModule_ReplyWithError(ctx,"ERR invalid arguments");

RedisModule_ReplyWithLongLong(ctx,12345);

RedisModule_ReplyWithSimpleString(ctx,"OK");

int RedisModule_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len);
int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str);

RedisModule_ReplyWithArray(ctx,2);
RedisModule_ReplyWithStringBuffer(ctx,"age",3);
RedisModule_ReplyWithLongLong(ctx,22);

RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);

RedisModule_ReplySetArrayLength(ctx, number_of_items);
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
number_of_factors = 0;
while(still_factors) {
    RedisModule_ReplyWithLongLong(ctx, some_factor);
    number_of_factors++;
}
RedisModule_ReplySetArrayLength(ctx, number_of_factors);
  • Arity and type checks
if (argc != 2) return RedisModule_WrongArity(ctx);
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
    REDISMODULE_READ|REDISMODULE_WRITE);

int keytype = RedisModule_KeyType(key);
if (keytype != REDISMODULE_KEYTYPE_STRING &&
    keytype != REDISMODULE_KEYTYPE_EMPTY)
{
    RedisModule_CloseKey(key);
    return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
}
  • Low level access to keys
RedisModuleKey *key;
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);
RedisModule_CloseKey(key);
  • Getting the key type
int keytype = RedisModule_KeyType(key);
- 返り値
	- REDISMODULE_KEYTYPE_EMPTY
	- REDISMODULE_KEYTYPE_STRING
	- REDISMODULE_KEYTYPE_LIST
	- REDISMODULE_KEYTYPE_HASH
	- REDISMODULE_KEYTYPE_SET
	- REDISMODULE_KEYTYPE_ZSET
  • Creating new keys
RedisModuleKey *key;
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
    RedisModule_StringSet(key,argv[2]);
}
  • Deleting keys: RedisModule_DeleteKey(key);

  • Managing key expires (TTLs)

mstime_t RedisModule_GetExpire(RedisModuleKey *key);

int RedisModule_SetExpire(RedisModuleKey *key, mstime_t expire);
  • 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)を使用

size_t len, j;
char *myptr = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE);
for (j = 0; j < len; j++) myptr[j] = 'A';
RedisModule_StringTruncate(mykey,1024);
  • List type API
int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele);
RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where);
- where で以下のマクロを指定
	- REDISMODULE_LIST_HEAD
	- REDISMODULE_LIST_TAIL
  • 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

reply = RedisModule_Call(ctx,"INCR","!sc",argv[1],"10");

影響がないか確認

RedisModule_ReplicateVerbatim(ctx);

どのコマンドをレプリケーションするべきか指定可能

RedisModule_Replicate(ctx,"INCRBY","cl","foo",my_increment);
  • Automatic memory management : RedisModule_AutoMemory(ctx);

    • 以下を自動でメモリ管理
      • キーのクローズ
      • リプライの解放
      • RedisModuleString オブジェクトの解放
  • Allocating memory into modules

void *RedisModule_Alloc(size_t bytes);
void* RedisModule_Realloc(void *ptr, size_t bytes);
void RedisModule_Free(void *ptr);
void RedisModule_Calloc(size_t nmemb, size_t size);
char *RedisModule_Strdup(const char *str);
  • Pool allocator
void *RedisModule_PoolAlloc(RedisModuleCtx *ctx, size_t bytes);
  • Writing commands compatible with Redis Cluster
    • module.c の実装を参考にコマンドを書く。
RedisModule_IsKeysPositionRequest(ctx);
RedisModule_KeyAtPos(ctx,pos);

Implementing native data types

  • URL: https://redis.io/topics/modules-native-types

  • Redis の元々ある型は、/modules/hellotype.c で見ることができる。

  • Registering a new data type

static RedisModuleType *MyType;
#define MYTYPE_ENCODING_VERSION 0

int RedisModule_OnLoad(RedisModuleCtx *ctx) {
RedisModuleTypeMethods tm = {
    .version = REDISMODULE_TYPE_METHOD_VERSION,
    .rdb_load = MyTypeRDBLoad,
    .rdb_save = MyTypeRDBSave,
    .aof_rewrite = MyTypeAOFRewrite,
    .free = MyTypeFree
};

    MyType = RedisModule_CreateDataType(ctx, "MyType-AZ",
    MYTYPE_ENCODING_VERSION, &tm);
    if (MyType == NULL) return REDISMODULE_ERR;
}
  • rdb_load, rdb_save, aof_rewrite, digest, free, mem_usage は、全て以下のプロトタイプのコールバック関数
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
typedef size_t (*RedisModuleTypeMemUsageFunc)(void *value);
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
typedef void (*RedisModuleTypeFreeFunc)(void *value);
  • 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()関数で新たな型を登録後、以下のように実装
RedisModuleKey *key = RedisModule_OpenKey(ctx,keyname,REDISMODULE_WRITE);
struct some_private_struct *data = createMyDataStructure();
RedisModule_ModuleTypeSetValue(key,MyType,data);
struct some_private_struct *data;
data = RedisModule_ModuleTypeGetValue(key);
:
:
if (RedisModule_ModuleTypeGetType(key) == MyType) {
    /* ... do something ... */
}

鍵が空かといったチェックも必要

RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
    REDISMODULE_READ|REDISMODULE_WRITE);
int type = RedisModule_KeyType(key);
if (type != REDISMODULE_KEYTYPE_EMPTY &&
    RedisModule_ModuleTypeGetType(key) != MyType)
{
    return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
}

鍵の型が正しいことを確認できた場合、以下のように処理

/* Create an empty value object if the key is currently empty. */
struct some_private_struct *data;
if (type == REDISMODULE_KEYTYPE_EMPTY) {
    data = createMyDataStructure();
    RedisModule_ModuleTypeSetValue(key,MyTyke,data);
} else {
    data = RedisModule_ModuleTypeGetValue(key);
}
/* Do something with 'data'... */
  • Free method
typedef void (*RedisModuleTypeFreeFunc)(void *value);

実装例

void MyTypeFreeCallback(void *value) {
    RedisModule_Free(value);
}
  • RDB load and save methods
void RedisModule_SaveUnsigned(RedisModuleIO *io, uint64_t value);
uint64_t RedisModule_LoadUnsigned(RedisModuleIO *io);
void RedisModule_SaveSigned(RedisModuleIO *io, int64_t value);
int64_t RedisModule_LoadSigned(RedisModuleIO *io);
void RedisModule_SaveString(RedisModuleIO *io, RedisModuleString *s);
void RedisModule_SaveStringBuffer(RedisModuleIO *io, const char *str, size_t len);
RedisModuleString *RedisModule_LoadString(RedisModuleIO *io);
char *RedisModule_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr);
void RedisModule_SaveDouble(RedisModuleIO *io, double value);
double RedisModule_LoadDouble(RedisModuleIO *io);

以下のような独自の型の場合

struct double_array {
    size_t count;
    double *values;
};

以下のようにラッパー関数を実装

void DoubleArrayRDBSave(RedisModuleIO *io, void *ptr) {
    struct dobule_array *da = ptr;
    RedisModule_SaveUnsigned(io,da->count);
    for (size_t j = 0; j < da->count; j++)
        RedisModule_SaveDouble(io,da->values[j]);
}
void *DoubleArrayRDBLoad(RedisModuleIO *io, int encver) {
    if (encver != DOUBLE_ARRAY_ENC_VER) {
        /* We should actually log an error here, or try to implement
           the ability to load older versions of our data structure. */
        return NULL;
    }

    struct double_array *da;
    da = RedisModule_Alloc(sizeof(*da));
    da->count = RedisModule_LoadUnsigned(io);
    da->values = RedisModule_Alloc(da->count * sizeof(double));
    for (size_t j = 0; j < da->count; j++)
        da->values = RedisModule_LoadDouble(io);
    return da;
}
  • AOF rewriting : void RedisModule_EmitAOF(RedisModuleIO *io, const char *cmdname, const char *fmt, ...);
  • Handling multiple encodings
  • Allocating memory : RedisModule_Alloc()関数
    • malloc() のようなライブラリを使っている場合、以下の様なマクロを定義して置き換える方法もある。
#define malloc RedisModule_Alloc
#define realloc RedisModule_Realloc
#define free RedisModule_Free
#define strdup RedisModule_Strdup

Blocking operations

  • URL: https://redis.io/topics/modules-blocking-ops

  • Blocking commands in Redis modules

    • コマンドでは、BLPOP, BRPOP
    • 実験的な API で、REDISMODULE_EXPERIMENTAL_API を定義している場合に使うことが出来る
#define REDISMODULE_EXPERIMENTAL_API
#include "redismodule.h"
  • How blocking and resuming works.
    • 以下の関数で、クライアントはブロック状態/アンブロック状態
RedisModuleBlockedClient *RedisModule_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms);

int RedisModule_UnblockClient(RedisModuleBlockedClient *bc, void *privdata);
void *RedisModule_GetBlockedClientPrivateData(RedisModuleCtx *ctx);

コマンド実装例

int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
                         int argc)
{
    RedisModuleBlockedClient *bc =
        RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);

    pthread_t tid;
    pthread_create(&tid,NULL,threadmain,bc);

    return REDISMODULE_OK;
}

void *threadmain(void *arg) {
    RedisModuleBlockedClient *bc = arg;

    sleep(1); /* Wait one second and unblock. */
    RedisModule_UnblockClient(bc,NULL);
}
int reply_func(RedisModuleCtx *ctx, RedisModuleString **argv,
               int argc)
{
    return RedisModule_ReplyWithSimpleString(ctx,"Hello!");
}

int timeout_func(RedisModuleCtx *ctx, RedisModuleString **argv,
               int argc)
{
    return RedisModule_ReplyWithNull(ctx);
}

クライアントへ何を返すべきか分かるように、threadmain 関数を以下のように改良

void *threadmain(void *arg) {
    RedisModuleBlockedClient *bc = arg;

    sleep(1); /* Wait one second and unblock. */

    long *mynumber = RedisModule_Alloc(sizeof(long));
    *mynumber = rand();
    RedisModule_UnblockClient(bc,mynumber);
}
int reply_func(RedisModuleCtx *ctx, RedisModuleString **argv,
               int argc)
{
    long *mynumber = RedisModule_GetBlockedClientPrivateData(ctx);
    /* IMPORTANT: don't free mynumber here, but in the
     * free privdata callback. */
    return RedisModule_ReplyWithLongLong(ctx,mynumber);
}
void free_privdata(void *privdata) {
    RedisModule_Free(privdata);
}
  • Aborting the blocking of a client
    • int RedisModule_AbortBlock(RedisModuleBlockedClient *bc);
    • 以下のようにして使う。この場合、スレッドを作成しようとしてもエラーを返す。
int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
                         int argc)
{
    RedisModuleBlockedClient *bc =
        RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);

    pthread_t tid;
    if (pthread_create(&tid,NULL,threadmain,bc) != 0) {
        RedisModule_AbortBlock(bc);
        RedisModule_ReplyWithError(ctx,"Sorry can't create a thread");
    }

    return REDISMODULE_OK;
}
  • Implementing the command, reply and timeout callback using a single function
int RedisModule_IsBlockedReplyRequest(RedisModuleCtx *ctx);
int RedisModule_IsBlockedTimeoutRequest(RedisModuleCtx *ctx);

実装例

int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
                         int argc)
{
    if (RedisModule_IsBlockedReplyRequest(ctx)) {
        long *mynumber = RedisModule_GetBlockedClientPrivateData(ctx);
        return RedisModule_ReplyWithLongLong(ctx,mynumber);
    } else if (RedisModule_IsBlockedTimeoutRequest) {
        return RedisModule_ReplyWithNull(ctx);
    }

    RedisModuleBlockedClient *bc =
        RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);

    pthread_t tid;
    if (pthread_create(&tid,NULL,threadmain,bc) != 0) {
        RedisModule_AbortBlock(bc);
        RedisModule_ReplyWithError(ctx,"Sorry can't create a thread");
    }

    return REDISMODULE_OK;
}
  • References
    • Neural Redis: https://github.com/antirez/neural-redis

Redis modules API reference

  • URL: https://redis.io/topics/modules-api-ref
  • Redis モジュール API リファレンス ★5
void *RedisModule_ lloc(size_t bytes)         
void *RedisModule_Calloc(size_t nmemb, size_t size)         
void* RedisModule_Realloc(void *ptr, size_t bytes)         
void RedisModule_Free(void *ptr)         
char *RedisModule_Strdup(const char *str)         
void *RedisModule_Pool lloc(RedisModuleCt  *ct , size_t bytes)         
int RedisModule_Get pi(const char *funcname, void **targetPtrPtr)         
int RedisModule_IsKeysPositionRequest(RedisModuleCt  *ct )         
void RedisModule_Key tPos(RedisModuleCt  *ct , int pos)         
if (RedisModule_IsKeysPositionRequest(ct )) {            RedisModule_Key tPos(ct ,1)             RedisModule_Key tPos(ct ,2)         }        
int RedisModule_CreateCommand(RedisModuleCt  *ct , const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep)         
 int MyCommand_RedisCommand(RedisModuleCt  *ct , RedisModuleString **argv, int argc)         
void RedisModule_SetModule ttribs(RedisModuleCt  *ct , const char *name, int ver, int apiver)         
long long RedisModule_Milliseconds(void)         
void RedisModule_ utoMemory(RedisModuleCt  *ct )         
RedisModuleString *RedisModule_CreateString(RedisModuleCt  *ct , const char *ptr, size_t len)         
RedisModuleString *RedisModule_CreateStringPrintf(RedisModuleCt  *ct , const char *fmt, ...)         
RedisModuleString *RedisModule_CreateStringFromLongLong(RedisModuleCt  *ct , long long ll)         
RedisModuleString *RedisModule_CreateStringFromString(RedisModuleCt  *ct , const RedisModuleString *str)         
void RedisModule_FreeString(RedisModuleCt  *ct , RedisModuleString *str)         
void RedisModule_RetainString(RedisModuleCt  *ct , RedisModuleString *str)         
const char *RedisModule_StringPtrLen(const RedisModuleString *str, size_t *len)         
int RedisModule_StringToLongLong(const RedisModuleString *str, long long *ll)         
int RedisModule_StringToDouble(const RedisModuleString *str, double *d)         
int RedisModule_StringCompare(RedisModuleString *a, RedisModuleString *b)         
int RedisModule_String ppendBuffer(RedisModuleCt  *ct , RedisModuleString *str, const char *buf, size_t len)         
int RedisModule_Wrong rity(RedisModuleCt  *ct )         
if (argc != 3) return RedisModule_Wrong rity(ct )         
int RedisModule_ReplyWithLongLong(RedisModuleCt  *ct , long long ll)         
int RedisModule_ReplyWithError(RedisModuleCt  *ct , const char *err)         
RedisModule_ReplyWithError(ct , quot ERR Wrong Type quot )         
RedisModule_ReplyWithError(ct , quot Wrong Type quot )         
int RedisModule_ReplyWithSimpleString(RedisModuleCt  *ct , const char *msg)         
int RedisModule_ReplyWith rray(RedisModuleCt  *ct , long len)         
void RedisModule_ReplySet rrayLength(RedisModuleCt  *ct , long len)         
 RedisModule_ReplyWith rray(ct ,REDISMODULE_POSTPONED_ RR Y_LEN)          RedisModule_ReplyWithLongLong(ct ,1)          RedisModule_ReplyWith rray(ct ,REDISMODULE_POSTPONED_ RR Y_LEN)          RedisModule_ReplyWithLongLong(ct ,1 )          RedisModule_ReplyWithLongLong(ct ,2 )          RedisModule_ReplyWithLongLong(ct ,3 )          RedisModule_ReplySet rrayLength(ct ,3)  // Set len of 1 ,2 ,3  array.         RedisModule_ReplySet rrayLength(ct ,2)  // Set len of top array        
int RedisModule_ReplyWithStringBuffer(RedisModuleCt  *ct , const char *buf, size_t len)         
int RedisModule_ReplyWithString(RedisModuleCt  *ct , RedisModuleString *str)         
int RedisModule_ReplyWithNull(RedisModuleCt  *ct )         
int RedisModule_ReplyWithCallReply(RedisModuleCt  *ct , RedisModuleCallReply *reply)         
int RedisModule_ReplyWithDouble(RedisModuleCt  *ct , double d)         
int RedisModule_Replicate(RedisModuleCt  *ct , const char *cmdname, const char *fmt, ...)         
int RedisModule_ReplicateVerbatim(RedisModuleCt  *ct )         
unsigned long long RedisModule_GetClientId(RedisModuleCt  *ct )         
int RedisModule_GetSelectedDb(RedisModuleCt  *ct )         
int RedisModule_SelectDb(RedisModuleCt  *ct , int newid)         
void *RedisModule_OpenKey(RedisModuleCt  *ct , robj *keyname, int mode)         
void RedisModule_CloseKey(RedisModuleKey *key)         
int RedisModule_KeyType(RedisModuleKey *key)         
size_t RedisModule_ValueLength(RedisModuleKey *key)         
int RedisModule_DeleteKey(RedisModuleKey *key)         
mstime_t RedisModule_GetE pire(RedisModuleKey *key)         
int RedisModule_SetE pire(RedisModuleKey *key, mstime_t e pire)         
int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str)         
char *RedisModule_StringDM (RedisModuleKey *key, size_t *len, int mode)         
REDISMODULE_RE D -- Read access        REDISMODULE_WRITE -- Write access        
int RedisModule_StringTruncate(RedisModuleKey *key, size_t newlen)         
int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele)         
RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where)         
int RedisModule_Zset ddFlagsToCoreFlags(int flags)         
int RedisModule_Zset ddFlagsFromCoreFlags(int flags)         
int RedisModule_Zset dd(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr)         
REDISMODULE_Z DD_XX: Element must already e ist. Do nothing otherwise.        REDISMODULE_Z DD_NX: Element must not e ist. Do nothing otherwise.        
REDISMODULE_Z DD_ DDED: The new element was added to the sorted set.        REDISMODULE_Z DD_UPD TED: The score of the element was updated.        REDISMODULE_Z DD_NOP: No operation was performed because XX or NX flags.        
int RedisModule_ZsetIncrby(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore)         
int RedisModule_ZsetRem(RedisModuleKey *key, RedisModuleString *ele, int *deleted)         
int RedisModule_ZsetScore(RedisModuleKey *key, RedisModuleString *ele, double *score)         
void RedisModule_ZsetRangeStop(RedisModuleKey *key)         
int RedisModule_ZsetRangeEndReached(RedisModuleKey *key)         
int RedisModule_ZsetFirstInScoreRange(RedisModuleKey *key, double min, double ma , int mine , int ma e )         
int RedisModule_ZsetLastInScoreRange(RedisModuleKey *key, double min, double ma , int mine , int ma e )         
int RedisModule_ZsetFirstInLe Range(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *ma )         
int RedisModule_ZsetLastInLe Range(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *ma )         
RedisModuleString *RedisModule_ZsetRangeCurrentElement(RedisModuleKey *key, double *score)         
int RedisModule_ZsetRangeNe t(RedisModuleKey *key)         
int RedisModule_ZsetRangePrev(RedisModuleKey *key)         
int RedisModule_HashSet(RedisModuleKey *key, int flags, ...)         
 RedisModule_HashSet(key,REDISMODULE_H SH_NONE,argv[1],argv[2],NULL)         
 RedisModule_HashSet(key,REDISMODULE_H SH_NONE,argv[1],                             REDISMODULE_H SH_DELETE,NULL)         
REDISMODULE_H SH_NX: The operation is performed only if the field was not                             already e isting in the hash.        REDISMODULE_H SH_XX: The operation is performed only if the field was                             already e isting, so that a new value could be                             associated to an e isting filed, but no new fields                             are created.        REDISMODULE_H SH_CFIELDS: The field names passed are null terminated C                                  strings instead of RedisModuleString objects.        
 RedisModule_HashSet(key,REDISMODULE_H SH_CFIELDS, quot foo quot ,                             REDISMODULE_H SH_DELETE,NULL)         
int RedisModule_HashGet(RedisModuleKey *key, int flags, ...)         
 RedisModuleString *first, *second          RedisModule_HashGet(mykey,REDISMODULE_H SH_NONE,argv[1], amp first,                         argv[2], amp second,NULL)         
 RedisModuleString *username, *hashedpass          RedisModule_HashGet(mykey, quot username quot , amp username, quot hp quot , amp hashedpass, NULL)         
 int e ists          RedisModule_HashGet(mykey,argv[1], amp e ists,NULL)         
void RedisModule_FreeCallReply_Rec(RedisModuleCallReply *reply, int freenested)         
void RedisModule_FreeCallReply(RedisModuleCallReply *reply)         
int RedisModule_CallReplyType(RedisModuleCallReply *reply)         
size_t RedisModule_CallReplyLength(RedisModuleCallReply *reply)         
RedisModuleCallReply *RedisModule_CallReply rrayElement(RedisModuleCallReply *reply, size_t id )         
long long RedisModule_CallReplyInteger(RedisModuleCallReply *reply)         
const char *RedisModule_CallReplyStringPtr(RedisModuleCallReply *reply, size_t *len)         
RedisModuleString *RedisModule_CreateStringFromCallReply(RedisModuleCallReply *reply)         
RedisModuleCallReply *RedisModule_Call(RedisModuleCt  *ct , const char *cmdname, const char *fmt, ...)         
const char *RedisModule_CallReplyProto(RedisModuleCallReply *reply, size_t *len)         
moduleType *RedisModule_CreateDataType(RedisModuleCt  *ct , const char *name, int encver, void *typemethods_ptr)         
 // Optional fields         .digest = myType_DigestCallBack,         .mem_usage = myType_MemUsageCallBack,        
 static RedisModuleType *BalancedTreeType                  int RedisModule_OnLoad(RedisModuleCt  *ct ) {             // some code here ...             BalancedTreeType = RM_CreateDataType(...)          }        
int RedisModule_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value)         
moduleType *RedisModule_ModuleTypeGetType(RedisModuleKey *key)         
void *RedisModule_ModuleTypeGetValue(RedisModuleKey *key)         
void RedisModule_SaveUnsigned(RedisModuleIO *io, uint64_t value)         
uint64_t RedisModule_LoadUnsigned(RedisModuleIO *io)         
void RedisModule_SaveSigned(RedisModuleIO *io, int64_t value)         
int64_t RedisModule_LoadSigned(RedisModuleIO *io)         
void RedisModule_SaveString(RedisModuleIO *io, RedisModuleString *s)         
void RedisModule_SaveStringBuffer(RedisModuleIO *io, const char *str, size_t len)         
RedisModuleString *RedisModule_LoadString(RedisModuleIO *io)         
char *RedisModule_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr)         
void RedisModule_SaveDouble(RedisModuleIO *io, double value)         
double RedisModule_LoadDouble(RedisModuleIO *io)         
void RedisModule_SaveFloat(RedisModuleIO *io, float value)         
float RedisModule_LoadFloat(RedisModuleIO *io)         
void RedisModule_Digest ddStringBuffer(RedisModuleDigest *md, unsigned char *ele, size_t len)         
foreach element {             ddElement(element)             EndSequence()         }        
foreach key,value {             ddElement(key)              ddElement(value)             EndSquence()         }        
foreach element {             ddElement(element)         }        EndSequence()         
void RedisModule_Digest ddLongLong(RedisModuleDigest *md, long long ll)         
void RedisModule_DigestEndSequence(RedisModuleDigest *md)         
void RedisModule_Emit OF(RedisModuleIO *io, const char *cmdname, const char *fmt, ...)         
void RedisModule_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_list ap)         
 RM_Log()         RM_LogIOError()        
void RedisModule_Log(RedisModuleCt  *ct , const char *levelstr, const char *fmt, ...)         
void RedisModule_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...)         
RedisModuleBlockedClient *RedisModule_BlockClient(RedisModuleCt  *ct , RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms)         
reply_callback:  called after a successful RedisModule_UnblockClient()                         call in order to reply to the client and unblock it.                reply_timeout:   called when the timeout is reached in order to send an                         error to the client.                free_privdata:   called in order to free the privata data that is passed                         by RedisModule_UnblockClient() call.        
int RedisModule_UnblockClient(RedisModuleBlockedClient *bc, void *privdata)         
int RedisModule_ bortBlock(RedisModuleBlockedClient *bc)         
int RedisModule_IsBlockedReplyRequest(RedisModuleCt  *ct )         
int RedisModule_IsBlockedTimeoutRequest(RedisModuleCt  *ct )         
void *RedisModule_GetBlockedClientPrivateData(RedisModuleCt  *ct )         
RedisModuleCt  *RedisModule_GetThreadSafeConte t(RedisModuleBlockedClient *bc)         
RedisModule_ThreadSafeCallStart(ct )         ... make your call here ...        RedisModule_ThreadSafeCallStop(ct )         
void RedisModule_FreeThreadSafeConte t(RedisModuleCt  *ct )         
void RedisModule_ThreadSafeConte tLock(RedisModuleCt  *ct )         
void RedisModule_ThreadSafeConte tUnlock(RedisModuleCt  *ct )         

Tutorials & FAQ

Introduction to Redis data types

  • URL: https://redis.io/topics/data-types-intro

  • Redis keys

    • キーが長すぎる(1024バイト等)のは、メモリ効率や検索のコスト的にも良くない。SHA1等でハッシュ化するのも良い。
    • キーが短すぎるのも読みにくさが悪ければ良くない。
    • スキーマに遵守してみる
    • キーの最大サイズは512MB
  • Redis Strings

    • 最大512MB
    • SET コマンドで既にキーが存在している場合は失敗にする
    > set mykey newval nx
    (nil)
    
    • INCR, INCRBY, DECR, DECRBYはレースコンディションの心配
    • GETSETを使えば、新たな値を設定し、古いキーを返す
    • MSETやMGETで一気に値の取得、設定をできる。
    > mset a 10 b 20 c 30
    OK
    > mget a b c
    1) "10"
    2) "20"
    3) "30"
    
  • 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を使う。

Writing a simple Twitter clone with PHP and Redis

  • URL: https://redis.io/topics/twitter-clone

  • ログイン処理の実装

function isLoggedIn() {
    global $User, $_COOKIE;

    if (isset($User)) return true;

    if (isset($_COOKIE['auth'])) {
        $r = redisLink();
        $authcookie = $_COOKIE['auth'];
        if ($userid = $r->hget("auths",$authcookie)) {
            if ($r->hget("user:$userid","auth") != $authcookie) return false;
            loadUserInfo($userid);
            return true;
        }
    }
    return false;
}

function loadUserInfo($userid) {
    global $User;

    $r = redisLink();
    $User['id'] = $userid;
    $User['username'] = $r->hget("user:$userid","username");
    return true;
}
  • ログイン時の実装
include("retwis.php");

if (!isLoggedIn()) {
    header("Location: index.php");
    exit;
}

$r = redisLink();
$newauthsecret = getrand();
$userid = $User['id'];
$oldauthsecret = $r->hget("user:$userid","auth");

$r->hset("user:$userid","auth",$newauthsecret);
$r->hset("auths",$newauthsecret,$userid);
$r->hdel("auths",$oldauthsecret);

header("Location: index.php");
  • ツイート投稿時
include("retwis.php");

if (!isLoggedIn() || !gt("status")) {
    header("Location:index.php");
    exit;
}

$r = redisLink();
$postid = $r->incr("next_post_id");
$status = str_replace("\n"," ",gt("status"));
$r->hmset("post:$postid","user_id",$User['id'],"time",time(),"body",$status);
$followers = $r->zrange("followers:".$User['id'],0,-1);
$followers[] = $User['id']; /* Add the post to our own posts too */

foreach($followers as $fid) {
    $r->lpush("posts:$fid",$postid);
}
# Push the post on the timeline, and trim the timeline to the
# newest 1000 elements.
$r->lpush("timeline",$postid);
$r->ltrim("timeline",0,1000);

header("Location: index.php");

Auto complete with Redis

  • ZRANGEBYLDEXのデモ
    • URL: http://autocomplete.redis.io/

Data types short summary

  • 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

FAQ

  • 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 )をスケールさせるために始められた

Administration

Redis-cli

  • URL: https://redis.io/topics/rediscli

コマンドライン使用方法

$ redis-cli incr mycounter
(integer) 7

$ redis-cli incr mycounter > /tmp/output.txt
$ cat /tmp/output.txt
8

$ redis-cli --raw incr mycounter
9
# Cf. --no-raw


$ redis-cli -h redis15.localnet.org -p 6390 ping
PONG
# デフォルト: 127.0.0.1 port 6379

$ redis-cli -a myUnguessablePazzzzzword123 ping
PONG


# データベース番号を指定して実行
$ redis-cli flushall
OK
$ redis-cli -n 1 incr a
(integer) 1
$ redis-cli -n 1 incr a
(integer) 2
$ redis-cli -n 2 incr a
(integer) 1

$ redis-cli -u redis://p%40ssw0rd@redis-16379.hosted.com:16379/0 ping
PONG
  • 他のプログラムから実行する方法

    • stdinで取り込む方法
    $ redis-cli -x set foo < /etc/services
    OK
    $ redis-cli getrange foo 0 50
    "#\n# Network services, Internet style\n#\n# Note that "
    
    • テキストを展開してパイプでredis-cliで渡す方法
    $ cat /tmp/commands.txt
    set foo 100
    incr foo
    append foo xxx
    get foo
    $ cat /tmp/commands.txt | redis-cli
    OK
    (integer) 101
    (integer) 6
    "101xxx"
    
  • 同じコマンドを連続して実行

    • キーの値の変化やINFOコマンドの内容を確認したいときなどに便利
    • -r <count> and -i <delay> オプション
    • 無限に繰り返す時は -1 オプション
$ redis-cli -r 5 incr foo
(integer) 1
(integer) 2
(integer) 3
(integer) 4
(integer) 5

$ redis-cli -r -1 -i 1 INFO | grep rss_human
used_memory_rss_human:1.38M
used_memory_rss_human:1.38M
used_memory_rss_human:1.38M
... a new line will be printed each second ...
  • CSV 出力 : --csv オプション
  • Lua スクリプトを実行 : --eval オプション
$ cat /tmp/script.lua
return redis.call('set',KEYS[1],ARGV[1])
$ redis-cli --eval /tmp/script.lua foo , bar
OK

インタラクティブモード

$ redis-cli
127.0.0.1:6379> ping
PONG
  • 接続、再接続の処理
    • CONNECT hostname port で接続
    • 切断が検知された場合は自動で復旧
127.0.0.1:6379> connect metal 6379
metal:6379> ping
PONG
  • 編集、履歴、補完

    • linenoise line editing library(https://github.com/antirez/linenoise )を使用
  • 同じコマンドを繰り返し実行

127.0.0.1:6379> 5 incr mycounter
(integer) 1
(integer) 2
(integer) 3
(integer) 4
(integer) 5
  • help コマンド
    • カテゴリ種類: @generic, @list, @set, @sorted_set, @hash, @pubsub, @transactions, @connection, @server, @scripting, @hyperloglog.
help @<category>
help <commandname>
  • ターミナルスクリーンのクリア : clear コマンド

操作の特殊モード

  • 継続的なstatsモード
    • -i <interval> オプションでインターバルを指定
$ redis-cli --stat
------- data ------ --------------------- load -------------------- - child -
keys       mem      clients blocked requests            connections
506        1015.00K 1       0       24 (+0)             7
506        1015.00K 1       0       25 (+1)             7
506        3.40M    51      0       60461 (+60436)      57
  • サイズの大きいキーのスキャン
$ redis-cli --bigkeys

# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).

[00.00%] Biggest string found so far 'key-419' with 3 bytes
[05.14%] Biggest list   found so far 'mylist' with 100004 items
[35.77%] Biggest string found so far 'counter:__rand_int__' with 6 bytes
[73.91%] Biggest hash   found so far 'myobject' with 3 fields

-------- summary -------

Sampled 506 keys in the keyspace!
Total key length in bytes is 3452 (avg len 6.82)
:
  • キーリストの一覧
    • --scan オプションを使用
    • --pattern '-11' のようにパターン検索も可能
$ redis-cli --scan | head -10
key-419
key-71
key-236
  • Pub/Subモード
$ redis-cli psubscribe '*'
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "*"
3) (integer) 1
  • Redis で実行されたコマンド監視
$ redis-cli monitor
OK
1460100081.165665 [0 127.0.0.1:51706] "set" "foo" "bar"
1460100083.053365 [0 127.0.0.1:51707] "get" "foo"
  • レイテンシー監視
    • 最大や平均を調べるには、--latency-history オプション
    • -i <interval> オプションでインターバルを指定できる
    • --latency-dist オプションで異なるASCII文字を有効化
    • --intrinsic-latency <test-time> オプションで本来のRedisのレイテンシー計測(Redisサーバ上での実行が必要)
$ redis-cli --latency
min: 0, max: 1, avg: 0.19 (427 samples)

$ redis-cli --latency-history
min: 0, max: 1, avg: 0.14 (1314 samples) -- 15.01 seconds range
min: 0, max: 1, avg: 0.18 (1299 samples) -- 15.00 seconds range
min: 0, max: 1, avg: 0.20 (113 samples)^C

$ redis-cli --latency-dist
(output not displayed, requires a color terminal, try it!)
  • RDBファイルのリモートバックアップ
$ redis-cli --rdb /tmp/dump.rdb
SYNC sent to master, writing 13256 bytes to '/tmp/dump.rdb'
Transfer finished with success.
$ echo $?
0
  • スレーブモード : --slave オプション

  • LRU シミュレーションを実行

    • --lru-test オプション
    • テストでのキーの総数の指定とmaxmemoryの指定が必要(maxmemoryを指定しないと全キーをメモリに載せるので100%使い切る可能性)
$ ./redis-cli --lru-test 10000000
156000 Gets/sec | Hits: 4552 (2.92%) | Misses: 151448 (97.08%)
153750 Gets/sec | Hits: 12906 (8.39%) | Misses: 140844 (91.61%)
159250 Gets/sec | Hits: 21811 (13.70%) | Misses: 137439 (86.30%)
151000 Gets/sec | Hits: 27615 (18.29%) | Misses: 123385 (81.71%)

Configuration

  • 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
slaveof 127.0.0.1 6380
  • コマンドラインから引数を渡す
    • コマンドラインから渡す引数のフォーマットは、頭に -- が必要な点を除けば redis.conf ファイルと同じフォーマットとなっている。
./redis-server --port 6380 --slaveof 127.0.0.1 6379
  • サーバー動作中の Redis コンフィグレーションの変更

    • CONFIG SETやCONFIG GET
    • 再起動で設定内容は消える。
    • Redis 2.8からの CONFIG REWRITE コマンドでredis.confと設定内容が異なる部分を書き換える
      • 項目が存在せずデフォルト値のままになっているものは追加しない。設定ファイル内のコメントは保持される。
  • Redis をキャッシュとして設定する

    • もし Redis を単純なキャッシュ用途で設計しており、すべてのキーに TTL を設定する場合には、その代わりに以下のような設定を利用してもよい。 (この例では最大メモリを 2MB としている)
maxmemory 2mb
maxmemory-policy allkeys-lru

Replication

  • 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 <password>
    • 設定ファイルで masterauth <password>
  • Redis2.8からマスターにアタッチされるスレーブの最低台数を指定できるようになった

    • 以下のパラメータ
      • min-slaves-to-write <number of slaves>
      • min-slaves-max-lag <number of seconds>
    • 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の際に便利

Persistence

  • 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 でスナップショットを転送

Redis Administration

  • 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 コマンドでサポートされない設定の変更が必要になる場合などである。

    • 以下の手順は、ダウンタイムを回避するための非常に一般的なものである。

        1. 新しい Redis 用のインスタンスを立ち上げ、現在稼働している Redis のスレーブにする。たいてい別のサーバーを立ち上げることになると思うが、十分なメモリ領域があるならば同じサーバー内で 2つの Redis を起動してもよい。
        1. もし単一のサーバーを使っているのであれば、マスターとは異なるポートを設定してスレーブを起動すること。そうでなければマスターのポート設定と干渉するので起動できない。
        1. 初回のレプリケーションの同期が完了するまで待つ(スレーブ側のログファイルを確認する)。
        1. INFO コマンドで、マスターと同じだけのキーがスレーブにあることを確認する。redis-cli でスレーブが意図した通りに動作していること、コマンドに応答することを確認する。
        1. CONFIG SET slave-read-only noでスレーブへの書き込みができるようにする。
        1. すべてのクライアントが新しい Redis を利用するように変更する(それはつまりスレーブ)。
        1. マスターがクエリを受け取らなくなったことを確認したら(MONITOR commandで確認できる)、SLAVEOF NO ONEコマンドでスレーブをマスターに昇格させ、それまでのマスターを停止する。
    • もしRedis Sentinel あるいは Redis Clusterを利用している場合、最も簡単にバージョンのアップグレードを行う方法はスレーブを順次アップグレードしていき、手動のフェイルオーバによってマスターをアップグレードされたものに切り替え、最終的に残りのスレーブもすべてアップグレードさせていくという方法になる。

    • 注意すべき点として、Redis クラスター 4.0 と Redis クラスター 3.2 はクラスターバスのプロトコルレベルで互換性がないので、この場合は全台の再起動が必要になるということだ。

Security

  • 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

Encryption

  • URL: https://redis.io/topics/encryption
  • Spiped が有効なケースが多い

Signals Handling

  • 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 コマンドで、エラーなしに、子プロセスを殺すことができる。

Connections Handling

  • 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上で実際に扱える値を上限値として設定する。

    • 以下のようなコマンドで現在のセッションおよびシステム単位の設定を変更することができます。

$ ulimit -Sn 100000 # This will only work if hard limit is big enough.
$ sysctl -w fs.file-max=100000
  • アウトプットバッファの上限

    • ハードリミット、ソフトリミット

      • ハードリミットは固定値で、この値に到達した時点でコネクションは即座に閉じられます。
      • ソフトリミットは時間によって計算され、例えば 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秒で有効化されている。このオプションは死んだピアを検出することに役立つ

High Availability

  • URL: https://redis.io/topics/sentinel

  • Redis Sentinel Documentation

    • 高可用性 を提供
    • 機能
      • 監視
      • 通知
      • 自動フェイルオーバー
      • 設定プロバイダー
  • Sentinel の実行

    • デフォルトで TCP の26379番ポートで稼働
    $ redis-sentinel /path/to/sentinel.conf
    $ redis-server /path/to/sentinel.conf --sentinel
    
  • 最低3台のSentinelインスタンスが必要で、それぞれ失敗条件が独立している必要あり(異なる AZ 等)

  • Sentinel の設定 : sentinel.conf, SENTINEL SET コマンド

    • 設定例
    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel down-after-milliseconds mymaster 60000
    sentinel failover-timeout mymaster 180000
    sentinel parallel-syncs mymaster 1
    
    sentinel monitor resque 192.168.1.3 6380 4
    sentinel down-after-milliseconds resque 10000
    sentinel failover-timeout resque 180000
    sentinel parallel-syncs resque 5
    
    • sentinel monitor <master-group-name> <ip> <port> <quorum>

      • 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 <ip>
        • sentinel announce-port <port>
    • マスターの INFO コマンド中のスレーブのリスト
  • クイックチュートリアル

port 5000
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
  • マスターの状態について Sentinel に聞く

    $ redis-cli -p 5000
    127.0.0.1:5000> sentinel master mymaster
     1) "name"
     2) "mymaster"
     3) "ip"
    
    • その他以下の様なコマンド
    SENTINEL slaves mymaster
    SENTINEL sentinels mymaster
    
  • 現在のマスターの IP アドレス取得

127.0.0.1:5000> SENTINEL get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6379"
  • フェイルオーバーをテスト
    • 以下を実行後、現在のマスターの IP アドレス取得して確認する
$ redis-cli -p 6379 DEBUG sleep 30

Sentinel API

  • Sentinel コマンド

    • PING
    • SENTINEL masters
    • SENTINEL master <master name>
    • SENTINEL slaves <master name>
    • SENTINEL sentinels <master name>
    • SENTINEL get-master-addr-by-name <master name>
    • SENTINEL reset <pattern>
    • SENTINEL failover <master name>
    • SENTINEL ckquorum <master name>
    • SENTINEL flushconfig
  • ランタイムで Sentinel を再設定

    • SENTINEL MONITOR <name> <ip> <port> <quorum>
    • SENTINEL REMOVE <name>
    • SENTINEL SET <name> <option> <value>
  • Pub/Sub Messages

    • SUBSCRIBE, PSUBSCRIBE (PUBLISH は使うことが出来ない)
    • フォーマット: <instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port>
  • Sentinel と Redis の認証

    • マスターの requirepass, スレーブの masterauth
    • requirepass の設定: sentinel auth-pass <master-group-name> <pass>
  • Sentinel 利用にあたってはクライアント側のサポートも必須

発展

  • SDOWN と ODOWN

    • Subjectively Down condition (SDOWN) : ローカルの状態
    • Objectively Down condition (ODOWN) : quorum パラメータの対象によるマスターの監視
  • フェイルオーバー時におけるマスターとなるスレーブ選出

    1. マスターからの切断時間
      • (down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state 以上の時間(INFO コマンドで確認)
    2. スレーブプライオリティ
    3. 処理されたレプリケーションのオフセット
    4. Run ID(辞書順)
  • TILT モード

    • Redis はコンピュータ時間に依存しているが、こちらが原因で思いがけない挙動になることを防止するために保護モードになる。
  • References

    • Guidelines for Redis clients with support for Redis Sentinel : https://redis.io/topics/sentinel-clients

Benchmarks

  • URL: https://redis.io/topics/benchmarks
Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]

 -h <hostname>      Server hostname (default 127.0.0.1)
 -p <port>          Server port (default 6379)
 -s <socket>        Server socket (overrides host and port)
 -a <password>      Password for Redis Auth
 -c <clients>       Number of parallel connections (default 50)
 -n <requests>      Total number of requests (default 100000)
 -d <size>          Data size of SET/GET value in bytes (default 2)
 --dbnum <db>       SELECT the specified db number (default 0)
 -k <boolean>       1=keep alive 0=reconnect (default 1)
 -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD
  Using this option the benchmark will expand the string __rand_int__
  inside an argument with a 12 digits number in the specified range
  from 0 to keyspacelen-1. The substitution changes every time a command
  is executed. Default tests use this to hit random keys in the
  specified range.
 -P <numreq>        Pipeline <numreq> requests. Default 1 (no pipeline).
 -q                 Quiet. Just show query/sec values
 --csv              Output in CSV format
 -l                 Loop. Run the tests forever
 -t <tests>         Only run the comma separated list of tests. The test
                    names are the same as the ones produced as output.
 -I                 Idle mode. Just open N idle connections and wait.
$ redis-benchmark -q -n 100000

# テストするコマンド指定
$ redis-benchmark -t set,lpush -n 100000 -q
SET: 74239.05 requests per second
LPUSH: 79239.30 requests per second

# 具体的なスクリプト指定
$ redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')"
script load redis.call('set','foo','bar'): 69881.20 requests per second




# キー空間のサイズ指定: -r オプション
$ redis-cli flushall
OK

$ redis-benchmark -t set -r 100000 -n 1000000
====== SET ======
  1000000 requests completed in 13.86 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

99.76% `<=` 1 milliseconds
99.98% `<=` 2 milliseconds
100.00% `<=` 3 milliseconds
100.00% `<=` 3 milliseconds
72144.87 requests per second

$ redis-cli dbsize
(integer) 99993



# パイプライン : -P オプション
$ redis-benchmark -n 1000000 -t set,get -P 16 -q
SET: 403063.28 requests per second
GET: 508388.41 requests per second
  • 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
# unix domain socket を使用
$ numactl -C 6 ./redis-benchmark -q -n 100000 -s /tmp/redis.sock -d 256

# TCP loopback を使用
$ numactl -C 6 ./redis-benchmark -q -n 100000 -d 256
  • 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

Redis Releases

  • URL: https://redis.io/topics/releases
  • リリースサイクル
    • unstable
    • development
    • frozen
    • release candidate
    • stable

Embedded and IoT

Redis on ARM and Raspberry Pi

  • URL: https://redis.io/topics/ARM

  • Redis4.0から、一般的な ARM、特にラズベリーパイを Linux/x86アーキテクチャと同様にサポート

  • Redis における /proc/cpu/alignment 要件

    • ARM における Linux では、アラインメントが適切でない場合にトラップしてカーネル内部で修正し、プログラムが SIGBUS を発する代わりに実行を続けられるようになっています。
    • Redis 4.0 以降のバージョンではこの問題に対応済みであり、カーネルで特定の設定を持つ必要はなくなりました。
    • アラインメントを修正する機能がカーネル側で無効になっていても、Redis は問題なく動作します。

Troubleshooting

Redis problems?

  • 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

Redis latency problems troubleshooting

  • URL: https://redis.io/topics/latency

  • チェックリスト

      1. サーバをブロックするようなスローコマンドが稼働していないか
      1. EC2ユーザーは、HVMベースの最近のEC2インスタンス(m3.medium等)を使っているか。そうでないとfork()が遅い
      1. transparent huge pagesをカーネルで無効化しているか
      • echo never > /sys/kernel/mm/transparent_hugepage/enabled で無効化してプロセス再起動
      1. 仮想マシンを使っている場合、Redisに関係ない intrinsic latency がないか。
      • ./redis-cli --intrinsic-latency 100 で最小のレイテンシー(ベースライン)を確認してみる。
      • サーバ側で実行する必要がある。
      1. 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で測定結果が見られる。
> INFO Stats
# Stats
total_connections_received:10
total_commands_processed:796661
instantaneous_ops_per_sec:3
total_net_input_bytes:28671383
total_net_output_bytes:505517208
instantaneous_input_kbps:0.14
instantaneous_output_kbps:4.63
rejected_connections:0
sync_full:2
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:135
migrate_cached_sockets:0
  • スワッピングによるレイテンシー
$ redis-cli -h testredis.meriux.ng.0001.usw2.cache.amazonaws.com info | grep process_id
process_id:1
$ cd /proc/1
$ ls
ls: cannot read symbolic link cwd: Permission denied
ls: cannot read symbolic link root: Permission denied
ls: cannot read symbolic link exe: Permission denied
attr        cmdline          cwd      fdinfo   limits     mem         net        oom_score      projid_map  sessionid  stat     task
auxv        comm             environ  gid_map  loginuid   mountinfo   ns         oom_score_adj  root        setgroups  statm    timerslack_ns
cgroup      coredump_filter  exe      io       map_files  mounts      numa_maps  pagemap        sched       smaps      status   uid_map
clear_refs  cpuset           fd       latency  maps       mountstats  oom_adj    personality    schedstat   stack      syscall  wchan
- /proc/<プロセスID>以下のsmapsというファイルでメモリのレイアウトの詳細が見られる。

```
[ec2-user@ip-172-31-19-138 1]$ sudo cat smaps | grep Swap:
Swap:                  0 kB
Swap:                  0 kB
Swap:                  0 kB
```

- メモリマップのサイズも出力してみる。

```
$ cat smaps | egrep '^(Swap|Size)'
```

- vmstatやiostatでも裏取り。

```
$ vmstat 1
$ iostat -xk 1
```
  • AOFやディスクI/Oによるレイテンシー
    • AOFはwrite()とfdatasync()の2つのシステムコールを基本的に使う
    • 遅いシステムコールの調査
$ sudo strace -f -p $(pidof redis-server) -T -e trace=fdatasync,write 2>&1 | grep -v '0.0' | grep -v unfinished
  • 失効によるレイテンシー

    • 同時に失効するキーが多いとレイテンシーの原因になる
  • Redis software watchdog

    • プロダクション環境でのデバッグに、2.6で試験的に導入されたRedis software watchdogが使える。
    • 以下のコマンドで有効化。終わったら値を0に設定する。
    CONFIG SET watchdog-period 500
    
    • サーバのブロックで例えば、DEBUG SLEEPコマンドが使われる。

SLOWLOG – Redis

  • URL: https://redis.io/commands/slowlog

  • パラメータ slowlog-log-slower-than と slowlog-max-len がある

  • SLOWLOG GET 10 のように直近N件のエントリを確認

  • SLOWLOG LEN で現在のスローログの長さ

  • SLOWLOG RESET でスローログをリセット

Redis latency monitoring framework – Redis

  • URL: https://redis.io/topics/latency-monitor

  • Redis 2.8.3から Latency Monitoring が導入

  • 以下のようにして有効化しておく

CONFIG SET latency-monitor-threshold 100
  • LATENCY コマンド
    • LATENCY LATEST
    • LATENCY HISTORY event-name
    • LATENCY RESET [event-name ... event-name]
    • LATENCY GRAPH event-name
    • LATENCY HISTORY
    • LATENCY DOCTOR

Redis debugging guide – Redis

  • URL: https://redis.io/topics/debugging

  • RedisのプロセスIDを取得して、gdbデバッグ

$ redis-cli info | grep process_id
process_id:58414
$ gdb /usr/local/bin/redis-server 58414
  • スタックトレースとプロセッサーのレジスター取得
(gdb) bt
(gdb) info registers
  • Redisは-O2の最適化オプションがされているがデバッグの時は大変になるので外す。

  • screen/tmuxの使用を推奨

  • References

    • Power Sessions with Screen: https://www.linuxjournal.com/article/6340

Others

  • Redis DBグループ: https://groups.google.com/forum/#!forum/redis-db

Redis Cluster

Redis Cluster tutorial

  • 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 <filename> : ユーザーではなく、Redis クラスターノードで自動保存に使われる設定ファイル
    • cluster-node-timeout <milliseconds>
    • cluster-slave-validity-factor <factor>
    • cluster-migration-barrier <count>
    • cluster-require-full-coverage <yes/no>
  • Creating and using a Redis Cluster

    port 7000
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    
    • クラスターの作成

      • redis-trib のコマンドが便利
        • gem install redis でインストール
        • Redis ソースコードの src/ 以下にある
      $ ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
      127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
      
      • 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を調べるために以下のコマンド

      $ redis-cli -p 7000 cluster nodes | grep myself
      97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5460
      
    • リシャーディングの終わりにクラスターのヘルス状態確認

      $ ./redis-trib.rb check 127.0.0.1:7000
      
  • Scripting a resharding operation

    • リシャーディングは、以下のコマンドで実行
$ ./redis-trib.rb reshard --from <node-id> --to <node-id> --slots <number of slots> --yes <host>:<port>
  • A more interesting example application
    • 一貫性のテスト
$ ruby consistency-test.rb
925 R (0 err) | 925 W (0 err) |
5030 R (0 err) | 5030 W (0 err) |
9261 R (0 err) | 9261 W (0 err) |
13517 R (0 err) | 13517 W (0 err) |
  • フェイルオーバーのテスト

    • 以下の構成

      $ redis-cli -p 7000 cluster nodes | grep master
      3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385482984082 0 connected 5960-10921
      2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 master - 0 1385482983582 0 connected 11423-16383
      97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5959 10922-11422
      
    • 以下のコマンドでフェイルオーバー

      $ redis-cli -p 7002 debug segfault
      Error: Server closed the connection
      
    • 手動フェイルオーバーは、 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 コマンドやクラスターの状態確認を行なう。

    • マスター

      $ ./redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000
      
    • レプリカ

      $ ./redis-trib.rb add-node --slave 127.0.0.1:7006 127.0.0.1:7000
      $ ./redis-trib.rb add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006 127.0.0.1:7000
      
      • CLUSTER REPLICATE コマンドで以下のようにしても追加することができる

        redis 127.0.0.1:7006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
        
  • ノード削除

    • ノード ID 取得
    $ redis-cli -p 7000 cluster nodes | grep slave | grep 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
    f093c80dde814da99c5cf72a7dd01590792b783b 127.0.0.1:7006 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617702 3 connected
    2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617198 3 connected
    
    • 実際に削除
    $ ./redis-trib del-node 127.0.0.1:7000 `<node-id>`
    
  • レプリカマイグレーション

    • 他のマスターへ、レプリカをマイグレーションすることができる。
    CLUSTER REPLICATE <master-node-id>
    
  • Redis クラスターのノードのアップグレード

    • スレーブ
      • 停止してアップデートしたバージョンで再起動
      • 再接続の際は異なるスレーブへ
    • マスター
      • CLUSTER FAILOVER コマンドでマスターをスレーブへ降格させて行なう
  • Redis クラスターのマイグレーション

    • クライアントを停止
    • 各マスターで BGREWRITEAOF コマンドで AOF ファイルを作成
    • スレーブを持たない、複数マスターを持つ Redis クラスターを作成
    • クラスターを停止して、AOF ファイルでデータ復旧
    • 新しい AOF ファイルで、クラスターを再起動
    • redis-trib fix コマンドで、各ノード hash slot に従ってキーをマイグレーションするようにクラスターを修正
    • 最後にクラスターがokか redis-trib check コマンドで確認
    • クライアントから接続

Redis Cluster specification

  • 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

def HASH_SLOT(key)
    s = key.index "{"
    if s
        e = key.index "}",s+1
        if e && e != s+1
            key = key[s+1..e-1]
        end
    end
    crc16(key) % 16384
end

C

unsigned int HASH_SLOT(char *key, int keylen) {
    int s, e; /* start-end indexes of { and } */

    /* Search the first occurrence of '{'. */
    for (s = 0; s < keylen; s++)
        if (key[s] == '{') break;

    /* No '{' ? Hash the whole key. This is the base case. */
    if (s == keylen) return crc16(key,keylen) & 16383;

    /* '{' found? Check if we have the corresponding '}'. */
    for (e = s+1; e < keylen; e++)
        if (key[e] == '}') break;

    /* No '}' or nothing between {} ? Hash the whole key. */
    if (e == keylen || e == s+1) return crc16(key,keylen) & 16383;

    /* If we are here there is both a { and a } on its right. Hash
     * what is in the middle between { and }. */
    return crc16(key+s+1,e-s-1) & 16383;
}
  • クラスターノードの属性

    • すべてのノードはそれぞれ異なった名前を持ちます。ノードの名前は 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 エラーを返します。

      GET x
      -MOVED 3999 127.0.0.1:6381
      
    • クライアントは、必須ではないものの、スロット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 と呼ばれ、スロットの範囲、その範囲に割り当てられたマスタおよびスレーブノード、といった情報を配列で返します。

      127.0.0.1:7000> cluster slots
      1) 1) (integer) 5461
         2) (integer) 10922
         3) 1) "127.0.0.1"
            2) (integer) 7001
         4) 1) "127.0.0.1"
            2) (integer) 7004
      2) 1) (integer) 0
         2) (integer) 5460
         3) 1) "127.0.0.1"
            2) (integer) 7000
         4) 1) "127.0.0.1"
            2) (integer) 7003
      
  • 複数のキーに関する操作

    • ハッシュタグのおかげで、クライアントは複数のキーを扱うことができます。
      • 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 の実装

/*
 * Copyright 2001-2010 Georges Menie (www.menie.org)
 * Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the University of California, Berkeley nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* CRC16 implementation according to CCITT standards.
 *
 * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
 * following parameters:
 *
 * Name                       : "XMODEM", also known as "ZMODEM", "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
 */

static const uint16_t crc16tab[256]= {
    0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
    0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
    0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
    0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
    0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
    0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
    0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
    0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
    0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
    0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
    0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
    0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
    0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
    0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
    0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
    0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
    0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
    0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
    0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
    0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
    0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
    0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
    0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
    0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
    0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
    0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
    0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
    0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
    0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
    0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
    0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
    0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
};

uint16_t crc16(const char *buf, int len) {
    int counter;
    uint16_t crc = 0;
    for (counter = 0; counter < len; counter++)
            crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
    return crc;
}

Other distributed systems based on Redis

Roshi

  • URL: https://github.com/soundcloud/roshi
  • Redis ベースの Go で実装されたタイムスタンプが付与されたイベント向けの大規模な CRDT セットの実装

Specifications

Redis Design Drafts

  • 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 で話す

Redis Protocol specification

  • 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

      *3\r\n
      $3\r\n
      foo\r\n
      $-1\r\n
      $3\r\n
      bar\r\n
      
    • 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\n となる。
C: *2\r\n
C: $4\r\n
C: LLEN\r\n
C: $6\r\n
C: mylist\r\n

S: :48293\r\n

Redis RDB format

  • URL: https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format

  • High Level Algorithm to parse RDB

----------------------------# RDB is a binary format. There are no new lines or spaces in the file.
52 45 44 49 53              # Magic String "REDIS"
00 00 00 03                 # RDB Version Number in big endian. In this case, version = 0003 = 3
----------------------------
FE 00                       # FE = code that indicates database selector. db number = 00
----------------------------# Key-Value pair starts
FD $unsigned int            # FD indicates "expiry time in seconds". After that, expiry time is read as a 4 byte unsigned int
$value-type                 # 1 byte flag indicating the type of value - set, map, sorted set etc.
$string-encoded-key         # The key, encoded as a redis string
$encoded-value              # The value. Encoding depends on $value-type
----------------------------
FC $unsigned long           # FC indicates "expiry time in ms". After that, expiry time is read as a 8 byte unsigned long
$value-type                 # 1 byte flag indicating the type of value - set, map, sorted set etc.
$string-encoded-key         # The key, encoded as a redis string
$encoded-value              # The value. Encoding depends on $value-type
----------------------------
$value-type                 # This key value pair doesn't have an expiry. $value_type guaranteed != to FD, FC, FE and FF
$string-encoded-key
$encoded-value
----------------------------
FE $length-encoding         # Previous db ends, next db starts. Database number read using length encoding.
----------------------------
...                         # Key value pairs for this database, additonal database

FF                          ## End of RDB file indicator
8 byte checksum             ## CRC 64 checksum of the entire file.

Internals

  • 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 は、以下のように定義
        struct sdshdr {
            long len;
            long free;
            char buf[];
        };
        
        • sdsnewlen 関数で Redis 文字列生成
        sds sdsnewlen(const void *init, size_t initlen) {
            struct sdshdr *sh;
        
            sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
        #ifdef SDS_ABORT_ON_OOM
            if (sh == NULL) sdsOomAbort();
        #else
            if (sh == NULL) return NULL;
        #endif
            sh->len = initlen;
            sh->free = 0;
            if (initlen) {
                if (init) memcpy(sh->buf, init, initlen);
                else memset(sh->buf,0,initlen);
            }
            sh->buf[initlen] = '\0';
            return (char*)sh->buf;
        }
        
    • Redis Virtual Memory : https://redis.io/topics/internals-vm

      • Redis 2.0 までの説明
    • Redis Event Library

      typedef struct aeEventLoop
      {
          int maxfd;
          long long timeEventNextId;
          aeFileEvent events[AE_SETSIZE]; /* Registered events */
          aeFiredEvent fired[AE_SETSIZE]; /* Fired events */
          aeTimeEvent *timeEventHead;
          int stop;
          void *apidata; /* This is used for polling API specific data */
          aeBeforeSleepProc *beforesleep;
      } aeEventLoop;
      
      • 以下の様な関数を定義
        • aeCreateEventLoop
        • aeCreateTimeEvent
        • aeCreateFileEvent
        • aeProcessEvents
        • processTimeEvents

Resources

Redis Cheat Sheet by tasjaevan - Download free from Cheatography - Cheatography.com: Cheat Sheets For Every Occasion

  • URL: https://www.cheatography.com/tasjaevan/cheat-sheets/redis/
  • Notes: Redisコマンドのチートシート ★5

Use cases

Who is using Redis

  • URL: https://redis.io/topics/whos-using-redis
  • Twitter, GitHub, Weibo, Pinterest, Snapchat, Craigslist, Digg, StackOverflow, Flickr 等

Useful Links

  • 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/

My Twitter & RSS

Leave a Reply

Your email address will not be published. Required fields are marked *