W Redis podstawową jednostką dystrybucji jest gniazdo skrótu. Rozproszone wersje redis – w tym open source Redis Cluster, komercyjny Redis Enterprise, a nawet AWS ElastiCache – mogą poruszać się tylko po jednym gnieździe danych na raz.
Prowadzi to do interesującego problemu - koślawych automatów. Co się stanie, jeśli jeden slot (lub kilka slotów) będzie zawierał większość danych?
Czy to w ogóle możliwe?
Redis decyduje o gnieździe skrótu dla klucza przy użyciu dobrze opublikowanego algorytmu. Ten algorytm zwykle zapewnia dobrą dystrybucję kluczy.
Ale programiści mogą wpływać na algorytm, określając tag mieszający . Znacznik hash to część klucza ujęta w nawiasy klamrowe {...}
. Po określeniu hash-tagu zostanie on użyty do określenia miejsca na hash.
Hash-tag w redis jest tym, co większość baz danych nazwałaby kluczem partycji. Jeśli wybierzesz niewłaściwy klucz partycji, otrzymasz krzywe gniazda.
Na przykład, jeśli twoje klucze są takie jak {users}:1234
i {users}:5432
, redis będzie przechowywać wszystkich użytkowników w tym samym gnieździe skrótu.
Jaka jest poprawka?
Poprawka jest koncepcyjnie proste - musisz zmienić nazwę klucza, aby usunąć nieprawidłowy hash tag. Zmieniam więc nazwę {users}:1234
do users:{1234}
a nawet users:1234
powinien załatwić sprawę…
… z wyjątkiem tego, że polecenie zmiany nazwy nie działa w klastrze redis.
Więc jedynym wyjściem jest najpierw zrzucenie klucza, a następnie przywrócenie go pod nową nazwą.
Oto jak to wygląda w kodzie:
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)