In Redis is de primaire distributie-eenheid een hash-slot. Gedistribueerde versies van redis - inclusief de open source Redis Cluster, commerciële Redis Enterprise en zelfs AWS ElastiCache - kunnen slechts met 1 slot tegelijk worden verplaatst.
Dit leidt tot een interessant probleem - scheve slots. Wat als één slot (of een paar slots) uiteindelijk de meeste gegevens heeft?
Is dat wel mogelijk?
Redis bepaalt de hash-slot voor een sleutel met behulp van een goed gepubliceerd algoritme. Dit algoritme zorgt er meestal voor dat de sleutels goed worden verdeeld.
Maar ontwikkelaars kunnen het algoritme beïnvloeden door een hashtag op te geven . Een hash-tag is een deel van de sleutel tussen accolades {...}
. Wanneer een hash-tag is opgegeven, wordt deze gebruikt om de hash-sleuf te bepalen.
De hash-tag in redis is wat de meeste databases een partitiesleutel zouden noemen. Als je een verkeerde partitiesleutel kiest, krijg je scheve slots.
Als uw sleutels bijvoorbeeld zijn zoals {users}:1234
en {users}:5432
, redis slaat alle gebruikers op in hetzelfde hashslot.
Wat is de oplossing?
De oplossing is conceptueel eenvoudig - u moet de sleutel hernoemen om de onjuiste hash-tag te verwijderen. Dus hernoemen van {users}:1234
aan users:{1234}
of zelfs users:1234
zou het lukken...
… behalve dat de opdracht hernoemen niet werkt in redis-cluster.
Dus de enige uitweg is om eerst de sleutel te dumpen en vervolgens te herstellen met de nieuwe naam.
Zo ziet het eruit in 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)