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!
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.
Utilisez les itérateurs SCAN: https://pypi.python.org/pypi/redis
for key in r.scan_iter("prefix:*"):
r.delete(key)
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')
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:*') )
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
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
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)
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_*")
Utilisez delete_pattern: https://niwinz.github.io/Django-redis/latest/
from Django.core.cache import cache
cache.delete_pattern("prefix:*")