Redis Python - как удалить все ключи в соответствии с определенным шаблоном В python, без повторения python

Я пишу команду управления django, чтобы обработать часть нашего кэширования redis. По сути, мне нужно выбрать все ключи, которые подтверждают определенный шаблон (например: "prefix: *"), и удалить их.

Я знаю, что могу использовать cli для этого:

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

Но мне нужно сделать это из приложения. Поэтому мне нужно использовать привязку Python (я использую Py-Redis). Я попытался ввести список в удаление, но это не удалось:

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

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

# И сейчас

cache.delete(x) 

# возвращает 0. ничего не удалено

Я знаю, что могу перебрать x:

for key in x:
   cache.delete(key)

Но это может привести к потере скорости и неправильному использованию его возможностей. Есть ли питоническое решение с py-redis, без итерации и/или кли?

Спасибо!

Ответы

Ответ 1

Я думаю, что

 for key in x: cache.delete(key)

довольно хорош и лаконичен. delete действительно хочет по одному ключу за раз, поэтому вам нужно зациклиться.

В противном случае этот предыдущий вопрос и ответ указывает вам на решение на основе lua.

Ответ 3

Из Документация

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

Это просто требует аргумент за каждый ключ для удаления, а затем он расскажет вам, сколько из них было найдено и удалено.

В случае вашего кода выше я считаю, что вы можете просто сделать:

    redis.delete(*x)

Но я признаю, что я новичок в python, и я просто делаю:

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

Ответ 4

cache.delete(*keys) решение Dirk отлично работает, но убедитесь, что ключи не пустые, чтобы избежать redis.exceptions.ResponseError: wrong number of arguments for 'del' command.

Если вы уверены, что всегда будете получать результат: cache.delete(*cache.keys('prefix:*') )

Ответ 5

Вот полный рабочий пример, используя 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

Вы также можете сделать scan_iter, чтобы получить все ключи в памяти, а затем передать все ключи в delete для массового удаления, но они могут взять хороший кусок памяти для больших пространств имен. Поэтому, вероятно, лучше всего запустить delete для каждого ключа.

Ура!

UPDATE:

С момента написания ответа я начал использовать функцию конвейерной обработки redis для отправки всех команд в один запрос и избежания латентности сети:

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 (Best Performing):

Если вы используете scan вместо scan_iter, вы можете контролировать размер блока и перебирать курсор, используя свою собственную логику. Это также кажется намного быстрее, особенно при работе со многими ключами. Если вы добавите конвейер для этого, вы получите немного повышения производительности, 10-25% в зависимости от размера блока, за счет использования памяти, так как вы не отправите команду выполнения Redis до тех пор, пока все не будет сгенерировано. Поэтому я застрял в сканировании:

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

Вот несколько этапов:

5k кусков с использованием занятого кластера Redis: 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 chunks и небольшой idle 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

Ответ 6

Согласно моему тесту, если я использую решение scan_iter (как писал Алекс Тодерита), это будет стоить слишком много времени.

Поэтому я предпочитаю использовать:

from redis.connection import ResponseError

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

prefix:* это шаблон.


ссылается на: fooobar.com/questions/16888/...

Ответ 9

Вы можете использовать определенный шаблон, чтобы сопоставить все ключи и удалить их:

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)