Di Redis, unit distribusi utama adalah slot hash. Versi redis terdistribusi - termasuk Redis Cluster open source, Redis Enterprise komersial, dan bahkan AWS ElastiCache - hanya dapat memindahkan data 1 slot pada satu waktu.
Ini mengarah ke masalah yang menarik - slot miring. Bagaimana jika satu slot (atau beberapa slot) akhirnya memiliki sebagian besar data?
Apakah itu mungkin?
Redis memutuskan hash-slot untuk kunci menggunakan algoritma yang dipublikasikan dengan baik. Algoritme ini biasanya akan memastikan bahwa kunci terdistribusi dengan baik.
Tetapi pengembang dapat memengaruhi algoritme dengan menentukan tag hash . Tag hash adalah bagian dari kunci yang diapit kurung kurawal {...}
. Ketika tag hash ditentukan, tag tersebut akan digunakan untuk menentukan slot hash.
Hash-tag di redis adalah apa yang kebanyakan database sebut sebagai kunci partisi. Jika Anda memilih kunci partisi yang salah, Anda akan mendapatkan slot yang miring.
Sebagai contoh, jika kunci Anda seperti {users}:1234
dan {users}:5432
, redis akan menyimpan semua pengguna di slot hash yang sama.
Apa perbaikannya?
Perbaikannya adalah secara konseptual sederhana - Anda perlu mengganti nama kunci untuk menghapus tag hash yang salah. Jadi ganti nama {users}:1234
untuk users:{1234}
atau bahkan users:1234
harus melakukan triknya…
… kecuali bahwa perintah rename tidak berfungsi di redis cluster.
Jadi satu-satunya jalan keluar adalah membuang kuncinya terlebih dahulu, lalu mengembalikannya dengan nama baru.
Berikut tampilannya dalam kode:
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)