sql >> Databasteknik >  >> NoSQL >> Redis

Hur man fixar sneda hash-slots i Redis

I Redis är den primära distributionsenheten en hash-slot. Distribuerade versioner av redis - inklusive öppen källkod Redis Cluster, kommersiella Redis Enterprise och till och med AWS ElastiCache - kan bara flytta runt data en plats åt gången.

Detta leder till ett intressant problem - sneda slots. Vad händer om en fack (eller några fack) har det mesta av data?

Är det ens möjligt?

Redis bestämmer hash-sloten för en nyckel med hjälp av en väl publicerad algoritm. Denna algoritm kommer vanligtvis att säkerställa att nycklar är väl fördelade.

Men utvecklare kan påverka algoritmen genom att ange en hash-tagg . En hash-tagg är en del av nyckeln omsluten av klammerparenteser {...} . När en hash-tagg är specificerad kommer den att användas för att bestämma hashplatsen.

Hash-taggen i redis är vad de flesta databaser skulle kalla en partitionsnyckel. Om du väljer fel partitionsnyckel kommer du att få skeva platser.

Som ett exempel, om dina nycklar är som {users}:1234 och {users}:5432 , kommer redis att lagra alla användare i samma hashplats.

Vad är åtgärden?

Korrigeringen är konceptuellt enkelt - du måste byta namn på nyckeln för att ta bort den felaktiga hash-taggen. Så döper om {users}:1234 till users:{1234} eller till och med users:1234 borde göra susen...

… förutom att kommandot byt namn inte fungerar i redis-kluster.

Så den enda utvägen är att först dumpa nyckeln och sedan återställa den mot det nya namnet.

Så här ser det ut i kod:



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)


  1. MongoDB $slice

  2. Mongoimport av JSON-fil

  3. Rails 3:hur man använder aktiv skiva och mongoid samtidigt

  4. Hantera MySQL, MongoDB &PostgreSQL med ChatOps från Slack