web-dev-qa-db-fra.com

Pop multiples valeurs de la structure de données Redis atomiquement?

Existe-t-il une structure de données Redis, qui permettrait une opération atomique consistant à décomposer (obtenir + supprimer) plusieurs éléments, qu’elle contient?

Il y a bien connu SPOP ou RPOP, mais ils renvoient toujours une valeur unique. Par conséquent, lorsque j'ai besoin des premières N valeurs de set/list, je dois appeler la commande N-times, ce qui est coûteux. Disons que l'ensemble/la liste contient des millions d'éléments. Existe-t-il quelque chose comme SPOPM "setName" 1000, qui renverrait et supprimerait 1000 éléments aléatoires de set ou RPOPM "listName" 1000, qui renverrait 1000 éléments les plus à droite de la liste?

Je sais qu'il existe des commandes telles que SRANDMEMBER et LRANGE, mais elles ne suppriment pas les éléments de la structure de données. Ils peuvent être supprimés séparément. Cependant, si plusieurs clients lisent à partir de la même structure de données, certains éléments peuvent être lus plusieurs fois et certains peuvent être supprimés sans lecture! Par conséquent, l'atomicité est le sujet de ma question.

En outre, je vais bien si la complexité en temps pour une telle opération est plus chère. Je doute que cela coûtera plus cher que d'émettre N (disons 1000, N de l'exemple précédent) des requêtes distinctes au serveur Redis.

Je connais aussi le support de transaction séparé. Cependant, cette phrase de Redis docs me dissuade de l’utiliser pour des processus parallèles modifiant l’ensemble (en le lisant de manière destructive):
Lors de l’utilisation de WATCH, EXEC n’exécutera les commandes que si les clés surveillées n’ont pas été modifiées, ce qui permet un mécanisme de vérification et de réglage.

24
Pavel S.

À partir de Redis 3.2, la commande SPOP a un argument [count] pour extraire plusieurs éléments d'un ensemble.

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

5

Utilisez LRANGE avec LTRIM dans un pipeline . Le pipeline sera exécuté comme une transaction atomique. Votre inquiétude concernant WATCH, EXEC ci-dessus ne s'appliquera pas ici car vous exécutez les LRANGE et LTRIM comme une transaction sans possibilité pour aucune autre transaction d'un autre client de les séparer. Essaye le.

16
Eli

Pour développer la réponse d'Eli avec un exemple complet de collections de listes, utilisez les commandes intégrées lrange et ltrim au lieu de Lua:

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"

Si vous voulez rendre l'opération atomique, vous devez envelopper les commandes lrange et ltrim dans les commandes multi et exec

De plus, comme indiqué ailleurs, vous devriez probablement ltrim le nombre d'articles retournés pas le nombre d'articles que vous avez demandés. par exemple. si vous avez lrange a 0 99 mais que vous avez 50 éléments, vous ltrim a 50 -1 et non pas ltrim a 100 -1

Pour implémenter la sémantique de la file d'attente au lieu d'une pile, remplacez lpush par rpush.

9
thom_nic

si vous voulez un script lua, cela devrait être rapide et facile.

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

alors vous n'avez pas à faire une boucle.

update: J'ai essayé de le faire avec srandmember (en 2.6) avec le script suivant:

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

mais je reçois une erreur: 

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

Je ne sais pas si la version future le permettra, mais je suppose que non. Je pense que ce serait un problème de réplication.

3
Yehosef

Redis 4.0+ prend désormais en charge les modules qui ajoutent toutes sortes de nouvelles fonctionnalités et types de données avec un traitement beaucoup plus rapide et sûr que les scripts Lua ou les pipelines multiexec.

Redis Labs, le sponsor actuel de Redis, dispose d'un ensemble de modules d'extension utiles appelé _/redex ici: https://github.com/RedisLabsModules/redex

Le module rxlists ajoute plusieurs opérations de liste, dont LMPOP et RMPOP, afin que vous puissiez extraire de manière atomique plusieurs valeurs d'une liste Redis. La logique est toujours O(n) (il s’agit de faire un simple clic dans une boucle), mais tout ce que vous avez à faire est d’installer le module une fois et d’envoyer simplement cette commande personnalisée. Je l’utilise sur des listes contenant des millions d’éléments et des milliers d’explosions en même temps, générant sans aucun problème plus de 500 Mo de trafic réseau.

1
Mani Gandham

Je pense que vous devriez regarder le support de LUA dans Redis. Si vous écrivez un script LUA et l'exécutez sur redis, il est garanti qu'il est atomique (car Redis est mono-threadé). Aucune requête ne sera exécutée avant la fin de votre script LUA (par exemple, vous ne pouvez pas implémenter une grosse tâche dans LUA, sinon redis sera lent).

Ainsi, dans ce script, vous ajoutez votre SPOP et votre RPOP, vous pouvez par exemple ajouter les résultats de chaque commande redis dans un tableau LUA, puis renvoyer le tableau à votre client Redis.

Ce que dit la documentation à propos de MULTI, c’est qu’il s’agit d’un verrouillage optimiste, c’est-à-dire qu’il va réessayer de faire le multi avec WATCH jusqu’à ce que la valeur surveillée ne soit pas modifiée. Si vous avez beaucoup d'écritures sur la valeur surveillée, cela sera plus lent que le verrouillage «pessimiste» (comme de nombreuses bases de données SQL: POSTGRESQL, MYSQL ...) qui, d'une certaine manière, "arrête le monde" pour que la requête soit exécutée en premier . Le verrouillage pessimiste n'est pas implémenté dans redis, mais vous pouvez l'implémenter si vous le souhaitez, mais il est complexe et vous n'en avez peut-être pas besoin (peu d'écritures sur cette valeur: optimiste devrait suffire).

0
zenbeni

vous pouvez probablement essayer un script lua (script.lua) comme ceci:

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

vous pouvez l'appeler de cette façon:

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