web-dev-qa-db-ja.com

Redisデータ構造から複数の値をアトミックにポップしますか?

含まれている複数の要素をポップ(get + remove)するアトミック操作を可能にするRedisデータ構造はありますか?

よく知られているSPOPまたはRPOPがありますが、常に単一の値を返します。したがって、set/listから最初のN個の値が必要な場合は、コマンドをN回呼び出す必要があり、コストがかかります。セット/リストに何百万ものアイテムが含まれているとしましょう。 SPOPM "setName" 1000はセットから1000個のランダムなアイテムを返し、削除しますか、またはRPOPM "listName" 1000はリストから右端の1000個のアイテムを返しますか?

SRANDMEMBERやLRANGEのようなコマンドがあることは知っていますが、それらはデータ構造から項目を削除しません。個別に削除できます。ただし、同じデータ構造から読み取るクライアントがさらにある場合、一部の項目は複数回読み取ることができ、一部は読み取らずに削除できます。したがって、私の質問は原子性です。

また、そのような操作の時間の複雑さがもっと高ければいいのですが。 N(前の例では1000、Nとしましょう)の個別の要求をRedisサーバーに発行するよりもコストがかかると思います。

個別のトランザクションサポートについても知っています。ただし、Redis docsの次の文では、セットを変更する(破壊的に読み取る)並列プロセスで使用することはできません。
WATCHを使用する場合、監視対象のキーが変更されていない場合にのみEXECがコマンドを実行し、チェックと設定のメカニズムが可能になります。

32
Pavel S.

Redis 3.2以降、コマンドSPOPには[count]引数を使用して、セットから複数の要素を取得します。

参照 http://redis.io/commands/spop#count-argument-extension

パイプラインLRANGELTRIMとともに使用します。パイプラインは1つのアトミックトランザクションとして実行されます。 WATCHEXECを他のトランザクションの機能なしに1つのトランザクションとして実行しているため、LRANGELTRIMに関する上記の心配はここでは当てはまりません。それらの間に来る他のクライアントから。やってみて。

18
Eli

Luaの代わりにlrangeおよびltrimビルトインを使用して、リストコレクションの完全な例でEliの応答を拡張するには:

127.0.0.1:6379> lpush a 0 1 2 3 4 5 6 7 8 9
(integer) 10
127.0.0.1:6379> lrange a 0 3        # read 4 items off the top of the stack
1) "9"
2) "8"
3) "7"
4) "6"
127.0.0.1:6379> ltrim a 4 -1        # remove those 4 items
OK
127.0.0.1:6379> lrange a 0 999      # remaining items
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
6) "0"

操作をアトミックにしたい場合は、lrangeとltrimをmultiおよびexecコマンドでラップします。

また、他の場所で説明したように、おそらくltrim返されたアイテムの数は、要求したアイテムの数ではありません。例えばあなたがした場合lrange a 0 99が50個のアイテムを入手しましたltrim a 50 -1ないltrim a 100 -1

スタックの代わりにキューのセマンティクスを実装するには、lpushrpushに置き換えます。

12
thom_nic

luaスクリプトが必要な場合、これは速くて簡単です。

local result = redis.call('lrange',KEYS[1],0,ARGV[1]-1)
redis.call('ltrim',KEYS[1],ARGV[1],-1)
return result

その後、ループする必要はありません。

更新:srandmember(2.6)で次のスクリプトを使用してこれを実行しようとしました:

local members = redis.call('srandmember', KEYS[1], ARGV[1])
redis.call('srem', KEYS[1], table.concat(table, ' '))
return members

しかし、エラーが発生します:

error: -ERR Error running script (call to f_6188a714abd44c1c65513b9f7531e5312b72ec9b): 
Write commands not allowed after non deterministic commands

将来のバージョンでこれが可能かどうかはわかりませんが、そうではないと思います。レプリケーションの問題だと思います。

3
Yehosef

Redis 4.0+ではモジュールがサポートされるようになりました これは、Luaスクリプトやmulti/execパイプラインよりもはるかに高速で安全な処理で、あらゆる種類の新しい機能とデータ型を追加します。

Redisの背後にある現在のスポンサーであるRedis Labsには、redexという便利な拡張モジュールのセットがここにあります: https://github.com/ RedisLabsModules/redex

rxlistsモジュールは、LMPOPRMPOPを含むいくつかのリスト操作を追加し、Redisリストから複数の値をアトミックにポップできるようにします。ロジックはまだO(n)(基本的にループでシングルポップを行う)ですが、モジュールを一度インストールして、そのカスタムコマンドを送信するだけです。リストで使用します数百万のアイテムと数千のアイテムが同時にポップされ、500MB以上のネットワークトラフィックが問題なく生成されました。

1
Mani Gandham

pythonスニペットは、redis-pyとパイプラインを使用してこれを実現できるスニペットです。

from redis import StrictRedis

client = StrictRedis()

def get_messages(q_name, prefetch_count=100):
    pipe = client.pipeline()
    pipe.lrange(q_name, 0, prefetch_count - 1)  # Get msgs (w/o pop)
    pipe.ltrim(q_name, prefetch_count, -1)  # Trim (pop) list to new value
    messages, trim_success = pipe.execute()
    return messages

popのforループを実行できると思っていましたが、特にリストキューがprefetch_countより小さい場合は、パイプラインを使用しても効率的ではありません。完全なRedisQueueクラスを実装しています ここ 見たい場合。それが役に立てば幸い!

1
radtek

RedisのLUAサポートを見てみるべきだと思います。 LUAスクリプトを記述してredisで実行する場合、それはアトミックであることが保証されます(Redisはモノスレッドであるため)。 LUAスクリプトが終了する前にクエリは実行されません(つまり、LUAで大きなタスクを実装できない場合、またはredisが遅くなります)。

したがって、このスクリプトでは、SPOPとRPOPを追加します。たとえば、各redisコマンドの結果をLUA配列に追加し、その配列をredisクライアントに返すことができます。

ドキュメントがマルチについて言っていることは、それが楽観的ロックであるということです。つまり、監視された値が変更されなくなるまで、WATCHでマルチ処理を再試行します。監視対象の値に多くの書き込みがある場合、クエリが最初に実行されるように何らかの方法で「世界を停止」する(多くのSQLデータベースのように)悲観的ロックよりも遅くなります(POSTGRESQL、MYSQL ...) 。悲観的ロックはredisに実装されていませんが、必要に応じて実装できますが、複雑であり、おそらく必要ありません(この値に対する書き込みはそれほど多くありません。楽観的で十分です)。

0
zenbeni

おそらく、次のようなluaスクリプト(script.lua)を試すことができます。

local result = {}
for i = 0 , ARGV[1] do
    local val = redis.call('RPOP',KEYS[1])
    if val then
        table.insert(result,val)
    end
end
return result

このように呼び出すことができます:

redis-cli  eval "$(cat script.lua)" 1 "listName" 1000
0
Philippe T.