Redis sorted set, her üyesine bir puan (score) bağlanmış tekil string'lerden oluşan ve her zaman puana göre sıralı duran bir koleksiyondur. ZADD ile üyeyi puanıyla eklersiniz, ZRANGE ile sıra, puan ya da alfabetik aralığa göre okursunuz, ZRANK ile bir üyenin kaçıncı sırada olduğunu, ZSCORE ile puanını öğrenirsiniz. Sıralama sorgu anında yapılmaz; veri yapısı zaten sıralı tutulduğu için en yüksek on puanı istemek bile sıralama maliyeti getirmez.
Bu yazıda sorted set komutlarını resmî Redis belgelerindeki örneklerle ele alıyoruz: sıralama kuralı, ZADD seçenekleri (NX, XX, GT, LT, CH, INCR), ZRANGE ile indeks, puan ve alfabetik aralıklar, sıra sorguları, aralıkla silme, ZPOPMIN ile öncelik kuyruğu ve ZUNION/ZINTER. Bu tipin en bilinen uygulaması olan sıralama tablosunu ayrıntılı olarak Redis leaderboard yazısında anlatıyoruz. Genel tanıtım için Redis nedir, nasıl kullanılır, serinin tamamı için Redis öğrenme rehberi sayfasına bakabilirsiniz.
Sorted set nasıl sıralanır?
Belgeler sorted set'i set ile hash arasında bir karışım olarak tarif eder. Set gibi üyeleri tekildir, aynı üye iki kez bulunamaz. Hash gibi her üye bir değere, burada kayan noktalı bir puana eşlenir. Sıralama kuralı iki adımlıdır: puanı büyük olan üye daha büyüktür; puanlar tam olarak eşitse üyeler string olarak alfabetik (sözlük) sıraya göre dizilir. Bu karşılaştırma bayt düzeyindedir, yani C dilindeki memcmp ile yapılır ve dil ya da yerel ayara göre değişmez.
Uygulama tarafında sorted set, hem bir atlamalı liste (skip list) hem de bir hash tablosu içeren çift yönlü bir yapıyla gerçeklenmiştir. Her eklemede Redis O(log(N)) iş yapar, buna karşılık sıralı veri istendiğinde ek bir iş yapmaz. Belgelere göre sorted set işlemlerinin çoğu O(log(n)) karmaşıklığındadır.
ZADD ile üye ve puan eklemek

ZADD, SADD'e benzer ama her üyeden önce bir puan alır. Birden fazla puan ve üye çifti tek komutta verilebilir. Belgelerdeki yarışçı örneği:
> ZADD racer_scores 10 "Norem"
(integer) 1
> ZADD racer_scores 12 "Castilla"
(integer) 1
> ZADD racer_scores 8 "Sam-Bodden" 10 "Royce" 6 "Ford" 14 "Prickett"
(integer) 4Dönüş değeri varsayılan olarak yeni eklenen üye sayısıdır. Zaten var olan bir üyeyi yeni puanla eklerseniz puanı güncellenir, üye doğru konuma taşınır ve bu üye dönüş sayısına katılmaz. Puan, çift duyarlıklı kayan noktalı bir sayının string gösterimi olmalıdır; +inf ve -inf de geçerlidir. Anahtar başka bir tip tutuyorsa hata döner.
ZADD seçenekleri
ZADD'in davranışını değiştiren altı seçenek vardır. NX ile XX birbirini dışlar, GT ile LT de birbirini dışlar.
| Seçenek | Etkisi |
|---|---|
| NX | Yalnızca yeni üye ekler, var olanı güncellemez |
| XX | Yalnızca var olan üyeyi günceller, yeni üye eklemez |
| GT | Var olan üyeyi yalnızca yeni puan daha büyükse günceller, yeni üye eklemeyi engellemez |
| LT | Var olan üyeyi yalnızca yeni puan daha küçükse günceller, yeni üye eklemeyi engellemez |
| CH | Dönüş değerini eklenen artı güncellenen üye sayısına çevirir |
| INCR | Puanı ayarlamak yerine artırır, ZINCRBY gibi davranır, tek çift kabul eder |
Bu seçenekler gerçek senaryolarda işe yarar. "Oyuncunun yalnızca en yüksek puanını sakla" kuralı GT ile tek komuttur. "Puanı güncelle ama yeni oyuncu ekleme" kuralı XX ile yazılır. Güncellemelerin de sayılmasını istiyorsanız CH ekleyin. Aşağıdaki komutlar belgelerde çıktısıyla gösterilmediği için sonuçlarını açıklıyoruz:
ZADD best_scores GT 120 "ayse"
ZADD best_scores XX CH 90 "ayse"
ZADD best_scores INCR 5 "ayse"İlk komut ayse yoksa onu 120 puanla ekler ve 1 döndürür; varsa puanı yalnızca 120 mevcut puandan büyükse değiştirir. İkinci komut yalnızca var olan üyeyi 90'a çeker ve CH sayesinde puan değiştiyse 1 döndürür. Üçüncü komut puanı 5 artırır ve yeni puanı bulk string olarak döndürür. Belgelere göre işlem XX, NX, LT ya da GT seçeneklerinden biriyle çakıştığı için iptal edilirse yanıt nil olur.
ZRANGE ile okumak: indeks, REV ve WITHSCORES
ZRANGE varsayılan olarak indeks (sıra) aralığıyla çalışır ve düşük puandan yükseğe doğru döndürür. 0 -1, LRANGE'de olduğu gibi ilk elemandan son elemana kadar demektir. REV sırayı yüksekten düşüğe çevirir:
> ZRANGE racer_scores 0 -1
1) "Ford"
2) "Sam-Bodden"
3) "Norem"
4) "Royce"
5) "Castilla"
6) "Prickett"
> ZRANGE racer_scores 0 -1 REV
1) "Prickett"
2) "Castilla"
3) "Royce"
4) "Norem"
5) "Sam-Bodden"
6) "Ford"Norem ve Royce'un ikisi de 10 puana sahiptir; eşit puanda alfabetik kural devreye girdiği için Norem önce gelir. REV kullanıldığında eşit puanlar da ters alfabetik sıraya göre dizilir. WITHSCORES eklerseniz yanıt üye, puan, üye, puan biçiminde düz bir dizi olur:
> ZRANGE racer_scores 0 -1 withscores
1) "Ford"
2) "6"
3) "Sam-Bodden"
4) "8"
5) "Norem"
6) "10"
7) "Royce"
8) "10"
9) "Castilla"
10) "12"
11) "Prickett"
12) "14"İndeks aralığı kuralları LRANGE ile aynıdır: bitiş dahildir, aralık dışı indeks hata üretmez, başlangıç sonun ötesindeyse boş liste döner. Belgeler ayrıca ZRANGE'in Redis 6.2.0 ile birlikte ZREVRANGE, ZRANGEBYSCORE, ZREVRANGEBYSCORE, ZRANGEBYLEX ve ZREVRANGEBYLEX komutlarının yerini alabildiğini belirtir; yeni kodda tek komut ve seçeneklerle çalışmak daha sadedir.
BYSCORE ile puan aralığı
BYSCORE eklendiğinde başlangıç ve bitiş indeks değil puan sınırı olarak yorumlanır. Sınırlar varsayılan olarak dahildir; değerin önüne ( koyarsanız hariç olur. -inf ve +inf en düşük ya da en yüksek puanı bilmenize gerek bırakmaz. 10 ve altı puanlı yarışçılar:
> ZRANGE racer_scores -inf 10 BYSCORE
1) "Ford"
2) "Sam-Bodden"
3) "Norem"
4) "Royce"LIMIT offset count, SQL'deki LIMIT gibi eşleşen sonuçlardan bir alt aralık alır ve yalnızca BYSCORE ya da BYLEX ile kullanılabilir. Belgelerdeki örnekte 1 hariç sonsuza kadar olan puanlar istenir ve yalnızca ikinci sonuç döndürülür:
> DEL myzset
(integer) 1
> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3
> ZRANGE myzset (1 +inf BYSCORE LIMIT 1 1
1) "three"REV ile BYSCORE birlikte kullanıldığında sınırların sırası da değişir: başlangıç en yüksek, bitiş en düşük puan olmalıdır. Örneğin ZRANGE zset 10 5 REV BYSCORE 10'dan küçük ve 5'ten büyük puanları döndürür. Sınırları ters yazarsanız hata almazsınız ama sonuç boş gelir; bu, en sık yaşanan şaşkınlıklardan biridir. Büyük bir offset değeri de Redis'in o kadar elemanı geçmesini gerektirir ve O(N) maliyete yaklaşabilir.
BYLEX ile alfabetik aralık
Bütün üyeler aynı puana sahipse sorted set tamamen alfabetik sıralı olur ve BYLEX ile string aralığı sorgulayabilirsiniz. Sınırlar [ (dahil) ya da ( (hariç) ile başlamalıdır; - ve + sonsuz küçük ve sonsuz büyük string anlamına gelir.
> ZADD racer_scores 0 "Norem" 0 "Sam-Bodden" 0 "Royce" 0 "Castilla" 0 "Prickett" 0 "Ford"
(integer) 3
> ZRANGE racer_scores 0 -1
1) "Castilla"
2) "Ford"
3) "Norem"
4) "Prickett"
5) "Royce"
6) "Sam-Bodden"
> ZRANGE racer_scores [A [L BYLEX
1) "Castilla"
2) "Ford"Belgelerdeki sırada bu komut, kümede yalnızca Norem, Royce ve Prickett kalmışken çalıştırılır. Bu yüzden ZADD 3 döndürür: altı üyeden üçü yeni eklenir, diğer üçünün puanı 0'a güncellenir. Farklı puanlı üyelerde BYLEX sonucunun belirsiz olduğunu unutmayın. Belgeler bu özelliğin sorted set'i genel amaçlı bir indeks gibi kullanmayı sağladığını ve otomatik tamamlama gibi işler için üyenin başına normalize edilmiş bir önek koyma tekniğini anlatır.
Sıra ve puan sorguları: ZRANK, ZREVRANK, ZSCORE

ZRANK bir üyenin düşükten yükseğe sıradaki konumunu sıfırdan başlayarak verir; ZREVRANK aynı şeyi yüksekten düşüğe yapar. Üye yoksa nil döner. İsteğe bağlı WITHSCORE konumla birlikte puanı da döndürür.
> DEL racer_scores
(integer) 1
> ZADD racer_scores 10 "Norem" 10 "Royce" 14 "Prickett"
(integer) 3
> ZRANK racer_scores "Norem"
(integer) 0
> ZREVRANK racer_scores "Norem"
(integer) 2ZSCORE tek üyenin puanını string olarak döndürür ve üyenin var olup olmadığını kontrol etmek için de kullanılabilir. ZMSCORE birden fazla üyenin puanını tek seferde getirir ve olmayan her üye için nil koyar. ZCARD üye sayısını, ZCOUNT belirli bir puan aralığındaki üye sayısını verir; belgelere göre ZCOUNT sıra bilgisinden yararlandığı için yalnızca O(log(N)) çalışır.
> ZADD myzset 1 "one"
(integer) 1
> ZADD myzset 2 "two"
(integer) 1
> ZADD myzset 3 "three"
(integer) 1
> ZCOUNT myzset -inf +inf
(integer) 3
> ZCOUNT myzset (1 3
(integer) 2ZINCRBY ile puanı artırmak
ZINCRBY üyenin puanını verdiğiniz miktar kadar artırır ve yeni puanı döndürür. Üye yoksa önceki puanı 0 kabul edilerek eklenir; negatif miktarla azaltma yapılır. Belgelerdeki örnek ZADD ile ZINCRBY arasındaki farkı gösterir:
> ZADD racer_scores 100 "Wood"
(integer) 1
> ZADD racer_scores 100 "Henshaw"
(integer) 1
> ZADD racer_scores 150 "Henshaw"
(integer) 0
> ZINCRBY racer_scores 50 "Wood"
"150"
> ZINCRBY racer_scores 50 "Henshaw"
"200"Henshaw'ın puanı önce ZADD ile önceki değerden bağımsız olarak 150'ye ayarlanır, sonra ZINCRBY ile 50 artırılarak 200 olur. Puan güncellemesi O(log(N)) olduğu için belgeler sorted set'i çok sık güncellenen senaryolar için uygun bulur. Sayfalama, eşit puan ve oyuncunun kendi sırası gibi konular sıralama tablosu yazısında ele alınıyor.
Silme: ZREM ve aralıkla silme
ZREM belirttiğiniz üyeleri siler ve silinen sayısını döndürür. Aralıkla silmek için ZREMRANGEBYSCORE (puan aralığı), ZREMRANGEBYRANK (sıra aralığı) ve ZREMRANGEBYLEX (alfabetik aralık) vardır. Belgelerdeki örnekte Castilla ile 10'dan kesinlikle düşük puanlı yarışçılar silinir:
> ZREM racer_scores "Castilla"
(integer) 1
> ZREMRANGEBYSCORE racer_scores -inf 9
(integer) 2
> ZRANGE racer_scores 0 -1
1) "Norem"
2) "Royce"
3) "Prickett"Bu örnek ilk ZADD oturumundaki altı yarışçının üzerinde çalışır. Aralıkla silme, zaman damgasını puan olarak kullanan yapılarda eski kayıtları temizlemek için idealdir: ZREMRANGEBYSCORE key -inf eşik ile eşikten eski her şeyi tek komutla atarsınız. ZREMRANGEBYRANK key 0 -101 gibi bir çağrı ise yalnızca en yüksek puanlı 100 üyeyi bırakır.
Öncelik kuyruğu: ZPOPMIN, ZPOPMAX ve ZMPOP
ZPOPMIN en düşük puanlı üyeleri, ZPOPMAX en yüksek puanlı üyeleri siler ve puanlarıyla birlikte döndürür. count verilmezse bir üye çekilir; küme boyutundan büyük count hata üretmez. Belgelerin karşılaştırma sayfası sorted set'i öncelik kuyrukları için uygun yapı olarak anar: puan önceliği temsil eder, en acil iş her zaman bir uçta durur.
> ZADD myzset 1 "one"
(integer) 1
> ZADD myzset 2 "two"
(integer) 1
> ZADD myzset 3 "three"
(integer) 1
> ZPOPMIN myzset
1) "one"
2) "1"ZMPOP, LMPOP'un sorted set karşılığıdır: verdiğiniz anahtarlar arasında ilk dolu kümeden MIN ya da MAX uçtan COUNT kadar üye çeker ve yanıtta anahtar adını da döndürür. Engelleyen BZPOPMIN, BZPOPMAX ve BZMPOP sürümleri de vardır.
Birden çok kümeyi birleştirmek: ZUNION ve ZINTER
ZUNION ve ZINTER birden fazla sorted set'i birleştirir ya da kesiştirir. İlk argüman anahtar sayısıdır. Birden çok kümede bulunan üyenin puanları varsayılan olarak toplanır; AGGREGATE ile MIN, MAX ya da COUNT seçebilirsiniz. WEIGHTS her kümenin puanlarını birleştirmeden önce bir katsayıyla çarpar.
> ZADD zset1 1 "one"
(integer) 1
> ZADD zset1 2 "two"
(integer) 1
> ZADD zset2 1 "one"
(integer) 1
> ZADD zset2 2 "two"
(integer) 1
> ZADD zset2 3 "three"
(integer) 1
> ZUNION 2 zset1 zset2 WITHSCORES
1) "one"
2) "2"
3) "three"
4) "3"
5) "two"
6) "4"Burada one iki kümede de 1 puanla bulunduğu için toplam 2, two toplam 4 olur; three yalnızca ikinci kümede olduğu için 3 kalır. Sonucu bir anahtara yazmak için ZUNIONSTORE ve ZINTERSTORE kullanılır. Örneğin haftalık puan tablolarını ZUNIONSTORE ile toplayarak aylık tabloyu üretebilirsiniz. Çok anahtarlı bu komutların cluster davranışı için hash slot yazısına bakın.
Uygulamalı örnek: zamanı puan olarak kullanmak
Puanın her zaman "puan" olması gerekmez. Unix zaman damgasını puan olarak kullanırsanız sorted set zamana göre sıralı bir indekse dönüşür. Örneğin ileri bir tarihte çalışacak işleri tutalım; komutlar belgelerde çıktısıyla gösterilmediği için sonuçlarını açıklıyoruz:
ZADD delayed:jobs 1767225600 "job:42"
ZADD delayed:jobs NX 1767229200 "job:43"
ZRANGE delayed:jobs -inf 1767226000 BYSCORE LIMIT 0 10
ZREM delayed:jobs "job:42"İlk iki komut işleri çalışma zamanlarıyla ekler; NX aynı işin zamanının yanlışlıkla ezilmesini engeller. Üçüncü komut zamanı gelmiş en fazla on işi en eskisinden başlayarak döndürür. İşi işledikten sonra dördüncü komutla silersiniz. Birden fazla işçi aynı işi alabileceği için alma ve silme adımını atomik yapmak isterseniz bunu bir Lua betiğinde birleştirebilirsiniz; ayrıntı için Lua betikleri yazısına bakın. Belgeler sorted set ile kayan pencereli bir hız sınırlayıcı (rate limiter) da kurulabileceğini belirtir; algoritmalar API rate limiting stratejileri yazısında anlatılıyor.
Sık yapılan hatalar

- REV ile BYSCORE sınırlarını ters yazmak:
REVvarken ilk sınır büyük puan olmalıdır; aksi halde sonuç boş gelir. - Farklı puanlarla BYLEX: Alfabetik aralık yalnızca bütün üyeler aynı puana sahipken anlamlıdır.
- Türkçe karakterlerin sırası: Bayt karşılaştırması Türkçe alfabe sırasını izlemez.
- ZADD dönüşünü güncelleme sayısı sanmak:
CHolmadan yalnızca yeni eklenen üyeler sayılır. - Puan hassasiyeti: 2^53'ü aşan tamsayılar kesin saklanmaz.
- Büyük aralık okumak:
ZRANGEO(log(n) + m) çalışır; belgeler on binlerce sonuç döndüren sorgularda dikkatli olmanızı ister.
Sık Sorulan Sorular
Sorted set ile set arasındaki fark nedir?
İkisinde de üyeler tekildir. Set sırasızdır ve üyeye değer bağlamaz; sorted set her üyeye bir puan bağlar ve her zaman sıralı durur. Karşılığında sorted set daha fazla bellek ve işlem gerektirir. Set komutları için Redis set komutları yazısına bakın.
Eşit puanlı üyeler hangi sırayla gelir?
Puanlar eşitse üyeler bayt düzeyinde alfabetik sıraya göre dizilir; REV kullanıldığında bu sıra da tersine döner.
ZRANGEBYSCORE kullanmaya devam edebilir miyim?
Komut belgelerde duruyor, ancak ZRANGE sayfası Redis 6.2.0 ile birlikte BYSCORE, BYLEX ve REV seçenekleriyle bu komutların yerini alabildiğini belirtir. Yeni kodda ZRANGE kullanmak daha tutarlıdır.
Puan olarak ondalıklı sayı kullanabilir miyim?
Evet. Puanlar çift duyarlıklı kayan noktalı sayıdır; ZINCRBY ve ZADD INCR da ondalıklı artışları kabul eder.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Redis öğrenme rehberi · Redis leaderboard · Redis set · Redis list · API rate limiting · Redis nedir, nasıl kullanılır