Redis
 sql >> Database >  >> NoSQL >> Redis

Come risolvere gli slot hash sbilenchi in Redis

In Redis, l'unità di distribuzione principale è uno slot hash. Le versioni distribuite di redis, tra cui Redis Cluster open source, Redis Enterprise commerciale e persino AWS ElastiCache, possono spostare i dati solo 1 slot alla volta.

Questo porta a un problema interessante:gli slot sbilenchi. Cosa succede se uno slot (o alcuni slot) finiscono per avere la maggior parte dei dati?

È possibile?

Redis decide l'hash-slot per una chiave utilizzando un algoritmo ben pubblicato. Questo algoritmo di solito garantisce che le chiavi siano ben distribuite.

Ma gli sviluppatori possono influenzare l'algoritmo specificando un tag hash . Un hash tag è una parte della chiave racchiusa tra parentesi graffe {...} . Quando viene specificato un hash-tag, verrà utilizzato per decidere lo slot hash.

L'hash-tag in redis è ciò che la maggior parte dei database chiamerebbe una chiave di partizione. Se scegli una chiave di partizione sbagliata, otterrai slot sbilenco.

Ad esempio, se le tue chiavi sono come {users}:1234 e {users}:5432 , redis memorizzerà tutti gli utenti nello stesso hash slot.

Qual ​​è la soluzione?

La soluzione è concettualmente semplice:è necessario rinominare la chiave per rimuovere l'hashtag errato. Quindi rinominando {users}:1234 a users:{1234} o anche users:1234 dovrebbe fare il trucco...

... tranne per il fatto che il comando rename non funziona nel cluster redis.

Quindi l'unica via d'uscita è prima scaricare la chiave e poi ripristinarla con il nuovo nome.

Ecco come appare nel codice:



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)