Redis bitmap ayrı bir veri tipi değildir; string tipinin üzerine tanımlanmış, string'i bir bit dizisi gibi ele alan komutlardır. SETBIT ile tek bir biti 1 ya da 0 yapar, GETBIT ile okur, BITCOUNT ile kaç bitin 1 olduğunu sayar, BITPOS ile ilk 1 ya da 0 bitin yerini bulur, BITOP ile birden çok bitmap arasında AND, OR, XOR gibi işlemler yaparsınız. BITFIELD ise aynı string içinde istediğiniz bit genişliğinde tamsayı sayaçları tutmanızı sağlar.

Bitmap'in en büyük avantajı alan tasarrufudur: kimliği tamsayı olan her kullanıcı için tek bir bitlik bilgi tutabilirsiniz. Bu yazıda resmî Redis belgelerindeki örneklerle bit komutlarını, bit sırasını, Redis 8.2 ile BITOP'a eklenen DIFF, DIFF1, ANDOR ve ONE işlemlerini ve BITFIELD'ın taşma (overflow) davranışını anlatıyoruz. String komutlarının temeli için Redis string komutları yazısına, serinin tamamı için Redis öğrenme rehberi sayfasına bakabilirsiniz.

Bitmap ne zaman kullanılır?

Bitmap ne zaman kullanılır?

String'ler ikili veri güvenlidir (binary safe) ve en fazla 512 MB olabilir; bu da tek bir anahtarda 2^32 farklı biti adreslemeye yeter. Belgeler iki tipik kullanım verir. Birincisi, üyeleri 0 ile N arasındaki tamsayılara karşılık gelen kümelerdir: kullanıcı kimliği 1234 olan kişi bugün giriş yaptıysa 1234 numaralı bit 1 olur. İkincisi, dosya sistemlerinin izinleri saklama biçimine benzer şekilde her bitin ayrı bir izni temsil ettiği nesne izinleridir.

Belgelerdeki ölçek örneği bitmap'in neden cazip olduğunu gösterir: artan tamsayı kimlikli dört milyar kullanıcının her biri için tek bitlik bir bilgiyi (örneğin bülten almak isteyip istemediğini) yalnızca 512 MB bellekle tutabilirsiniz. Aynı bilgiyi set içinde kullanıcı kimliklerini string olarak saklayarak tutmak çok daha fazla yer kaplar. Belgelerin veri tipi karşılaştırma sayfası da anahtarlar her zaman bilinen bir aralıktaki basit tamsayı indeksleriyse en az bellek yükü için bitmap'i önerir.

SETBIT ve GETBIT

SETBIT verilen konumdaki (offset) biti 1 ya da 0 yapar ve o bitin önceki değerini döndürür. Anahtar yoksa yeni bir string oluşturulur; konum mevcut uzunluğun ötesindeyse string büyütülür ve eklenen bitler 0 olur. GETBIT bitin değerini döndürür; string'in dışındaki ya da hiç olmayan anahtardaki her bit 0 kabul edilir.

text
> SETBIT mykey 7 1
(integer) 0
> GETBIT mykey 0
(integer) 0
> GETBIT mykey 7
(integer) 1
> GETBIT mykey 100
(integer) 0

Belgelerdeki senaryoda 0 ile 999 arası numaralı sensörlere sahip bin bisikletçi vardır ve her saat için ayrı bir bitmap anahtarı tutulur. 123 numaralı bisikletçi 1 Ocak 2024 saat 00:00 diliminde sunucuya sinyal gönderdiğinde SETBIT pings:2024-01-01-00:00 123 1 çalışır ve 0 döner, çünkü bit daha önce 0'dı. Ardından BITCOUNT pings:2024-01-01-00:00 o saatte sinyal gönderen bisikletçi sayısını, yani 1'i verir. Anahtar adına zaman dilimini koymak, her dönemi ayrı bir bitmap olarak tutmanın en basit yoludur.

SETBIT ve GETBIT O(1) çalışır. Konum 0 veya daha büyük ve 2^32'den küçük olmalıdır.

Bit sırası ve GET ile ham değer

Bitmap bir string olduğu için GET ve SET ile bütün halinde okunup yazılabilir. String'in ilk baytı 0-7 numaralı bitlere, ikinci baytı 8-15 numaralı bitlere karşılık gelir ve her baytta 0 numaralı bit en anlamlı bittir. Belgelerdeki şu örnekte birkaç bit ayarlandıktan sonra GET okunabilir bir string döndürür:

text
> SETBIT bitmapsarestrings 2 1
> SETBIT bitmapsarestrings 3 1
> SETBIT bitmapsarestrings 5 1
> SETBIT bitmapsarestrings 10 1
> SETBIT bitmapsarestrings 11 1
> SETBIT bitmapsarestrings 14 1
> GET bitmapsarestrings
"42"

Sonucun nedeni şudur: ilk baytta 2, 3 ve 5 numaralı bitler 1 olduğu için bayt 00110100, yani onaltılık 0x34 olur ve bu ASCII'de "4" karakteridir. İkinci baytta 10, 11 ve 14 numaralı bitler, bayt içinde 2, 3 ve 6. konumlara düşer; bayt 00110010, yani 0x32 ve "2" karakteridir. Bu düzen sayesinde istemci bütün bitmap'i tek GET ile alıp kendi dilindeki bit işlemleriyle çözebilir. Tersine, bitleri istemcide bayta çevirip tek SET ile bütün bitmap'i yazmak da mümkündür; belgeler bunu bitmap'i sıfır dışı bir varsayılanla başlatmak için önerir.

BITCOUNT: 1 olan bitleri saymak

BITCOUNT string'deki 1 bitlerini sayar (population count). İsteğe bağlı başlangıç ve bitiş aralığı varsayılan olarak bayt indeksidir; BIT eklerseniz bit indeksi olarak yorumlanır. Negatif değerler sondan sayar. Olmayan anahtar boş string kabul edilir ve 0 döner.

text
> SET mykey "foobar"
OK
> BITCOUNT mykey
(integer) 26
> BITCOUNT mykey 0 0
(integer) 4
> BITCOUNT mykey 1 1
(integer) 6
> BITCOUNT mykey 1 1 BYTE
(integer) 6
> BITCOUNT mykey 5 30 BIT
(integer) 17

Belgelerin anlattığı gerçek zamanlı metrik deseni şöyledir: uygulamanın yayına girdiği gün 0, ertesi gün 1 olacak şekilde günleri numaralandırırsınız ve kullanıcı bir sayfa görüntülediğinde o günün bitini 1 yaparsınız. Sonra BITCOUNT kullanıcının kaç farklı gün siteyi ziyaret ettiğini verir. Belgelere göre on yıl sonra bile kullanıcı başına yalnızca 365*10 bit, yani yaklaşık 456 bayt veri olur ve bu boyutta BITCOUNT en hızlı komutlar kadar hızlıdır. Bitmap çok büyükse belgeler iki yol önerir: bitmap her değiştiğinde ayrı bir sayaç anahtarını küçük bir Lua betiğiyle atomik olarak artırmak ya da BITCOUNT'u aralıklarla parça parça çalıştırıp sonuçları istemcide toplamak.

BITPOS: ilk 1 veya 0 bitin yeri

BITPOS aradığınız değerdeki (1 ya da 0) ilk bitin konumunu döndürür. Konum her zaman string'in başından itibaren mutlak bit numarasıdır, aralık verseniz bile. Aralık yine varsayılan olarak bayt cinsindendir, BIT ile bit cinsine çevrilir.

text
> SET mykey "\xff\xf0\x00"
OK
> BITPOS mykey 0
(integer) 12
> SET mykey "\x00\xff\xf0"
OK
> BITPOS mykey 1 0
(integer) 8
> BITPOS mykey 1 2
(integer) 16
> BITPOS mykey 1 7 15 BIT
(integer) 8
> set mykey "\x00\x00\x00"
OK
> BITPOS mykey 1
(integer) -1

Uç durumlar belgelerde ayrıntılı tanımlanmıştır. 1 bit arıyorsanız ve string boşsa ya da yalnızca sıfır baytlardan oluşuyorsa -1 döner. 0 bit arıyorsanız ve string tamamen 1'lerden oluşuyorsa, aralık vermediyseniz ya da yalnızca başlangıç verdiyseniz string'in sağında sıfırlar varmış gibi davranılır ve string'in hemen sonrasındaki bit döner; örneğin üç baytı 0xff olan bir string için BITPOS key 0 24 döndürür. Hem başlangıç hem bitiş verdiyseniz ve aralıkta 0 yoksa -1 döner. Anahtar hiç yoksa sonsuz sıfır dizisi sayılır: 0 aramak 0, 1 aramak -1 döndürür; ama boş string ("") tutan bir anahtarda ikisi de -1 döndürür. Belgeler, günlük ziyaret bitmap'inde kullanıcının en uzun ardışık ziyaret serisini birkaç BITPOS çağrısıyla ya da bitmap'i istemciye alıp çözerek hesaplayabileceğinizi belirtir.

BITOP: bitmap'ler arasında işlem

BITOP: bitmap'ler arasında işlem: Bütün kaynaklarda 1 ise, En az bir kaynakta 1 ise, İki kaynakta farklıysa (fazlası sırayla XOR edilir), Tek kaynakta 0 ise

BITOP iki veya daha fazla kaynak anahtar üzerinde bit düzeyinde işlem yapar ve sonucu hedef anahtara yazar. Klasik işlemler AND, OR, XOR ve tek kaynaklı NOT'tur. Redis 8.2 dört yeni işlem ekledi; bunlar en az Redis 8.2 gerektirir:

İşlemHedefte bit 1 olur, eğer
ANDBütün kaynaklarda 1 ise
OREn az bir kaynakta 1 ise
XORİki kaynakta farklıysa (fazlası sırayla XOR edilir)
NOTTek kaynakta 0 ise
DIFF (8.2)İlk kaynakta 1, diğerlerinin hiçbirinde 1 değilse
DIFF1 (8.2)İlk kaynakta 0, diğerlerinden en az birinde 1 ise
ANDOR (8.2)İlk kaynakta 1 ve diğerlerinden en az birinde 1 ise
ONE (8.2)Kaynakların tam olarak birinde 1 ise

Belgeler bu işlemleri üç bitmap üzerinde gösterir: A = 11011000, B = 00011001, C = 01101100. GET çıktıları ham baytlardır; okunabilir karakter düşerse karakter, düşmezse onaltılık kaçış dizisi görünür.

text
> SETBIT A 0 1
(integer) 0
> SETBIT A 1 1
(integer) 0
> SETBIT A 3 1
(integer) 0
> SETBIT A 4 1
(integer) 0
> GET A
"\xd8"
> SETBIT B 3 1
(integer) 0
> SETBIT B 4 1
(integer) 0
> SETBIT B 7 1
(integer) 0
> GET B
"\x19"
> SETBIT C 1 1
(integer) 0
> SETBIT C 2 1
(integer) 0
> SETBIT C 4 1
(integer) 0
> SETBIT C 5 1
(integer) 0
> GET C
"l"
> BITOP AND R A B C
(integer) 1
> GET R
"\b"
> BITOP OR R A B C
(integer) 1
> GET R
"\xfd"
> BITOP DIFF R A B C
(integer) 1
> GET R
"\x80"
> BITOP ANDOR R A B C
(integer) 1
> GET R
"X"

C için GET çıktısı "l" karakteridir, çünkü 01101100 onaltılık 0x6c'dir. AND sonucu 00001000 (0x08, geri silme karakteri), OR sonucu 11111101 (0xfd), DIFF sonucu 10000000 (0x80), ANDOR sonucu 01011000 (0x58, "X") olur. Komutun dönüş değeri hedef string'in bayt cinsinden uzunluğudur ve en uzun kaynak string'e eşittir.

Farklı uzunluktaki string'lerde kısa olanlar en uzun string'in uzunluğuna kadar sıfırla doldurulmuş sayılır; olmayan anahtarlar da sıfır baytlar olarak ele alınır. Belgelerdeki reklam örneği ANDOR'un gücünü gösterir: kitap okumayı seven ve fantastik, macera ya da bilim kurgudan en az biriyle ilgilenen kitleyi BITOP ANDOR TA LRB B:F B:A B:SF tek komutla hesaplar.

Uygulamalı örnek: günlük aktif kullanıcılar

Her gün için bir bitmap tutup kullanıcı kimliğini bit konumu olarak kullanalım. Aşağıdaki komutlar belgelerde çıktısıyla gösterilmediği için sonuçlarını açıklıyoruz:

text
SETBIT active:2026-11-06 42 1
SETBIT active:2026-11-07 42 1
SETBIT active:2026-11-07 1001 1
BITCOUNT active:2026-11-07
BITOP AND active:both active:2026-11-06 active:2026-11-07
BITCOUNT active:both
GETBIT active:2026-11-06 1001

Her SETBIT bitin önceki değerini, yani bu örnekte 0 döndürür. İlk BITCOUNT 7 Kasım'da aktif olan iki kullanıcıyı sayar. BITOP AND iki gün de aktif olanları yeni bir anahtara yazar ve hedefin bayt uzunluğunu döndürür; ikinci BITCOUNT bu anahtarda yalnızca 42 numaralı kullanıcıyı bulur ve 1 verir. Son komut 0 döner, çünkü 1001 numaralı kullanıcı 6 Kasım'da aktif değildi. Haftalık aktif kullanıcıyı bulmak için yedi günü BITOP OR ile birleştirirsiniz.

Büyük bitmap'i anahtarlara bölmek

Belgeler, veri kümesini parçalamak ve genel olarak çok büyük anahtarlardan kaçınmak için bitmap'i birden fazla anahtara bölmeyi önerir. Basit strateji her anahtarda M bit tutmaktır: anahtar adını bit-numarası / M ile, anahtar içindeki konumu bit-numarası MOD M ile bulursunuz. Böylece hem tek bir dev anahtarın yaratacağı yükten hem de yukarıda anlatılan büyük bellek ayırma gecikmesinden kaçınırsınız.

BITFIELD: bit genişliği seçilebilen sayaçlar

BITFIELD, string'i bir bit dizisi olarak görür ve içinde istediğiniz genişlikte, istediğiniz konumda tamsayılar okuyup yazmanızı sağlar. Tür adı işaretli için i, işaretsiz için u öneki ve bit sayısıyla yazılır, örneğin u8 ya da i16. İşaretli tamsayılar 64 bite, işaretsizler 63 bite kadar desteklenir; bu sınırın sebebi Redis protokolünün 64 bit işaretsiz tamsayıyı yanıt olarak döndürememesidir.

Konum iki şekilde yazılır. Öneksiz sayı sıfırdan başlayan bit konumudur. Başına # konursa konum, tür genişliğiyle çarpılır: u32 #1 ikinci 32 bitlik alan demektir ve hesabı sizin yapmanız gerekmez. Belgelerdeki örnek bir bisikletin fiyatını ve sahip sayısını 32 bitlik iki sayaçta tutar:

text
> BITFIELD bike:1:stats SET u32 #0 1000
1) (integer) 0
> BITFIELD bike:1:stats INCRBY u32 #0 -50 INCRBY u32 #1 1
1) (integer) 950
2) (integer) 1
> BITFIELD bike:1:stats INCRBY u32 #0 500 INCRBY u32 #1 1
1) (integer) 1450
2) (integer) 2
> BITFIELD bike:1:stats GET u32 #0 GET u32 #1
1) (integer) 1450
2) (integer) 2

Tek çağrıda birden çok alt komut gönderebilirsiniz ve yanıt dizisinde her alt komutun sonucu aynı sırada yer alır. SET eski değeri, INCRBY yeni değeri döndürür. Okuma (GET) string dışındaki bitleri 0 sayar; yazma ise string'i gereken en küçük uzunluğa kadar sıfırla büyütür. Belgeler ayrıca birden çok SETBIT çağrısı yerine u1 türüyle tek bir BITFIELD çağrısı yapmayı önerir.

Taşma kontrolü: OVERFLOW WRAP, SAT, FAIL

OVERFLOW alt komutu, kendisinden sonra gelen SET ve INCRBY işlemlerinin taşmada nasıl davranacağını belirler. WRAP (varsayılan) başa sarar; örneğin 127 değerindeki bir i8 1 artırılınca -128 olur. SAT doyurma aritmetiği uygular, değer en büyük ya da en küçük sınırda kalır. FAIL taşma algılanınca işlemi yapmaz ve o alt komut için nil döndürür. Belgelerdeki örnekte 2 bitlik işaretsiz iki sayaçtan biri WRAP, diğeri SAT ile artırılır; dördüncü çağrıda ilki 0'a sarar, ikincisi 3'te kalır:

text
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 3
2) (integer) 3
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 0
2) (integer) 3
> BITFIELD mykey OVERFLOW FAIL incrby u2 102 1
1) (nil)

Bu oturum belgelerdeki dört çağrılık örneğin son iki çağrısını ve ardından gelen FAIL örneğini gösterir; ilk iki çağrı sayaçları 1 ve 2 yapmıştır. Salt okunur replikalarda okuma yapmak için yalnızca GET kabul eden BITFIELD_RO vardır; BITFIELD yazma bayrağı taşıdığı için cluster'daki salt okunur replikalar onu ana sunucuya yönlendirir.

Bitmap, set ve HyperLogLog karşılaştırması

Bitmap, set ve HyperLogLog karşılaştırması: Üye tipi, Üyelik testi, Tekil sayım, Birleştirme
SoruBitmapSetHyperLogLog
Üye tipiBilinen aralıkta tamsayıHerhangi bir stringHerhangi bir string
Üyelik testiKesin, GETBITKesin, SISMEMBERYok
Tekil sayımKesin, BITCOUNTKesin, SCARDYaklaşık, PFCOUNT
BirleştirmeBITOPSUNION, SINTERPFMERGE (birleşim)
BellekAralığın bit sayısı kadarÜye sayısıyla artarEn fazla 12 KB

Kimlikleriniz seyrek ve çok büyükse bitmap boşa bellek ayırır; bu durumda set ya da yalnızca sayı gerekiyorsa HyperLogLog daha uygundur.

Sık yapılan hatalar

  • Bayt ile bit aralığını karıştırmak: BITCOUNT ve BITPOS aralıkları varsayılan olarak bayttır; bit aralığı için BIT ekleyin.
  • Bit sırasını tersten düşünmek: 0 numaralı bit ilk baytın en anlamlı bitidir.
  • Çok büyük konum: Kısa bir string'te çok uzak bir biti ayarlamak büyük bir bellek ayırmasına ve gecikmeye yol açabilir.
  • Seyrek kimlikler: 4 milyar civarındaki tek bir kimlik bile bitmap'i yaklaşık 512 MB'a büyütür; kimlikler yoğun değilse başka bir tip seçin.
  • Eski sunucuda yeni BITOP işlemleri: DIFF, DIFF1, ANDOR, ONE için en az Redis 8.2 gerekir.
  • OVERFLOW'un kapsamı: Her OVERFLOW yalnızca ardından gelen SET/INCRBY işlemlerini, bir sonraki OVERFLOW'a kadar etkiler.

Sık Sorulan Sorular

Bitmap ayrı bir veri tipi mi?

Hayır. Bitmap komutları string tipi üzerinde çalışır; TYPE böyle bir anahtar için string döndürür ve GET/SET ile bitmap'i bütün halinde okuyup yazabilirsiniz.

Bir bitmap en fazla kaç bit tutar?

String'ler en fazla 512 MB olduğu için 2^32 bit. SETBIT konumu 0 ile 2^32 - 1 arasında olmalıdır.

BITFIELD ile HINCRBY arasında ne zaman seçim yapmalıyım?

Çok sayıda küçük tamsayıyı tek bir anahtarda en az bellekle tutmak ve taşma davranışını kontrol etmek istiyorsanız BITFIELD; okunabilir alan adlarıyla az sayıda sayaç yeterliyse hash ve HINCRBY daha basittir. Hash sayaçları için Redis hash komutları yazısına bakın.

Ham bitmap'i istemciye nasıl alırım?

GET ile alırsınız; yanıt ham baytlardır. Belgelere göre istemci bu baytları kendi dilindeki bit işlemleriyle çözebilir. Bitmap'in yalnızca bir bölümü gerekiyorsa GETRANGE ile bayt aralığı okuyabilirsiniz; belgeler GETRANGE ve SETRANGE ile bit aralıklarına erişen bir Lua betiği örneği de verir.

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 string komutları · HyperLogLog · Redis set · Lua betikleri · Redis nedir, nasıl kullanılır