Cómo eliminar atómicamente las claves que coinciden con un patrón usando Redis

576

En mi Redis DB tengo varios prefix:<numeric_id>hashes.

A veces quiero purgarlos a todos atómicamente. ¿Cómo hago esto sin usar algún mecanismo de bloqueo distribuido?

Alexander Gladysh
fuente
Hola Steve: Hay un problema con mi sitio web, lo he agregado a mi otro blog mind-geek.net/nosql/redis/delete-keys-specific-expiry-time , espero que esto ayude.
Gaurav Tewari
43
Este es un escenario tan común que desearía que el equipo de Redis considere agregarle un comando nativo.
Todd Menier
Hoy en día puedes hacer eso con Lua, ver más abajo.
Alexander Gladysh
3
@ToddMenier Recién sugerido, recuperé este razonamiento de por qué nunca sucederá: github.com/antirez/redis/issues/2042
Ray
1
Muchas personas hacen preguntas relacionadas sobre cómo manejar una gran cantidad de teclas, teclas con caracteres especiales, etc. Creé una pregunta por separado ya que ahora tenemos este problema y no creo que la respuesta esté publicada en esta pregunta. Aquí está la otra pregunta: stackoverflow.com/questions/32890648/…
jakejgordon

Respuestas:

431

A partir de redis 2.6.0, puede ejecutar scripts lua, que se ejecutan atómicamente. Nunca he escrito uno, pero creo que se vería así

EVAL "return redis.call('del', unpack(redis.call('keys', ARGV[1])))" 0 prefix:[YOUR_PREFIX e.g delete_me_*]

Advertencia : como dice el documento de Redis , debido a los problemas de rendimiento, el keys comando no debe usarse para operaciones regulares en producción, este comando está destinado a la depuración y operaciones especiales. Lee mas

Consulte la documentación de EVAL .

mcdizzle
fuente
23
Nota importante: esto falla si tiene más de un par de miles de claves que coinciden con el prefijo.
Nathan Osman
93
Este funciona para una gran cantidad de teclas:EVAL "local keys = redis.call('keys', ARGV[1]) \n for i=1,#keys,5000 do \n redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) \n end \n return keys" 0 prefix:*
sheerun
181
Ouch ... redis se usa mucho como caché de clave / tienda simple. Esto parece del prefix:* ser una operación fundamental: /
Ray
55
@Ray francamente, si necesita esa característica, simplemente debe particionar los datos por base de datos numérica o servidor, y usar flush / flushdb
Marc Gravell
99
Sí, falla si ninguna clave coincide con el patrón. Para solucionar eso, agregué una clave predeterminada:EVAL "return redis.call('del', 'defaultKey', unpack(redis.call('keys', ARGV[1])))" 0 prefix:*
manuelmhtr
706

Ejecutar en bash:

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

ACTUALIZAR

Esta bien, entendí. Qué hay de esta manera: almacene el prefijo incremental adicional actual y agréguelo a todas sus claves. Por ejemplo:

Tienes valores como este:

prefix_prefix_actuall = 2
prefix:2:1 = 4
prefix:2:2 = 10

Cuando necesite purgar datos, primero cambie prefix_actuall (por ejemplo, configure prefix_prefix_actuall = 3), por lo que su aplicación escribirá nuevos datos en las claves prefijo: 3: 1 y prefijo: 3: 2. Entonces puede tomar con seguridad los valores antiguos del prefijo: 2: 1 y el prefijo: 2: 2 y purgar las claves antiguas.

Casey
fuente
14
Lo sentimos, pero esto no es eliminación atómica. Alguien puede agregar nuevas claves entre KEYS y DEL. No quiero eliminarlos.
Alexander Gladysh
36
Las claves que se crearán después del comando KEYS no se eliminarán.
Casey
66
Solo necesitaba borrar algunas claves malas, por lo que la primera respuesta de Casey fue acertada, excepto que tuve que mover las claves fuera de las comillas: redis-cli KEYS "prefijo: *" | xargs redis-cli DEL
jslatts
19
La primera respuesta también me ayudó. Otra variante si sus teclas redis contienen comillas u otros caracteres que estropean los xargs:redis-cli KEYS "prefix:*" | xargs --delim='\n' redis-cli DEL
piense demasiado el
18
Si tiene bases de datos múltiples (espacios de teclas), entonces este es el truco: digamos que necesita eliminar las claves en db3:redis-cli -n 3 KEYS "prefix:*" | xargs redis-cli -n 3 DEL
Christoffer
73

Aquí hay una versión completamente funcional y atómica de una eliminación de comodines implementada en Lua. Se ejecutará mucho más rápido que la versión xargs debido a que la red es mucho menor, y es completamente atómica, bloqueando cualquier otra solicitud contra redis hasta que finalice. Si desea eliminar claves atómicamente en Redis 2.6.0 o superior, este es definitivamente el camino a seguir:

redis-cli -n [some_db] -h [some_host_name] EVAL "return redis.call('DEL', unpack(redis.call('KEYS', ARGV[1] .. '*')))" 0 prefix:

Esta es una versión funcional de la idea de @ mcdizzle en su respuesta a esta pregunta. El crédito por la idea le corresponde al 100%.

EDITAR: Según el comentario de Kikito a continuación, si tiene más claves para eliminar que memoria libre en su servidor Redis, se encontrará con el error "demasiados elementos para descomprimir" . En ese caso, haz:

for _,k in ipairs(redis.call('keys', ARGV[1])) do 
    redis.call('del', k) 
end

Como sugirió Kikito.

Eli
fuente
10
El código anterior se acumulará si tiene un número significativo de claves (el error es "demasiados elementos para descomprimir"). Recomiendo usar un bucle en la parte Lua:for _,k in ipairs(redis.call('keys', KEYS[1])) do redis.call('del', k) end
kikito
@kikito, sí, si lua no puede aumentar la pila al número de claves que desea eliminar (probablemente debido a la falta de memoria), deberá hacerlo con un bucle for. No recomendaría hacer esto a menos que sea necesario.
Eli
1
Lua unpacktransforma una tabla en una "lista de variables independientes" (otros idiomas lo llaman explode) pero el número máximo no depende de la memoria del sistema; se arregla en lua a través de la LUAI_MAXSTACKconstante. En Lua 5.1 y LuaJIT es 8000 y en Lua 5.2 es 100000. Se recomienda la opción for loop IMO.
kikito
1
Vale la pena señalar que las secuencias de comandos lua solo están disponibles desde Redis 2.6 en adelante
wallacer
1
Cualquier solución basada en Lua violará la semántica de, EVALya que no especifica de antemano las teclas con las que operará. Debería funcionar en una sola instancia, pero no espere que funcione con Redis Cluster.
Kevin Christopher Henry
67

Descargo de responsabilidad: la siguiente solución no proporciona atomicidad.

A partir de v2.8, realmente desea utilizar el comando SCAN en lugar de KEYS [1]. La siguiente secuencia de comandos Bash muestra la eliminación de claves por patrón:

#!/bin/bash

if [ $# -ne 3 ] 
then
  echo "Delete keys from Redis matching a pattern using SCAN & DEL"
  echo "Usage: $0 <host> <port> <pattern>"
  exit 1
fi

cursor=-1
keys=""

while [ $cursor -ne 0 ]; do
  if [ $cursor -eq -1 ]
  then
    cursor=0
  fi

  reply=`redis-cli -h $1 -p $2 SCAN $cursor MATCH $3`
  cursor=`expr "$reply" : '\([0-9]*[0-9 ]\)'`
  keys=${reply##[0-9]*[0-9 ]}
  redis-cli -h $1 -p $2 DEL $keys
done

[1] KEYS es un comando peligroso que potencialmente puede resultar en un DoS. La siguiente es una cita de su página de documentación:

Advertencia: considere KEYS como un comando que solo debe usarse en entornos de producción con extremo cuidado. Puede arruinar el rendimiento cuando se ejecuta en grandes bases de datos. Este comando está destinado a la depuración y operaciones especiales, como cambiar el diseño del espacio de teclas. No use KEYS en su código de aplicación habitual. Si está buscando una manera de encontrar claves en un subconjunto de su espacio de claves, considere usar conjuntos.

ACTUALIZACIÓN: un trazador de líneas para el mismo efecto básico -

$ redis-cli --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli DEL
Itamar Haber
fuente
99
Sin embargo, evitar KEYS definitivamente se considera la mejor práctica, por lo que esta es una gran solución siempre que sea posible realizar eliminaciones no atómicas.
fatal_error
Esto funcionó para mí; sin embargo, mis claves estaban en la base de datos 1. Así que tuve que agregar -n 1a cada redis-cliinvocación:redis-cli -n 1 --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli -n 1 DEL
Rob Johansen
Tenga en cuenta que esto no funciona si sus claves contienen caracteres especiales
mr1031011
Interesante y valioso hallazgo ... Me pregunto si hay una manera de citar cosas para xargs ...
Itamar Haber
¿Qué hace -L 100?
Aparna
41

Para aquellos que tenían problemas para analizar otras respuestas:

eval "for _,k in ipairs(redis.call('keys','key:*:pattern')) do redis.call('del',k) end" 0

Reemplace key:*:patterncon su propio patrón e ingrese esto redis-cliy estará listo para comenzar .

Crédito lisco de: http://redis.io/commands/del

aleatorio
fuente
37

Estoy usando el siguiente comando en redis 3.2.8

redis-cli KEYS *YOUR_KEY_PREFIX* | xargs redis-cli DEL

Puede obtener más ayuda relacionada con la búsqueda de patrones de teclas desde aquí: - https://redis.io/commands/keys . Utilice su cómoda de estilo patrón glob como por su exigencia como *YOUR_KEY_PREFIX*o YOUR_KEY_PREFIX??, o cualquier otro.

Y si alguno de ustedes ha integrado la biblioteca PHP de Redis, la siguiente función lo ayudará.

flushRedisMultipleHashKeyUsingPattern("*YOUR_KEY_PATTERN*"); //function call

function flushRedisMultipleHashKeyUsingPattern($pattern='')
        {
            if($pattern==''){
                return true;
            }

            $redisObj = $this->redis;
            $getHashes = $redisObj->keys($pattern);
            if(!empty($getHashes)){
                $response = call_user_func_array(array(&$redisObj, 'del'), $getHashes); //setting all keys as parameter of "del" function. Using this we can achieve $redisObj->del("key1","key2);
            }
        }

Gracias :)

Yashrajsinh Jadeja
fuente
23

La solución de @ mcdizle no funciona, solo funciona para una entrada.

Este funciona para todas las teclas con el mismo prefijo

EVAL "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end" 0 prefix*

Nota: Debe reemplazar 'prefijo' con su prefijo clave ...

efaruk
fuente
2
usar lua es muuuucho más rápido que usar xargs, en el orden de 10 ^ 4.
deepak
22

También puede usar este comando para eliminar las teclas: -

Supongamos que hay muchos tipos de teclas en su redis como:

  1. 'xyz_category_fpc_12'
  2. 'xyz_category_fpc_245'
  3. 'xyz_category_fpc_321'
  4. 'xyz_product_fpc_876'
  5. 'xyz_product_fpc_302'
  6. 'xyz_product_fpc_01232'

Ex ' xyz_category_fpc ' aquí XYZ es un nombre del sitio , y estas claves están relacionadas con los productos y categorías de un sitio de comercio electrónico y genera por FPC.

Si usa este comando como a continuación-

redis-cli --scan --pattern 'key*' | xargs redis-cli del

O

redis-cli --scan --pattern 'xyz_category_fpc*' | xargs redis-cli del

Elimina todas las teclas como ' xyz_category_fpc ' ( elimina las teclas 1, 2 y 3). Para eliminar otras 4, 5 y 6 teclas numéricas, use ' xyz_product_fpc ' en el comando anterior.

Si desea eliminar todo en Redis , siga estos comandos:

Con redis-cli:

  1. FLUSHDB : elimina datos de la base de datos ACTUAL de su conexión.
  2. FLUSHALL : elimina datos de TODAS las bases de datos.

Por ejemplo: - en su shell:

redis-cli flushall
redis-cli flushdb
Vishal Thakur
fuente
3
Gracias, pero la salida de tubería redis-cli delno es atómica.
Alexander Gladysh
13

Si tiene espacio en el nombre de las teclas, puede usar esto en bash:

redis-cli keys "pattern: *" | xargs -L1 -I '$' echo '"$"' | xargs redis-cli del
Inc33
fuente
10

La respuesta de @ itamar es genial, pero el análisis de la respuesta no me funcionó, especialmente. en el caso de que no se encuentren claves en un escaneo determinado. Una solución posiblemente más simple, directamente desde la consola:

redis-cli -h HOST -p PORT  --scan --pattern "prefix:*" | xargs -n 100 redis-cli DEL

Esto también usa SCAN, que es preferible a KEYS en producción, pero no es atómico.

Guitan
fuente
8

Acabo de tener el mismo problema. Almacené datos de sesión para un usuario en el formato:

session:sessionid:key-x - value of x
session:sessionid:key-y - value of y
session:sessionid:key-z - value of z

Entonces, cada entrada era un par clave-valor separado. Cuando se destruye la sesión, quería eliminar todos los datos de la sesión eliminando claves con el patrón session:sessionid:*, pero redis no tiene esa función.

Lo que hice: almacené los datos de la sesión dentro de un hash . Me acabo de crear un hash con el identificador de hash session:sessionidy luego empujo key-x, key-y, key-zen esa almohadilla (para que no me importaba) y si no necesitas ese hash de más Me acaba de hacer una DEL session:sessionidy todos los datos asociados a un determinado código hash se ha ido. DELes atómico y acceder a datos / escribir datos en el hash es O (1).

Max
fuente
Buena solución, pero mis valores son hash en sí. Y Redis almacena hash dentro de otro hash.
Alexander Gladysh
3
Sin embargo, los campos dentro de un hash carecen de la funcionalidad de caducidad, que a veces es realmente útil.
Evi Song
para mí esta es la respuesta más simple / limpia hasta ahora
Sebastien H.
¿No tiene sentido un conjunto?
Jack Tuck
5

Creo que lo que podría ayudarlo es MULTI / EXEC / DISCARD . Si bien no es 100% equivalente a las transacciones , debería poder aislar las eliminaciones de otras actualizaciones.

alexpopescu
fuente
44
Pero no puedo entender cómo usarlos aquí. DEL es atómico en sí mismo (o eso creo). Y no puedo obtener valores de KEYS hasta que hago EXEC, así que no puedo usar KEYS y DEL en el mismo MULTI.
Alexander Gladysh
5

FYI.

  • solo usando bash y redis-cli
  • sin usar keys(esto usa scan)
  • funciona bien en modo de clúster
  • no atómico

Tal vez solo necesite modificar caracteres en mayúscula.

scan-match.sh

#!/bin/bash
rcli=“/YOUR_PATH/redis-cli" 
default_server="YOUR_SERVER"
default_port="YOUR_PORT"
servers=`$rcli -h $default_server -p $default_port cluster nodes | grep master | awk '{print $2}' | sed 's/:.*//'`
if [ x"$1" == "x" ]; then 
    startswith="DEFAULT_PATTERN"
else
    startswith="$1"
fi
MAX_BUFFER_SIZE=1000
for server in $servers; do 
    cursor=0
    while 
        r=`$rcli -h $server -p $default_port scan $cursor match "$startswith*" count $MAX_BUFFER_SIZE `
        cursor=`echo $r | cut -f 1 -d' '`
        nf=`echo $r | awk '{print NF}'`
        if [ $nf -gt 1 ]; then
            for x in `echo $r | cut -f 1 -d' ' --complement`; do 
                echo $x
            done
        fi
        (( cursor != 0 ))
    do
        :
    done
done

clear-redis-key.sh

#!/bin/bash
STARTSWITH="$1"

RCLI=YOUR_PATH/redis-cli
HOST=YOUR_HOST
PORT=6379
RCMD="$RCLI -h $HOST -p $PORT -c "

./scan-match.sh $STARTSWITH | while read -r KEY ; do
    $RCMD del $KEY 
done

Ejecutar en bash prompt

$ ./clear-redis-key.sh key_head_pattern
plhn
fuente
5

Es posible que otras respuestas no funcionen si su clave contiene caracteres especiales, Guide$CLASSMETADATA][1]por ejemplo. Ajustar cada clave entre comillas asegurará que se eliminen correctamente:

redis-cli --scan --pattern sf_* | awk '{print $1}' | sed "s/^/'/;s/$/'/" | xargs redis-cli del
Quentin S.
fuente
2
Este script funciona perfecto, probado con más de 25000 claves.
Jordi
1
También puede agregar las comillas simples en awk usando esta expresión divertida 'awk' {print "'"' "'" $ 1 "'" '"'"} '`
Roberto Congiu
3

Una versión que usa SCAN en lugar de KEYS (como se recomienda para servidores de producción) y en --pipelugar de xargs.

Prefiero canalizar sobre xargs porque es más eficiente y funciona cuando sus claves contienen comillas u otros caracteres especiales que su shell intenta e interpreta. La sustitución de expresiones regulares en este ejemplo envuelve la clave entre comillas dobles y escapa a las comillas dobles dentro.

export REDIS_HOST=your.hostname.com
redis-cli -h "$REDIS_HOST" --scan --pattern "YourPattern*" > /tmp/keys
time cat /tmp/keys | perl -pe 's/"/\\"/g;s/^/DEL "/;s/$/"/;'  | redis-cli -h "$REDIS_HOST" --pipe
tekumara
fuente
¡Esta solución funcionó bien para mí incluso con teclas de aproximadamente 7 m!
Danny
2

Esta no es una respuesta directa a la pregunta, pero dado que llegué aquí para buscar mis propias respuestas, compartiré esto aquí.

Si tiene decenas o cientos de millones de claves con las que tiene que coincidir, las respuestas que se dan aquí harán que Redis no responda por una cantidad significativa de tiempo (¿minutos?) Y posiblemente se bloquee debido al consumo de memoria (asegúrese de que se guarde en segundo plano patear en medio de su operación).

El siguiente enfoque es innegablemente feo, pero no encontré uno mejor. La atomicidad está fuera de discusión aquí, en este caso el objetivo principal es mantener a Redis activo y receptivo el 100% del tiempo. Funcionará perfectamente si tiene todas sus claves en una de las bases de datos y no necesita hacer coincidir ningún patrón, pero no puede usar http://redis.io/commands/FLUSHDB debido a su naturaleza de bloqueo.

La idea es simple: escriba un script que se ejecute en un bucle y utilice la operación O (1) como http://redis.io/commands/SCAN o http://redis.io/commands/RANDOMKEY para obtener claves, comprueba si haga coincidir el patrón (si lo necesita) y http://redis.io/commands/DEL ellos uno por uno.

Si hay una mejor manera de hacerlo, hágamelo saber, actualizaré la respuesta.

Ejemplo de implementación con randomkey en Ruby, como una tarea de rastrillo, un sustituto sin bloqueo de algo como redis-cli -n 3 flushdb:

desc 'Cleanup redis'
task cleanup_redis: :environment do
  redis = Redis.new(...) # connection to target database number which needs to be wiped out
  counter = 0
  while key = redis.randomkey               
    puts "Deleting #{counter}: #{key}"
    redis.del(key)
    counter += 1
  end
end
Spajus
fuente
2

Se implementa de manera simple a través de la funcionalidad "Eliminar rama" en FastoRedis , solo seleccione la rama que desea eliminar.

ingrese la descripción de la imagen aquí

Topilski Alexandr
fuente
¿Esto lo hace atómicamente? ¿Y cómo ayuda esto a hacerlo en código ?
Matthew leyó el
2

Utilice este comando e intente:

redis-cli --raw keys "$PATTERN" | xargs redis-cli del
Suf_Malek
fuente
No atómico, y duplica otras respuestas.
Matthew leyó el
1

Probé la mayoría de los métodos mencionados anteriormente, pero no funcionaron para mí, después de algunas búsquedas encontré estos puntos:

  • si tiene más de una base de datos en redis, debe determinar la base de datos utilizando -n [number]
  • si usa algunas teclas, delpero si hay miles o millones de teclas, es mejor usar unlinkporque unlink no se bloquea mientras que del está bloqueando, para más información visite esta página unlink vs del
  • también keysson como del y está bloqueando

así que usé este código para eliminar claves por patrón:

 redis-cli -n 2 --scan --pattern '[your pattern]' | xargs redis-cli -n 2 unlink 
mahdi yousefi
fuente
0

¿Eliminación masiva atómica del pobre hombre?

tal vez podría configurarlos para que EXPIRE el mismo segundo, como unos minutos en el futuro, y luego esperar hasta ese momento y verlos a todos "autodestruirse" al mismo tiempo.

pero no estoy seguro de qué tan atómico sería eso.

Chris
fuente
0

Anuncio de ahora, puede usar un cliente redis y realizar primero SCAN (admite la coincidencia de patrones) y luego DEL cada tecla individualmente.

Sin embargo, hay un problema en el redis github oficial para crear un patrón de coincidencia del patrón aquí , ¡ve a mostrarle algo de amor si te resulta útil!

Asalle
fuente
-1

Apoyo todas las respuestas relacionadas con tener alguna herramienta o ejecutar la expresión Lua.

Una opción más de mi lado:

En nuestras bases de datos de producción y preproducción hay miles de claves. De vez en cuando necesitamos eliminar algunas claves (mediante alguna máscara), modificarlas según algunos criterios, etc. Por supuesto, no hay forma de hacerlo manualmente desde la CLI, especialmente si tiene un fragmento (512 dbs lógicos en cada físico).

Para este propósito, escribo la herramienta de cliente java que hace todo este trabajo. En caso de eliminación de claves, la utilidad puede ser muy simple, solo hay una clase allí:

public class DataCleaner {

    public static void main(String args[]) {
        String keyPattern = args[0];
        String host = args[1];
        int port = Integer.valueOf(args[2]);
        int dbIndex = Integer.valueOf(args[3]);

        Jedis jedis = new Jedis(host, port);

        int deletedKeysNumber = 0;
        if(dbIndex >= 0){
            deletedKeysNumber += deleteDataFromDB(jedis, keyPattern, dbIndex);
        } else {
            int dbSize = Integer.valueOf(jedis.configGet("databases").get(1));
            for(int i = 0; i < dbSize; i++){
                deletedKeysNumber += deleteDataFromDB(jedis, keyPattern, i);
            }
        }

        if(deletedKeysNumber == 0) {
            System.out.println("There is no keys with key pattern: " + keyPattern + " was found in database with host: " + host);
        }
    }

    private static int deleteDataFromDB(Jedis jedis, String keyPattern, int dbIndex) {
        jedis.select(dbIndex);
        Set<String> keys = jedis.keys(keyPattern);
        for(String key : keys){
            jedis.del(key);
            System.out.println("The key: " + key + " has been deleted from database index: " + dbIndex);
        }

        return keys.size();
    }

}
Denys
fuente
-1

El siguiente comando funcionó para mí.

redis-cli -h redis_host_url KEYS "*abcd*" | xargs redis-cli -h redis_host_url DEL
Sumit Saurabh
fuente
-3

Spring RedisTemplate proporciona la funcionalidad. RedissonClient en la última versión ha desaprobado la funcionalidad "deleteByPattern".

Set<String> keys = redisTemplate.keys("geotag|*");
redisTemplate.delete(keys);
Arijeet Saha
fuente
2
Actualicé el código de muestra de Redisson. Su código no tiene un enfoque atómico como lo hace Redisson. Pueden aparecer nuevas claves entre invocaciones keysy deletemétodos.
Nikita Koksharov