sql >> Base de Datos >  >> NoSQL >> Redis

Cómo arreglar ranuras hash asimétricas en Redis

En Redis, la unidad principal de distribución es una ranura hash. Las versiones distribuidas de redis, incluido Redis Cluster de código abierto, Redis Enterprise comercial e incluso AWS ElastiCache, solo pueden mover datos en una ranura a la vez.

Esto conduce a un problema interesante:las ranuras desequilibradas. ¿Qué pasa si una ranura (o algunas ranuras) terminan teniendo la mayoría de los datos?

¿Es eso posible?

Redis decide el hash-slot para una clave utilizando un algoritmo bien publicado. Este algoritmo generalmente garantizará que las claves estén bien distribuidas.

Pero los desarrolladores pueden influir en el algoritmo especificando una etiqueta hash . Una etiqueta hash es una parte de la clave encerrada entre llaves {...} . Cuando se especifica una etiqueta hash, se utilizará para decidir la ranura hash.

La etiqueta hash en redis es lo que la mayoría de las bases de datos llamarían una clave de partición. Si elige una clave de partición incorrecta, obtendrá ranuras desequilibradas.

Como ejemplo, si sus claves son como {users}:1234 y {users}:5432 , redis almacenará a todos los usuarios en la misma ranura hash.

¿Cuál es la solución?

La solución es conceptualmente simple:debe cambiar el nombre de la clave para eliminar la etiqueta hash incorrecta. Así que renombrando {users}:1234 a users:{1234} o incluso users:1234 debería hacer el truco…

… excepto que el comando de cambio de nombre no funciona en el clúster de Redis.

Entonces, la única salida es primero volcar la clave y luego restaurarla con el nuevo nombre.

Así es como se ve en el código:



from redis import StrictRedis
try:
    from itertools import izip_longest
except:
    from itertools import zip_longest as izip_longest


def get_batches(iterable, batch_size=2, fillvalue=None):
    """
    Chunks a very long iterable into smaller chunks of `batch_size`
    For example, if iterable has 9 elements, and batch_size is 2,
    the output will be 5 iterables - each of length 2. 
    The last iterable will also have 2 elements, 
    but the 2nd element will be `fillvalue`
    """
    args = [iter(iterable)] * batch_size
    return izip_longest(fillvalue=fillvalue, *args)


def migrate_keys(allkeys, host, port, password=None):
    db = 0
    red = StrictRedis(host=host, port=port, password=password)

    batches = get_batches(allkeys)
    for batch in batches:
        pipe = red.pipeline()
        keys = list(batch)
        for key in keys:
            if not key:
                continue
            pipe.dump(key)
            
        response = iter(pipe.execute())
        # New pipeline to run the restore command
        pipe = red.pipeline(transaction=False)
        for key in keys:
            if not key:
                continue
            obj = next(response)
            new_key = "restored." + key
            pipe.restore(new_key, 0, obj)

        pipe.execute()


if __name__ == '__main__':
    allkeys = ['users:18245', 'users:12328:answers_by_score', 'comments:18648']
    migrate_keys(allkeys, host="localhost", port=6379)