Redis
 sql >> Base de données >  >> NoSQL >> Redis

Comment réparer les emplacements de hachage déséquilibrés dans Redis

Dans Redis, l'unité principale de distribution est un emplacement de hachage. Les versions distribuées de Redis, y compris le cluster Redis open source, Redis Enterprise commercial et même AWS ElastiCache, ne peuvent déplacer les données que d'un emplacement à la fois.

Cela conduit à un problème intéressant - les fentes déséquilibrées. Que se passe-t-il si un emplacement (ou quelques emplacements) finit par avoir la plupart des données ?

Est-ce même possible ?

Redis décide de l'emplacement de hachage d'une clé à l'aide d'un algorithme bien publié. Cet algorithme garantit généralement que les clés sont bien distribuées.

Mais les développeurs peuvent influencer l'algorithme en spécifiant une balise de hachage . Une balise de hachage est une partie de la clé entre accolades {...} . Lorsqu'un hashtag est spécifié, il sera utilisé pour décider de l'emplacement de hachage.

Le hashtag dans redis est ce que la plupart des bases de données appelleraient une clé de partition. Si vous choisissez une mauvaise clé de partition, vous obtiendrez des emplacements déséquilibrés.

Par exemple, si vos clés sont du type {users}:1234 et {users}:5432 , redis stockera tous les utilisateurs dans le même emplacement de hachage.

Quel est le correctif ?

Le correctif est conceptuellement simple - vous devez renommer la clé pour supprimer la balise de hachage incorrecte. Donc renommer {users}:1234 aux users:{1234} ou même users:1234 devrait faire l'affaire…

… sauf que la commande rename ne fonctionne pas dans le cluster redis.

La seule solution consiste donc à vider d'abord la clé, puis à la restaurer avec le nouveau nom.

Voici à quoi cela ressemble dans le code :



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)