Table Of Contents

Previous topic

文字列型

Next topic

セット型

This Page

リスト型

Redisリスト型はRedis文字列型のリストになっています。Redisリストには新しい要素をリストの先頭(左側)または末尾(右側)に追加することが可能です。

LPUSH コマンドでは新しい要素をリストの先頭に追加して、 RPUSH では新しい要素を末尾に追加します。対応するリストを持たないキーに対してこの操作が行われた場合は、新しいリストが作成されます。

たとえば次のような操作をした場合は結果は次のようになります:

LPUSH mylist a   # now the list is "a"
LPUSH mylist b   # now the list is "b","a"
RPUSH mylist c   # now the list is "b","a","c" (RPUSH was used this time)

最終的に上のサンプルでは mylist は要素 “b”,”a”,”c” を保持しています。

Redisリストの要素数の最大値は 2^32-1(4294967295, 1リストあたり4億以上)です。

実装の詳細

Redisリストは双方向リストで実装されています。両端(先頭または末尾)から必要な要素にアクセスするために双方向リストで実装されていることによって、いくつかのコマンドではその恩恵を受けています。 LRANGELINDEX といったコマンドがその例です。

双方向リストを使うことによって、リスト長にかかわらずプッシュやポップがO(1)の操作になることが保証されています。

Redisリスト型はリスト長の情報をキャッシュするので LLEN は同様にO(1)の操作になります。

リスト型のコマンド

RPUSH(key, string)
LPUSH(key, string)

計算時間: O(1)

文字列型の値 string をキー key にひもづいているリストの先頭( LPUSH )または末尾( RPUSH )に加えます。もしキーが存在しない場合は空のリストが作成された後に、前述の操作が行われます。キーが存在するけれど値がリスト型でなかった場合はエラーが返ります。

返り値

Integer replyが返ります。具体的には操作後のリストの要素数です。

LLEN(key)

計算時間: O(1)

Return the length of the list stored at the specified key. If the key does not exist zero is returned (the same behaviour as for empty lists). If the value stored at key is not a list an error is returned.

返り値

Integer replyが返ります。具体的には下記:

The length of the list.
LRANGE(key, start, end)

計算時間: O(start+n) (nは範囲の長さ、startは開始位置のオフセット)

キー key に対応するリスト内の指定された位置の要素を返します。 startend はゼロから始まるインデックスです。0はリストの先頭の要素を指します。1は2番目の要素、といった具合です。

例えば LRANGE foobar 0 2 と下場合にはリストの最初の3要素を返します。

startend は負の整数とすることもできます。その場合はリストの末尾から数えたオフセットになります。例えば、-1はリストの末尾、-2は最後から2番目といった感じです。

多くのプログラミング言語におけるrange関数との一貫性

0から100の数を持つリストを考えてください。この場合 LRANGE 0 10 は11個の要素を返します。つまり最も右の要素が含まれるわけです。これはあなたがいま使っているプログラミング言語のrange関数、あるいはそれに類する関数(たとえばRubyの Range.new, Array#slice またはPythonの range() 関数)の動作と一致するかもしれないし、一致しないかもしれません。このことは注意してください。

LRANGE の動作はtclのそれと一致します。

範囲外のインデックス

範囲外のインデックスはエラーの原因とはなりません。もし start がリストの末尾を超えた値、あるいは startend よりも大きい場合は空リストが返ります。もし end がリストの末尾を超えていた場合はRedisはその値をリストの末尾に勝手に置き換えます。

返り値

Multi bulk replyが返ります。具体的には与えられた範囲内の要素数です。

LTRIM(key, start, end)

計算時間: O(n) (nはリストの長さから範囲の長さを引いたものです)

既存のリストを指定された範囲の要素を持つリストになるようにトリムします。 startend は0から始まるインデックスです。0はリストの先頭を指し、1は2番目を指すという具合です。

たとえば、 LTRIM foobar 0 2foobar というキーに対応するリストを最初の3つの要素しか持たないリストに変更します。

startend は負の整数にすることも可能です。この場合はリストの末尾からのオフセットとなります。たとえば、-1はリストの末尾、-2は最後から2番目、といった具合です。

範囲外のインデックスを指定してもエラーにはなりません。もし start がリストの末尾を超えた値、あるいは startend よりも大きな値になったとしても、空リストが返るだけです。もし end がリストの末尾を超えた場合はRedisはそれを勝手にリストの末尾として解釈します。

ヒント: LTRIMLPUSH/RPUSH と一緒に用いるというのはよくあるイディオムです:

LPUSH mylist <someelement>
LTRIM mylist 0 99

いま例示した2つのコマンドは要素をあるリストに追加するものですが、そのリストが際限なしに大きくならないような操作をしています。この例は例えばRedisでログを残す場合に非常に有効です。:com:LTRIM がこのような用法で用いられた場合には計算時間はO(1)になることに注目してください。その理由は一般的にはリストの末尾の要素だけが削除されるだけだからです。

返り値

Status code replyが返ります。
LINDEX(key, index)

計算時間: O(n) (nはリストの長さ)

キー key に対応するリスト内の指定されたインデックス index が指す要素を返します。0はリストの先頭、1は2番目の要素、といった具合です。負のインデックスも指定可能です。たとえば-1はリストの末尾、-2は最後から2番目、と続きます。

もしキー key に対応する値がリスト型でない場合、エラーが返ります。もしインデックスが範囲外だった場合、 “nil” が返ります。

たとえ平均計算時間がO(n)だとしても、先頭もしくは末尾の要素の場合にはO(1)で取得可能であることに気をつけてください。

返り値

Bulk replyが返ります。具体的には参照された要素が返ります。
LSET(key, index, value)

計算時間: O(N) (Nはリストの長さ)

キー key に対応するリスト内の指定されたインデックス index の要素を値を新しい値 value にします。(引数 index に関しては LINDEX を見て下さい)範囲外のインデックスを指定した場合はエラーが起きます。リストの先頭および末尾の要素に値をセットする場合はO(1)です。

インデックスを指定する他のリスト操作系のコマンドと同様に、負のインデックスを指定した場合はリストの末尾からの値となります。-1の場合はリストの末尾、-2ならその前、となります。

戻り値

Status code replyが返ります。
LREM(key, count, value)

計算時間: O(N) (Nはリストの長さ)

キー key 対応するリスト内で値が value に等しいの最初の count 要素を削除します。もし count がゼロだった場合は該当するすべての要素が削除されます。もし count が負だった場合には、通常とは逆に要素はリストの末尾から先頭に向かって削除されます。例えば key に対応するリストが (a,b,c,hello,x,hello,hello) だったとして LREM key -2 hello を呼び出した場合には (a,b,c,hello,x) が残ります。削除された要素数が整数値として返ります。返り値に関する詳細は後述します。存在しないキーの場合はLREMは空リストに対して操作をした、と判断しますのでそういう場合は常に0が返ります。

戻り値

Integer replyが返ります。具体的には:

The number of removed elements if the operation succeeded
LPOP(key)
RPOP(key)

計算時間: O(1)

キー key に対応するリストに対してアトミックに先頭 ( LPOP )または末尾 ( RPOP )の要素を返し、削除します。例えば “a”,”b”,”c”を含むリストに対して操作を行った場合は LPOP は”a”を返し、リストは”b”,”c”となります。

もしキーが存在しない場合、あるいはリストがすでに空だった場合は特別な値 ‘nil’ が返ります。

返り値

Bulk replyが返ります。
BLPOP(key1, key2, ..., keyN, timeout)

New in version 1.3.1.

BRPOP(key1, key2, ..., keyN, timeout)

New in version 1.3.1.

計算時間: O(1)

BLPOP (と BRPOP )はブロッキングなポップのプリミティブです。言い換えれば、 LPOPRPOP のブロッキング版で、指定されたキーが存在しない場合や対応するリストが空でも使うことができるものだとも言えます。

これより実際のセマンティクスについての説明をします。 BLPOP についての説明しか書きませんが、 BRPOP はただ先頭から操作するか末尾から操作するかの違いなので、基本的には同じです。

ノンブロッキングな動作

BLPOP が呼び出されたとき、指定したキーのもし少なくとも一つが空でないリストを持っていた場合、そのリストの先頭の要素がポップされて、ポップされたリストに紐づいたキーとともに呼び出し元に返されます。( BLPOP は2つの要素の配列で、最初の要素はキー、2番目の要素はポップされた値となります)

キーは左から右にスキャンされます。たとえばlist1は存在しない、list2とlist3は空でないリストという状況だった場合には、 BLPOP list1 list2 list3 0 を呼びだすと、list2から要素を取り出して返すことは保証されます。(なぜなら左から数えていってlist2が最初の空でないリストだからです)

ブロッキングな動作

指定したキーのどれもが存在しないあるいは空リストの場合には、 BLPOP は他のクライアントが指定したリストのどれかに LPUSH あるいは RPUSH しない限りブロックします。

リストのうちどれか一つにでも新しいデータが投入されれば、クライアントはようやくそのリストとひもづいているキーとポップされた値を返します。

ブロックしているときは、もしゼロでないタイムアウトが指定されていれば、クライアントはタイムアウトまでの間に少なくとも1つのリストにプッシュ操作がされなかった場合に、特別な値”nil”を返してブロックをやめます。

タイムアウト用の引数 timeout は整数値として解釈されます。タイムアウト時間が0だった場合には制限なくブロックするようになります。

複数のクライアントによる同キーに対してのブロッキング

複数のクライアントが同一キーに対してブロックすることが可能です。リクエストはキューに貯められるので、最初にそのキーに対して操作を行うことができるのは早くキューに並んだ順となります。

blocking POP inside a MULTI/EXEC transaction

MULTI/EXECトランザクション内でのブロッキングなPOP

BLPOPBRPOP はパイプライン(1回のバッチで複数のコマンドを送信して、複数の返信を読み込む)に使えまが、 BLPOP または BRPOPMULTI / EXEC ブロック(Redisトランザクション)内で使うのはあまり意味がありません。

操作対象のリストが空の時に BLPOPMULTI / EXEC 内では風数のBulk nil replyを返す仕様になっています。まさにタイムアウトになった時に起きることと一緒です。もしSFが好きならば、 MULTI / EXEC の中では時間は無限の速さで流れていると考えてください。

返り値

BLPOP はmulti bulk replyを経由してブロックしているキーとポップされた値のペアからなる配列を返します。

もしタイムアウトにゼロでない値が指定されて、 BLPOP の操作がタイムアウトしたときに、返り値はnil multi bulk replyとなります。たいていのクライアントでは使っているプログラミング言語に応じて falsenil を返すことになると思います。

RPOPLPUSH(srckey, dstkey)

New in version 1.1.

計算時間: O(1)

キー srckey に対応するリストでアトミックに末尾の要素を削除して、その要素を dstkey に対応するリストの先頭にプッシュします。たとえば、ソースのリストが”a”,”b”,”c”でターゲットのリストが”foo”,”bar”だった場合に RPOPLPUSH コマンドを実行すると2つのリストはそれぞれ “a”,”b” と “c”,”foo”,”bar”

もしキーが存在しないまたはリストがすでに空だった場合には特別な値”nil”が返されます。もし srckeydstkey が同じだった場合は、そのリストの末尾の要素を取り除いて、先頭に持ってくる操作となります。これは「リストローテーション」コマンドですね。

プログラミングパターン: セーフキュー

Redisのリストはよく複数のプログラム缶でのメッセージキューとして用いられます。あるプログラム(プロデューサ)が LPUSH によってRedisリストにメッセージを追加して、他のプログラム(コンシューマ)が RPOP コマンドを使って古い順からメッセージを読み取るという処理を行ないます。

もし残念なことにコンシューマが RPOP の後にクラッシュしてしまった場合、メッセージはなくなってしまいます。 RPOPLPUSH ならこの問題を解決できます。その理由は返されたメッセージは他の”バックアップ”リストに追加されるからです。コンシューマはメッセージがきちんと処理されたあとに LREM コマンドを使ってバックアップリストから該当するメッセージを削除できます。

ヘルパーと呼ばれる他のプロセスが”バックアップ”リストを監視してメインキューにタイムアウトした要素を再度プッシュすることもできます。

プログラミングパターン: サーバサイド O(N) リストトラバーサル*

RPOPLPUSH のソースとターゲットに同じキーを指定すると、N要素を持つリスト内のすべての要素をなめるとき、 LRANGE の操作をするためにサーバからクライアントにO(N)でできます。ここで、そのリストを他のプロセスが RPUSH している最中でさえも、すべての要素を漏らすことなくトラバースできることを知っておいてください。

返り値

Bulk replyが返ります。