Redis HyperLogLog, bir kümedeki farklı (tekil) eleman sayısını tahmin eden olasılıksal bir veri yapısıdır. PFADD ile gördüğünüz her elemanı eklersiniz, PFCOUNT ile yaklaşık tekil sayıyı alırsınız, PFMERGE ile birden çok HyperLogLog'u birleştirirsiniz. Belgelere göre Redis uygulaması en fazla 12 KB bellek kullanır ve %0,81 standart hatayla sonuç verir. Yani milyonlarca farklı ziyaretçiyi saymak için milyonlarca kimliği saklamanız gerekmez; karşılığında kesin değil, çok yakın bir sayı alırsınız.

Bu yazıda resmî Redis belgelerine göre HyperLogLog'un ne zaman doğru araç olduğunu, üç komutun ayrıntılı davranışını, PFADD dönüş değerinin gerçekte ne anlama geldiğini, tek ve çok anahtarlı PFCOUNT arasındaki performans farkını, iç gösterimi ve set ile bitmap karşısındaki yerini anlatıyoruz. Redis'e genel bakış için Redis nedir, nasıl kullanılır, serinin tamamı için Redis öğrenme rehberi sayfasına bakabilirsiniz.

Tekil sayım neden pahalıdır?

Tekil sayım neden pahalıdır?

Bir sayfayı kaç farklı kişinin ziyaret ettiğini kesin olarak saymak için daha önce gördüğünüz her kimliği hatırlamanız gerekir; aksi halde aynı kişiyi iki kez sayarsınız. Belgelerin ifadesiyle bu, saymak istediğiniz eleman sayısıyla orantılı bellek demektir. Redis'te bunun kesin yolu her elemanı SADD ile bir set'e eklemek ve SCARD ile saymaktır; set tekrarları zaten kabul etmez. Ama on milyon farklı ziyaretçi, set içinde on milyon string demektir.

HyperLogLog (kısaca HLL) gibi algoritmalar bu dengeyi tersine çevirir: kesinlikten vazgeçip sabit bellek kullanır. Belgelere göre Redis uygulamasında bellek en kötü durumda 12 KB'tır, HLL az eleman görmüşse bundan çok daha azdır. Standart hata %1'in altındadır, belgelerin verdiği değerle %0,81. API, set kullanımına bilinçli olarak benzetilmiştir: SADD yerine PFADD, SCARD yerine PFCOUNT yazarsınız. Fark şudur: HLL elemanların kendisini saklamaz, yalnızca bir durum (register değerleri) tutar. Bu yüzden "şu eleman eklendi mi?" sorusunu soramaz, elemanları geri listeleyemezsiniz.

PFADD ve PFCOUNT ile ilk tahmin

Belgelerdeki bisiklet örneğinde iki ayrı HLL'e bisiklet adları eklenir ve sonra birleştirilir. Quaoar iki kümede de geçtiği için birleşimde bir kez sayılır:

text
> PFADD bikes Hyperion Deimos Phoebe Quaoar
(integer) 1
> PFCOUNT bikes
(integer) 4
> PFADD commuter_bikes Salacia Mimas Quaoar
(integer) 1
> PFMERGE all_bikes bikes commuter_bikes
OK
> PFCOUNT all_bikes
(integer) 6

Bu kadar küçük kümelerde tahmin kesin sayıyla örtüşür: 4 bisiklet ve birleşimde 4 + 3 - 1 = 6 tekil bisiklet. Eleman sayısı büyüdükçe sonuç gerçek değerin çevresinde küçük bir sapmayla gelir.

PFADD dönüş değeri "yeni eleman" demek değildir

PFADD 1 ya da 0 döndürür, ama bu bir elemanın daha önce görülüp görülmediğini söylemez. Belgelere göre komut, HLL'in iç register'larından en az biri değiştiyse, yani tahmin edilen kardinalite değişmiş olabilecekse 1, hiçbir register değişmediyse 0 döndürür. Aşağıdaki örnekte olduğu gibi daha önce eklenmiş elemanlar 0 üretir, ama yeni bir eleman da hiçbir register'ı değiştirmeyebilir ve 0 üretebilir. Bu yüzden PFADD dönüşünü "bu kullanıcı yeni mi?" sorusunun cevabı olarak kullanmayın; o soru için kesin bir yapı (set ya da bitmap) gerekir.

text
> PFADD hll foo bar zap
(integer) 1
> PFADD hll zap zap zap
(integer) 0
> PFADD hll foo bar
(integer) 0
> PFCOUNT hll
(integer) 3
> PFADD some-other-hll 1 2 3
(integer) 1
> PFCOUNT hll some-other-hll
(integer) 6

Yukarıdaki örnek tekrarların sayıyı değiştirmediğini gösterir: zap üç kez eklense de tahmin 3 kalır. Anahtar yoksa PFADD boş bir HLL oluşturur. Komutu hiç eleman vermeden yalnızca anahtar adıyla çağırmak da geçerlidir: anahtar varsa hiçbir şey yapmaz, yoksa yalnızca yapıyı oluşturur ve bu durumda 1 döndürür. Olmayan bir anahtar için PFCOUNT 0 döndürür.

Tek anahtarlı ve çok anahtarlı PFCOUNT

PFCOUNT tek anahtarla çağrıldığında o HLL'in tahminini döndürür. Birden çok anahtarla çağrıldığında anahtarları içeride geçici bir HLL'de birleştirir ve birleşimin tahminini döndürür; yukarıdaki örnekte PFCOUNT hll some-other-hll iki kümenin birleşimini, yani 6 sonucunu verir. Belgeler bu iki kullanımın anlam olarak da performans olarak da farklı olduğunu vurgular.

Tek anahtarlı çağrı çok hızlıdır, çünkü PFCOUNT son hesaplanan kardinaliteyi önbellekte tutar ve çoğu PFADD hiçbir register'ı değiştirmediği için bu değer nadiren yeniden hesaplanır. Önbellek HLL'in kendi içinde, son 8 baytta saklanır; bu yüzden belgeler PFCOUNT'un teknik olarak bir yazma komutu olduğunu belirtir. Çok anahtarlı çağrıda ise birleştirme anında yapılır, birleşimin kardinalitesi önbelleğe alınamaz ve komut milisaniye mertebesinde sürebilir. Belgelerin önerisi açıktır: çok anahtarlı PFCOUNT kötüye kullanılmamalıdır.

PFMERGE ile birleştirme

PFMERGE ile birleştirme

PFMERGE birden çok HLL'i, kaynak kümelerin birleşimini temsil eden tek bir HLL'de toplar ve sonucu hedef anahtara yazar. Hedef yoksa boş bir HLL olarak oluşturulur. Belgelerin dikkat çektiği ayrıntı şudur: hedef anahtar zaten varsa o da kaynaklardan biri sayılır ve kendi kardinalitesi sonuca dahil edilir. Yani PFMERGE hedefi sıfırdan yazmaz, üzerine birleştirir.

text
> PFADD hll1 foo bar zap a
(integer) 1
> PFADD hll2 a b c foo
(integer) 1
> PFMERGE hll3 hll1 hll2
OK
> PFCOUNT hll3
(integer) 6

foo ve a iki kümede de geçtiği için birleşimde altı tekil eleman kalır. Belgelere göre birleştirme, birleştirilen sketch sayısıyla orantılı yani O(n) çalışır. HLL birleşim hesaplamayı destekler; kesişim için ayrı bir komut yoktur. PFCOUNT ve PFMERGE birden çok anahtar aldığı için cluster ortamında farklı davranabilir; ayrıntı için hash slot ve CROSSSLOT yazısına bakın.

İç yapı: string içinde iki gösterim

HLL teknik olarak ayrı bir veri yapısı olsa da Redis'te bir string olarak kodlanır. Bu yüzden GET ile HLL'i serileştirip SET ile geri yükleyebilirsiniz; belgeler bunu HLL'i serileştirmenin ve sunucuya geri yüklemenin yolu olarak gösterir.

Belgeler iki gösterimden söz eder. Seyrek (sparse) gösterim, az eleman görmüş ve register'larının çoğu sıfır olan HLL'ler içindir ve sıfır register'ları verimli saklamak için bir tür tekrar uzunluğu kodlaması kullanır. Yoğun (dense) gösterim ise 16384 adet 6 bitlik sayacı tutan 12288 baytlık bir string'dir. Redis gerektiğinde seyrekten yoğuna kendisi geçer. İki gösterimde de başta 16 baytlık bir başlık bulunur; başlıkta sihirli değer, kodlama ve sürüm alanı ile önbelleğe alınmış kardinalite tahmini yer alır. Gösterim işlemci kelime boyutundan ve bayt sırasından bağımsızdır; 32 ve 64 bit, büyük ya da küçük endian işlemciler aynı veriyi kullanır.

Bozuk bir HLL ile PFADD, PFCOUNT ya da PFMERGE çağırmak sunucunun kararlılığını etkilemez; belgelere göre rastgele değerler dönebilir, seyrek gösterim bozulduğunda ise sunucu çoğu zaman bozulmayı fark edip hata döndürür. Yine de bir HLL anahtarına APPEND ya da SETRANGE gibi string komutlarıyla dokunmanız için bir sebep yoktur.

Sınırlar ve performans

Belgelere göre HLL, 18.446.744.073.709.551.616 (2^64) üyeye kadar kümelerin kardinalitesini tahmin edebilir. PFADD ile yazma ve PFCOUNT ile okuma sabit sürede ve sabit alanda yapılır. Birleştirme O(n)'dir, burada n birleştirilen HLL sayısıdır. Bellek tarafında her HLL için en fazla 12 KB artı anahtarın kendisi için birkaç bayt hesaplamanız yeterlidir.

Hata payını doğru yorumlamak önemlidir. %0,81 standart hata, bir milyon civarında gerçek tekil sayı için yaklaşık 8.100'lük bir sapmaya karşılık gelir; ama bu bir üst sınır değil, tipik sapmanın ölçüsüdür. Raporlarda "yaklaşık" ibaresiyle göstermek, finansal ya da faturalama gibi kesin sayı gerektiren yerlerde ise HLL kullanmamak gerekir.

Kullanım senaryoları

Belgeler HLL için şu tipik soruları sayar: bir sayfa bugün kaç tekil ziyaret aldı, bir şarkıyı kaç farklı kullanıcı dinledi, bir videoyu kaç farklı kullanıcı izledi, arama formunda günde kaç farklı sorgu yapıldı. Önerilen düzen, her sayfa (ya da video, şarkı) ve her dönem için bir HLL oluşturmak ve her ziyarette ziyaretçinin IP adresini ya da başka bir tanımlayıcısını eklemektir.

Bu düzende bellek bütçesini önceden hesaplamak kolaydır, çünkü her HLL için üst sınır bellidir. Örneğin 1.000 sayfa için bir yıl boyunca günlük HLL tutarsanız 365.000 anahtar olur ve en kötü durumda her biri 12 KB'tan yaklaşık 4,2 GB eder. Gerçekte az ziyaret alan sayfaların HLL'leri seyrek gösterimde kaldığı için toplam bunun çok altında kalır. Yine de eski dönemlerin anahtarlarına süre vermek ya da günlükleri belirli aralıklarla haftalık ve aylık anahtarlarda birleştirip silmek, büyümeyi kontrol altında tutmanın en basit yoludur. Anahtar adına sayfa ve dönemi koymak, hem okumayı hem temizliği kolaylaştırır; adlandırma önerileri için anahtar isimlendirme yazısına bakabilirsiniz.

Uygulamalı örnek: günlük ve haftalık tekil ziyaretçi

Her gün için bir HLL tutalım ve haftalık sayıyı günlüklerden üretelim. Aşağıdaki komutlar belgelerde çıktısıyla gösterilmediği için sonuçlarını açıklıyoruz:

text
PFADD uv:/fiyatlar:2026-11-07 ziyaretci:81f2 ziyaretci:a07c
PFADD uv:/fiyatlar:2026-11-07 ziyaretci:81f2
EXPIRE uv:/fiyatlar:2026-11-07 3888000
PFCOUNT uv:/fiyatlar:2026-11-07
PFMERGE uv:/fiyatlar:2026-W45 uv:/fiyatlar:2026-11-02 uv:/fiyatlar:2026-11-07
PFCOUNT uv:/fiyatlar:2026-W45

İlk PFADD anahtarı oluşturur ve register'lar değiştiği için 1 döndürür. İkinci PFADD zaten eklenmiş bir ziyaretçiyi tekrar eklediği için 0 döndürür. EXPIRE günlük anahtarı 45 gün sonra siler; süre konusu EXPIRE ve TTL yazısında anlatılıyor. PFCOUNT o günün yaklaşık tekil ziyaretçi sayısını, bu örnekte 2'yi verir. PFMERGE haftanın günlerini (örnekte kısalık için yalnızca iki gün yazdık) haftalık bir anahtarda birleştirir ve OK döndürür; aynı ziyaretçi birden çok gün gelse de haftalık sayıda bir kez sayılır. Haftalık anahtarı bir kez ürettikten sonra tek anahtarlı PFCOUNT ile önbellekli okursunuz.

HyperLogLog, set, bitmap ve Bloom filtresi

HyperLogLog, set, bitmap ve Bloom filtresi: Set (SCARD), Bitmap (BITCOUNT), HyperLogLog (PFCOUNT), SUNIONCARD APPROX
YapıSonuçBellekÜyelik sorusu
Set (SCARD)KesinEleman sayısıyla artarVar, SISMEMBER
Bitmap (BITCOUNT)KesinKimlik aralığı kadar bitVar, GETBIT
HyperLogLog (PFCOUNT)Yaklaşık, yüzde 0,81En fazla 12 KBYok
SUNIONCARD APPROXYaklaşık, yüzde 0,81Set'ler zaten bellekteSet üzerinden var
Bloom filtresiYaklaşık üyelikKapasiteye göreVar, yanlış pozitif olabilir

Seçim kuralı basittir. Yalnızca "kaç farklı?" sorusunu soruyorsanız ve küçük bir hata kabul edilebiliyorsa HLL. "Bu eleman daha önce görüldü mü?" sorusunu da soruyorsanız set, kimlikler yoğun tamsayılarsa bitmap, yaklaşık üyelik yeterliyse Bloom filtresi. Elemanlar zaten set'lerde duruyorsa Redis 8.10 ile gelen SUNIONCARD ... APPROX birleşimi oluşturmadan aynı HLL tekniğiyle tahmin yapar. Ayrıntılar için Redis set, bitmap ve bitfield ve Bloom ve Cuckoo filtresi yazılarına bakabilirsiniz.

Sık yapılan hatalar

  • PFADD dönüşünü yeni eleman sanmak: 1 ve 0 register değişimini anlatır, elemanın yeni olup olmadığını değil.
  • Üyelik testi beklemek: HLL elemanları saklamaz; bir elemanın eklenip eklenmediğini soramazsınız.
  • Çok anahtarlı PFCOUNT'u sık çağırmak: Her çağrıda anında birleştirme yapılır ve önbellek kullanılmaz; sık sorulan birleşimleri PFMERGE ile saklayın.
  • PFMERGE hedefini boş sanmak: Hedef anahtar zaten varsa içeriği birleşime katılır.
  • Kesin sayı gereken yerde kullanmak: Faturalama, stok ya da hukuki raporlarda yaklaşık değer yeterli değildir.
  • HLL anahtarını string komutlarıyla değiştirmek: Yapı bozulabilir ve tahmin anlamsızlaşır.

Sık Sorulan Sorular

Bir HyperLogLog ne kadar bellek kullanır?

En fazla 12 KB, yani yoğun gösterimde 12288 bayt ve 16 baytlık başlık. Az eleman görmüş HLL'ler seyrek gösterimde çok daha az yer kaplar.

HyperLogLog'dan elemanları geri alabilir miyim?

Hayır. Yapı yalnızca bir durum saklar, elemanları saklamaz. Elemanlara ihtiyacınız varsa set kullanın.

İki HLL'in kesişimini hesaplayabilir miyim?

Redis HLL komutları birleşimi destekler: çok anahtarlı PFCOUNT ve PFMERGE. Belgelerde kesişim için ayrı bir HLL komutu yoktur.

PFCOUNT neden yazma komutu sayılıyor?

Çünkü son hesaplanan kardinaliteyi HLL'in son 8 baytında önbellek olarak saklar; bu yüzden çağrı sırasında yapı değişebilir. Kardinalite yeniden hesaplanması gerektiğinde bu önbellek alanı güncellenir; başlıktaki en anlamlı bit, HLL son hesaplamadan sonra değiştiyse tahminin geçersiz olduğunu işaretler.

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 set · Bitmap ve bitfield · Bloom filtresi · EXPIRE ve TTL · Redis nedir, nasıl kullanılır