2014-02-23 8 views
15

Ich bin ein django Verwaltungsbefehl zu schreiben einige unseres redis Caching zu behandeln. Grundsätzlich muss ich alle Schlüssel auswählen, die zu einem bestimmten Muster passen (zB: "prefix: *") und sie löschen. Ich weiß, dass ich die cli, das zu tun verwenden kann:Redis Python - wie alle Schlüssel löschen nach einem bestimmten Muster in Python, ohne Python Iterieren

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

Aber ich muß dazu aus der App heraus. Also muss ich die Python-Bindung verwenden (ich benutze py-redisis). Ich habe versucht, eine Liste in delete einzufügen. aber es funktioniert nicht:

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

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

# Und jetzt

cache.delete(x) 

# 0 zurück. nichts gelöscht

Ich weiß, ich iterieren kann x:

for key in x: 
    cache.delete(key) 

Aber das würde redis atemberaubende Geschwindigkeit verlieren und zu mißbrauchen Es ist Fähigkeiten. Gibt es eine pythontische Lösung mit Py-Redis, ohne Iteration und/oder Cli?

Danke!

Antwort

4

Ich denke, die

for key in x: cache.delete(key) 

ziemlich gut und prägnant ist. delete möchte wirklich einen Schlüssel zu einer Zeit, also müssen Sie Schleife.

andernfalls sind diese previous question and answer Punkte, die Sie auf eine lua-basierte Lösung.

+7

redis-Python-Paket verwenden Sie, dass eine solche Art und Weise tun: 'cache.delete (* Tasten)' – selfnamed

4

cache.delete(*keys) Lösung von Dirk funktioniert gut, aber stellen Sie sicher, dass Schlüssel nicht leer ist, um eine redis.exceptions.ResponseError: wrong number of arguments for 'del' command zu vermeiden.

Wenn Sie sicher sind, dass Sie immer ein Ergebnis bekommen: cache.delete(*cache.keys('prefix:*'))

+1

Verwenden Sie nicht 'cache.keys()' in prod, das zum Debuggen gedacht ist: https://redis.io/commands/keys – radtek

0

Btw, für die django-Redis Sie folgende verwenden können (von https://niwinz.github.io/django-redis/latest/):

from django.core.cache import cache 
cache.delete_pattern("foo_*") 
+0

Diese Frage bezieht sich auf redis, so dass django cache framework außerhalb von sco ist pe. – radtek

13

Verwenden SCAN Iteratoren: https://pypi.python.org/pypi/redis

for key in r.scan_iter("prefix:*"): 
    r.delete(key) 
+0

django-redisine implementiert delete_pattern, das etwas sehr ähnliches tut, siehe https://github.com/niwinz/django-redis/blob/master/django_redis/client/default.py#L264. –

6

Vom Documentation

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

Dies will nur ein Argument pro Schlüssel zu löschen und dann wird es Ihnen sagen, wie viele von ihnen gefunden und gelöscht wurden.

Im Falle des Codes oben Ich glaube, Sie gerade tun:

redis.delete(*x) 

Aber ich gebe zu ich bin neu in Python und ich gerade:

deleted_count = redis.delete('key1', 'key2') 
1

hier ein voll ist Arbeitsbeispiel mit 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 

Sie auch scan_iter tun können, alle Schlüssel zu bekommen in den Speicher, und übergeben Sie dann alle Schlüssel an delete für eine Massenlöschung, aber kann einen großen Teil des Speichers für größere Namespaces nehmen. Also am besten einen delete für jeden Schlüssel ausführen.

Prost!

UPDATE:

Da die Antwort zu schreiben, begann ich Pipeliningfunktion von redis unter Verwendung aller Befehle in einer Anfrage zu senden und Netzwerk-Latenz zu vermeiden:

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 (Beste Leistung):

Wenn Sie scan anstelle von scan_iter verwenden, können Sie die Chunk-Größe steuern und mit der eigenen Logik durch den Cursor iterieren. Dies scheint auch viel schneller zu sein, besonders wenn es um viele Schlüssel geht. Wenn Sie Pipelining hinzufügen, erhalten Sie eine Leistungssteigerung von 10-25%, abhängig von der Chunk-Größe, auf Kosten der Speichernutzung, da Sie den Ausführungsbefehl erst dann an Redis senden, wenn alles generiert ist. Also steckte ich mit 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 

Hier sind einige Benchmarks:

5k chunks ein Besetzt Redis Cluster mit: 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 Brocken und eine kleine Ruhe dev redis (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

+0

Haben Sie bemerkt, dass es sich um eine Frage aus dem Jahr 2014 handelt, die bereits beantwortet wurde? – alonisser

+1

Ich habe ein funktionierendes Beispiel geliefert. Möchte auch andere über scan_iter vs. bulk delete kommentieren – radtek

1

Nach meinem Test wird es zu viel Zeit kosten, wenn ich scan_iter Lösung verwende (wie @Alex Toderita oben geschrieben).

deshalb ziehe ich verwenden:

from redis.connection import ResponseError 

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

die prefix:* das Muster ist.


bezieht sich auf: https://stackoverflow.com/a/16974060