Poin pertama saya adalah mencatat bahwa 4 GB ketat untuk menyimpan 20 juta set yang diurutkan. Percobaan cepat menunjukkan bahwa 20 juta pengguna, masing-masing dengan 20 tag akan memakan waktu sekitar 8 GB pada kotak 64 bit (dan ini memperhitungkan pengoptimalan memori daftar zip yang diurutkan yang disediakan dengan Redis 2.4 - jangan coba ini dengan versi sebelumnya) .
Kumpulan yang diurutkan adalah struktur data yang ideal untuk mendukung kasus penggunaan Anda. Saya akan menggunakannya persis seperti yang Anda jelaskan.
Seperti yang Anda tunjukkan, KUNCI tidak dapat digunakan untuk beralih pada kunci. Ini lebih dimaksudkan sebagai perintah debug. Untuk mendukung iterasi kunci, Anda perlu menambahkan struktur data untuk menyediakan jalur akses ini. Satu-satunya struktur di Redis yang dapat mendukung iterasi adalah daftar dan set yang diurutkan (melalui metode rentang). Namun, mereka cenderung mengubah algoritma iterasi O(n) menjadi O(n^2) (untuk daftar), atau O(nlogn) (untuk zset). Daftar juga merupakan pilihan yang buruk untuk menyimpan kunci karena akan sulit untuk mempertahankannya saat kunci ditambahkan/dihapus.
Solusi yang lebih efisien adalah dengan menambahkan indeks yang terdiri dari set reguler. Anda perlu menggunakan fungsi hash untuk mengaitkan pengguna tertentu ke bucket, dan menambahkan id pengguna ke set yang sesuai dengan bucket ini. Jika id pengguna adalah nilai numerik, fungsi modulo sederhana sudah cukup. Jika tidak, fungsi hashing string sederhana akan berhasil.
Jadi untuk mendukung iterasi pada user:1000, user:2000 dan user:1001, mari kita pilih fungsi modulo 1000. user:1000 dan user:2000 akan dimasukkan ke dalam bucket index:0 sedangkan user:1001 akan dimasukkan ke dalam bucket index:1.
Jadi di atas zset, kita sekarang memiliki kunci berikut:
index:0 => set[ 1000, 2000 ]
index:1 => set[ 1001 ]
Dalam set, awalan kunci tidak diperlukan, dan ini memungkinkan Redis untuk mengoptimalkan konsumsi memori dengan membuat serialisasi set asalkan disimpan cukup kecil (pengoptimalan set bilangan bulat diusulkan oleh Sripathi Krishnan).
Iterasi global terdiri dari loop sederhana pada bucket dari 0 hingga 1000 (tidak termasuk). Untuk setiap bucket, perintah SMEMBERS diterapkan untuk mengambil set yang sesuai, dan klien kemudian dapat mengulangi item individual.
Berikut adalah contoh dengan Python:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ----------------------------------------------------
import redis, random
POOL = redis.ConnectionPool(host='localhost', port=6379, db=0)
NUSERS = 10000
NTAGS = 500
NBUCKETS = 1000
# ----------------------------------------------------
# Fill redis with some random data
def fill(r):
p = r.pipeline()
# Create only 10000 users for this example
for id in range(0,NUSERS):
user = "user:%d" % id
# Add the user in the index: a simple modulo is used to hash the user id
# and put it in the correct bucket
p.sadd( "index:%d" % (id%NBUCKETS), id )
# Add random tags to the user
for x in range(0,20):
tag = "tag:%d" % (random.randint(0,NTAGS))
p.zincrby( user, tag, 1 )
# Flush the pipeline every 1000 users
if id % 1000 == 0:
p.execute()
print id
# Flush one last time
p.execute()
# ----------------------------------------------------
# Iterate on all the users and display their 5 highest ranked tags
def iterate(r):
# Iterate on the buckets of the key index
# The range depends on the function used to hash the user id
for x in range(0,NBUCKETS):
# Iterate on the users in this bucket
for id in r.smembers( "index:%d"%(x) ):
user = "user:%d" % int(id)
print user,r.zrevrangebyscore(user,"+inf","-inf", 0, 5, True )
# ----------------------------------------------------
# Main function
def main():
r = redis.Redis(connection_pool=POOL)
r.flushall()
m = r.info()["used_memory"]
fill(r)
info = r.info()
print "Keys: ",info["db0"]["keys"]
print "Memory: ",info["used_memory"]-m
iterate(r)
# ----------------------------------------------------
main()
Dengan mengubah konstanta, Anda juga dapat menggunakan program ini untuk mengevaluasi konsumsi memori global dari struktur data ini.
IMO strategi ini sederhana dan efisien, karena menawarkan kompleksitas O(1) untuk menambah/menghapus pengguna, dan kompleksitas O(n) yang sebenarnya untuk beralih pada semua item. Satu-satunya downside adalah urutan iterasi kunci acak.