Redis
 sql >> Base de Dados >  >> NoSQL >> Redis

Como corrigir slots de hash desequilibrados no Redis


No Redis, a unidade primária de distribuição é um slot de hash. Versões distribuídas do redis - incluindo o Redis Cluster de código aberto, o Redis Enterprise comercial e até o AWS ElastiCache - só podem movimentar dados em um slot por vez.

Isso leva a um problema interessante - slots desiguais. E se um slot (ou alguns slots) acabarem tendo a maioria dos dados?

Isso é possível?


O Redis decide o slot de hash para uma chave usando um algoritmo bem publicado. Esse algoritmo geralmente garante que as chaves sejam bem distribuídas.

Mas os desenvolvedores podem influenciar o algoritmo especificando uma hash tag . Uma tag de hash é uma parte da chave entre chaves {...} . Quando uma tag de hash é especificada, ela será usada para decidir o slot de hash.

A hash-tag no redis é o que a maioria dos bancos de dados chamaria de chave de partição. Se você escolher uma chave de partição errada, obterá slots desiguais.

Por exemplo, se suas chaves forem como {users}:1234 e {users}:5432 , o redis armazenará todos os usuários no mesmo slot de hash.

Qual ​​é a correção?


A correção é conceitualmente simples - você precisa renomear a chave para remover a tag de hash incorreta. Então, renomeando {users}:1234 para users:{1234} ou mesmo users:1234 deve fazer o truque...

… exceto que o comando rename não funciona no cluster redis.

Portanto, a única saída é primeiro despejar a chave e restaurá-la com o novo nome.

Veja como fica no 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)