web-dev-qa-db-fra.com

Redis Python - comment supprimer toutes les clés selon un modèle spécifique En python, sans itération python

J'écris une commande de gestion Django pour gérer certaines de nos mises en cache redis. En gros, je dois choisir toutes les clés qui confirment un certain motif (par exemple: "prefix: *") et les supprimer.

Je sais que je peux utiliser la cli pour le faire:

redis-cli KEYS "prefix:*" | xargs redis-cli DEL

Mais je dois le faire depuis l'application. J'ai donc besoin d'utiliser la liaison python (j'utilise py-redis). J'ai essayé d'insérer une liste dans delete, mais cela a échoué:

from common.redis_client import get_redis_client
cache = get_redis_client()
x = cache.keys('prefix:*') 

x == ['prefix:key1','prefix:key2'] # True

# Et maintenant

cache.delete(x) 

# renvoie 0. rien n'est supprimé

Je sais que je peux parcourir plus de x:

for key in x:
   cache.delete(key)

Mais ce serait perdre une vitesse incroyable et utiliser ses capacités de manière abusive. Existe-t-il une solution Pythonic avec py-redis, sans itération et/ou cli?

Merci!

22
alonisser

Je pense que le 

 for key in x: cache.delete(key)

est assez bon et concis. delete veut vraiment une clé à la fois, vous devez donc boucler.

Sinon, cette question précédente et réponse vous indique une solution basée sur Lua.

8
Dirk Eddelbuettel

Utilisez les itérateurs SCAN: https://pypi.python.org/pypi/redis

for key in r.scan_iter("prefix:*"):
    r.delete(key)
21
Alex Toderita

De la Documentation

delete(*names)
    Delete one or more keys specified by names

Cela veut juste qu'un argument par clé soit supprimé et ensuite il vous dira combien d'entre eux ont été trouvés et supprimés.

Dans le cas de votre code ci-dessus, je pense que vous pouvez simplement faire:

    redis.delete(*x)

Mais je vais admettre que je suis nouveau sur python et que je fais juste:

    deleted_count = redis.delete('key1', 'key2')
8
James

La solution cache.delete(*keys) de Dirk fonctionne bien, mais assurez-vous que les clés ne sont pas vides pour éviter un redis.exceptions.ResponseError: wrong number of arguments for 'del' command

Si vous êtes sûr que vous obtiendrez toujours un résultat: cache.delete(*cache.keys('prefix:*') )

4
Blackeagle52

Voici un exemple de travail complet utilisant py-redis :

from redis import StrictRedis
cache = StrictRedis()

def clear_ns(ns):
    """
    Clears a namespace
    :param ns: str, namespace i.e your:prefix
    :return: int, cleared keys
    """
    count = 0
    ns_keys = ns + '*'
    for key in cache.scan_iter(ns_keys):
        cache.delete(key)
        count += 1
    return count

Vous pouvez également utiliser scan_iter pour obtenir toutes les clés en mémoire, puis passer toutes les clés à delete pour une suppression en bloc, mais peut utiliser une bonne quantité de mémoire pour les espaces de noms plus grands. Il est donc probablement préférable d’exécuter une delete pour chaque clé.

À votre santé! 

METTRE À JOUR:

Depuis que j'ai écrit la réponse, j'ai commencé à utiliser la fonctionnalité de traitement en pipeline de redis pour envoyer toutes les commandes en une requête et éviter la latence du réseau:

from redis import StrictRedis
cache = StrictRedis()

def clear_cache_ns(ns):
    """
    Clears a namespace in redis cache.
    This may be very time consuming.
    :param ns: str, namespace i.e your:prefix*
    :return: int, num cleared keys
    """
    count = 0
    pipe = cache.pipeline()
    for key in cache.scan_iter(ns_keys):
        pipe.delete(key)
        count += 1
    pipe.execute()
    return count

UPDATE2 (Meilleure performance): 

Si vous utilisez scan au lieu de scan_iter, vous pouvez contrôler la taille du bloc et parcourir le curseur à l'aide de votre propre logique. Cela semble également être beaucoup plus rapide, en particulier lorsqu'il s'agit de nombreuses clés. Si vous ajoutez le traitement en pipeline à cela, vous obtiendrez un gain de performances, 10 à 25% en fonction de la taille du bloc, au détriment de l'utilisation de la mémoire, car vous n'enverrez pas la commande d'exécution à Redis tant que tout n'a pas été généré. Alors j'ai collé avec scan:

from redis import StrictRedis
cache = StrictRedis()
CHUNK_SIZE = 5000

def clear_ns(ns):
    """
    Clears a namespace
    :param ns: str, namespace i.e your:prefix
    :return: int, cleared keys
    """
    cursor = '0'
    ns_keys = ns + '*'
    while cursor != 0::
        cursor, keys = cache.scan(cursor=cursor, match=ns_keys, count=CHUNK_SIZE)
        if keys:
            cache.delete(*keys)

    return True

Voici quelques repères:

5k morceaux utilisant un cluster Redis occupé: Done removing using scan in 4.49929285049 Done removing using scan_iter in 98.4856731892 Done removing using scan_iter & pipe in 66.8833789825 Done removing using scan & pipe in 3.20298910141

5k morceaux et un petit dev redis inactif (localhost): Done removing using scan in 1.26654982567 Done removing using scan_iter in 13.5976779461 Done removing using scan_iter & pipe in 4.66061878204 Done removing using scan & pipe in 1.13942599297

4
radtek

Selon mon test, cela prend trop de temps si j'utilise la solution scan_iter (comme Alex Toderita a écrit ).

Par conséquent, je préfère utiliser:

from redis.connection import ResponseError

try:
    redis_obj.eval('''return redis.call('del', unpack(redis.call('keys', ARGV[1])))''', 0, 'prefix:*')
except ResponseError:
    pass

Le prefix:* est le motif.


fait référence à: https://stackoverflow.com/a/16974060

2
carton.swing

Vous pouvez utiliser un modèle spécifique pour faire correspondre toutes les clés et les supprimer:

import redis
client = redis.Redis(Host='192.168.1.106', port=6379,
                password='pass', decode_responses=True)
for key in client.keys('prefix:*'):
    client.delete(key)
1
Lynn Han

En passant, pour les Django-redis, vous pouvez utiliser ce qui suit (à partir de https://niwinz.github.io/Django-redis/latest/ ):

from Django.core.cache import cache
cache.delete_pattern("foo_*")
1
Gleb

Utilisez delete_pattern: https://niwinz.github.io/Django-redis/latest/

from Django.core.cache import cache
cache.delete_pattern("prefix:*")
0
Jijo