Redis list, sıralı string değerlerden oluşan bir bağlı listedir. Başa eklemek için LPUSH, sona eklemek için RPUSH, baştan veya sondan çekmek için LPOP ve RPOP, bir aralığı okumak için LRANGE kullanırsınız. Liste boşken bekleyen BLPOP ve BRPOP komutları sayesinde aynı yapı basit bir iş kuyruğuna da dönüşür. Kısacası list tipi, eleman sırasının önemli olduğu ve işlemlerin çoğunlukla iki uçta yapıldığı her senaryo için Redis içindeki ilk adaydır.

Bu yazıda list komutlarını resmî Redis belgelerindeki örneklerle tek tek ele alıyoruz: ekleme ve çekme yönleri, negatif indeksler, LTRIM ile sınırlı liste, engelleyen komutların hangi istemciye ne zaman cevap verdiği, LMOVE ile güvenilir kuyruk deseni, LMPOP ve Redis 8.10 ile gelen LMOVEM. Redis'in genel tanıtımı ve kurulumu için Redis nedir, nasıl kullanılır yazısına, serinin tamamı için Redis öğrenme rehberi sayfasına bakabilirsiniz.

Redis list neden bağlı liste olarak tasarlandı?

"Liste" kelimesi yazılım dünyasında farklı şeyler için kullanılır. Örneğin Python'daki list aslında bir dizidir (array): indeksle erişim sabit sürede yapılır ama başa eleman eklemek pahalıdır. Redis list ise bağlı liste olarak uygulanmıştır. Bunun pratik sonucu şudur: listede on eleman da olsa on milyon eleman da olsa başa ya da sona yeni eleman eklemek aynı sürede tamamlanır. Belgeler bu tercihi, bir veritabanı için çok uzun listelere hızlı ekleme yapabilmenin kritik olmasıyla açıklar.

Bedeli de vardır: listenin ortasındaki bir elemana indeksle ulaşmak, o indekse kadar yürümeyi gerektirir. Yani LINDEX, LINSERT ve LSET gibi ortadaki elemanlarla çalışan komutlar eleman sayısıyla orantılı iş yapar. Büyük bir koleksiyonun ortasına sık sık hızlı erişmeniz gerekiyorsa belgeler sorted set tipini önerir; bu tip için Redis sorted set rehberine bakabilirsiniz. Bir Redis listesi en fazla 2^32 - 1, yani 4.294.967.295 eleman tutabilir.

LPUSH, RPUSH ve LRANGE ile ilk liste

LPUSH elemanı listenin soluna (başına), RPUSH ise sağına (sonuna) ekler. LRANGE verilen iki indeks arasındaki elemanları döndürür. Aşağıdaki oturum belgelerdeki bisiklet tamiri örneğidir: iki bisiklet sona eklenir, acil olan bir bisiklet başa alınır.

text
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1
(integer) 1
> RPUSH bikes:repairs bike:2
(integer) 2
> LPUSH bikes:repairs bike:important_bike
(integer) 3
> LRANGE bikes:repairs 0 -1
1) "bike:important_bike"
2) "bike:1"
3) "bike:2"

İki ekleme komutu da eklemeden sonraki liste uzunluğunu tamsayı olarak döndürür; bu yüzden ayrıca LLEN çağırmadan listenin kaç eleman tuttuğunu öğrenebilirsiniz. Anahtar yoksa komut önce boş bir liste oluşturur, anahtar list dışında bir tip tutuyorsa hata verir.

İki komut da birden fazla eleman alabilir (variadic). Burada sık gözden kaçan bir ayrıntı vardır: elemanlar soldan sağa doğru teker teker eklenir. RPUSH mylist a b c sonucunda liste a, b, c olur; ama LPUSH mylist a b c sonucunda her yeni eleman başa geçtiği için liste c, b, a sırasını alır. Tek komutla toplu ekleme yapmak, sunucuya yapılan gidiş dönüş sayısını da azaltır.

LRANGE indeksleri ve uç durumlar

LRANGE sıfırdan başlayan indeksler kullanır ve bitiş indeksi dahildir. Negatif indeksler sondan saymayı sağlar: -1 son eleman, -2 sondan bir önceki elemandır. Aralık dışı indeksler hata üretmez. Başlangıç listenin sonundan büyükse boş dizi döner, bitiş listenin sonunu aşıyorsa Redis onu son eleman gibi kabul eder. Belgelerdeki şu oturum bu kuralların hepsini gösterir:

text
> RPUSH mylist "one"
(integer) 1
> RPUSH mylist "two"
(integer) 2
> RPUSH mylist "three"
(integer) 3
> LRANGE mylist 0 0
1) "one"
> LRANGE mylist -3 2
1) "one"
2) "two"
3) "three"
> LRANGE mylist -100 100
1) "one"
2) "two"
3) "three"
> LRANGE mylist 5 10
(empty array)

LPOP ve RPOP ile eleman çekmek

Pop işlemi elemanı hem okur hem de listeden siler. LPOP baştan, RPOP sondan çeker. İsteğe bağlı count argümanı verilirse komut tek eleman yerine en fazla o kadar elemanı dizi olarak döndürür; listede daha az eleman varsa olanların hepsi gelir.

text
> RPUSH mylist "one" "two" "three" "four" "five"
(integer) 5
> LPOP mylist
"one"
> LPOP mylist 2
1) "two"
2) "three"
> LRANGE mylist 0 -1
1) "four"
2) "five"

Dönüş tipine dikkat edin: count olmadan çağrıldığında yanıt tek bir bulk string, count ile çağrıldığında ise dizidir. Anahtar yoksa iki durumda da nil (RESP3'te null) döner. İstemci kodunda bu iki biçimi aynı değişkene okuyorsanız tip kontrolü yapmanız gerekir. Liste tamamen boşaldığında son pop işlemi anahtarı da siler; bir sonraki RPOP nil döndürür.

Kuyruk mu yığın mı? Yönü doğru seçmek

Kuyruk mu yığın mı? Yönü doğru seçmek: LPUSH, RPUSH, LPUSH, LPUSH + LTRIM

Aynı list tipi, hangi uçtan ekleyip hangi uçtan çektiğinize göre farklı veri yapılarına dönüşür. Bir uçtan ekleyip öbür uçtan çekerseniz ilk giren ilk çıkar (FIFO kuyruk); aynı uçtan ekleyip çekerseniz son giren ilk çıkar (LIFO yığın). Belgelerdeki kuyruk örneği:

text
> LPUSH bikes:repairs bike:1
(integer) 1
> LPUSH bikes:repairs bike:2
(integer) 2
> RPOP bikes:repairs
"bike:1"
> RPOP bikes:repairs
"bike:2"

Yığın örneğinde ise ekleme ve çekme aynı taraftan, soldan yapılır ve önce en son eklenen bisiklet çıkar:

text
> LPUSH bikes:repairs bike:1
(integer) 1
> LPUSH bikes:repairs bike:2
(integer) 2
> LPOP bikes:repairs
"bike:2"
> LPOP bikes:repairs
"bike:1"
EklemeÇekmeDavranışTipik kullanım
LPUSHRPOPFIFO kuyrukArka plan işleri, üretici ve tüketici
RPUSHLPOPFIFO kuyrukAynı kuyruk, ters yön
LPUSHLPOPLIFO yığınGeri alma geçmişi, son işlem önce
LPUSH + LTRIMLRANGE 0 NSon N kayıtSon güncellemeler, son loglar

Hangi yönü seçtiğiniz tek başına önemli değildir; önemli olan uygulamanın her yerinde aynı kuralı izlemesidir. Üretici LPUSH, tüketici de LPOP kullanırsa farkında olmadan kuyruk yerine yığın kurmuş olursunuz ve eski işler hiç işlenmeden bekleyebilir.

LTRIM ile sınırlı liste: son N kaydı tutmak

Çoğu zaman listede yalnızca en yeni N kaydı tutmak istersiniz: son sosyal medya güncellemeleri, son loglar, ana sayfada gösterilecek son fotoğraflar. LTRIM, LRANGE gibi bir aralık alır ama aralığı göstermek yerine listeyi o aralığa indirger; dışarıda kalan her şey silinir.

text
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
> LTRIM bikes:repairs -3 -1
OK
> LRANGE bikes:repairs 0 -1
1) "bike:3"
2) "bike:4"
3) "bike:5"

Negatif indekslerle -3 -1 yazmak "sondaki üç elemanı tut" demektir; elemanları RPUSH ile sona eklediğiniz için bunlar en yeni üç kayıttır. Belgelerde önerilen klasik desen ise başa ekleyip hemen ardından kırpmaktır:

text
LPUSH mylist someelement
LTRIM mylist 0 99

Bu ikili listeye yeni elemanı ekler ve listenin 100 elemanı geçmemesini sağlar. Belgeler bu kullanımda LTRIM'in ortalama durumda O(1) olduğunu belirtir, çünkü her seferinde genellikle sondan yalnızca bir eleman silinir. Sonra LRANGE mylist 0 9 ile en yeni on kaydı okursunuz. LRANGE teknik olarak O(N) olsa da listenin başına ya da sonuna yakın küçük aralıklara erişim sabit sürede yapılır.

LTRIM'in uç durumları da LRANGE'e benzer: başlangıç indeksi listenin sonundan büyükse sonuç boş liste olur ve bu durumda anahtar silinir. Bitiş indeksi listenin sonunu aşıyorsa son eleman kabul edilir. Komut her zaman OK döndürür, kaç eleman silindiğini söylemez.

Engelleyen komutlar: BLPOP ve BRPOP

Engelleyen komutlar: BLPOP ve BRPOP: Birden fazla anahtar verilirse ve en az birinde eleman varsa, soldan sağa ilk..., Aynı anahtarı bekleyen birden fazla istemci varsa en uzun...

Üretici ve tüketici düzeninde üretici LPUSH ile iş ekler, tüketici RPOP ile çeker. Liste boşsa RPOP nil döner ve tüketici bir süre bekleyip tekrar dener. Belgeler bu yoklama (polling) yaklaşımının iki sorununu sayar: liste boşken gönderilen her istek sunucuyu boşuna meşgul eder, bekleme süresi de işlerin gecikmeli işlenmesine yol açar. Bekleme süresini kısaltmak gecikmeyi azaltır ama boşa giden istek sayısını artırır.

BLPOP ve BRPOP bu sorunu çözer: liste boşsa bağlantıyı bekletir, listeye eleman geldiğinde ya da verdiğiniz zaman aşımı dolduğunda cevap verir. Zaman aşımı saniye cinsinden ondalıklı (double) bir sayıdır; 0 süresiz beklemek anlamına gelir.

text
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1 bike:2
(integer) 2
> BRPOP bikes:repairs 1
1) "bikes:repairs"
2) "bike:2"
> BRPOP bikes:repairs 1
1) "bikes:repairs"
2) "bike:1"
> BRPOP bikes:repairs 1
(nil)
(2.01s)

Yanıt biçimi RPOP'tan farklıdır: iki elemanlı bir dizi gelir, ilki elemanın çekildiği anahtarın adı, ikincisi elemanın kendisidir. Anahtar adı gerekir, çünkü bu komutlar aynı anda birden fazla listeyi bekleyebilir. Belgelerin BLPOP sayfası öncelik kurallarını ayrıntılı anlatır:

  • Birden fazla anahtar verilirse ve en az birinde eleman varsa, soldan sağa ilk dolu listeden çekilir ve istemci hiç beklemez. BLPOP key1 key2 key3 key4 0 komutunda key2 ve key4 doluysa eleman her zaman key2'den gelir.
  • Aynı anahtarı bekleyen birden fazla istemci varsa en uzun süredir bekleyen önce cevap alır. Cevap alan istemci önceliğini korumaz; yeniden beklemeye başladığında sıranın sonuna geçer.
  • Aynı komut, transaction ya da betik içinde birden fazla liste dolarsa istemciye yazma sırasına göre değil, kendi BLPOP çağrısındaki anahtar sırasına göre cevap verilir.
  • Tek komutla birden fazla eleman eklendiğinde (örneğin LPUSH foo a b c) bekleyen istemciye komut tamamlandıktan sonra cevap verilir. Liste o anda c, b, a olduğu için BLPOP foo 0 ile bekleyen istemci c alır.

LMOVE ve BLMOVE: atomik taşıma ve güvenilir kuyruk

BLPOP elemanı istemciye verdiği anda listeden siler. Tüketici elemanı aldıktan hemen sonra çökerse ya da ağ koparsa o iş kaybolur. LMOVE bu sorun için tasarlanmıştır: kaynak listenin bir ucundan elemanı çeker ve aynı atomik işlemde hedef listenin bir ucuna ekler. Hangi uçların kullanılacağını LEFT ve RIGHT argümanlarıyla siz belirlersiniz.

text
> RPUSH mylist "one"
(integer) 1
> RPUSH mylist "two"
(integer) 2
> RPUSH mylist "three"
(integer) 3
> LMOVE mylist myotherlist RIGHT LEFT
"three"
> LMOVE mylist myotherlist LEFT RIGHT
"one"
> LRANGE mylist 0 -1
1) "two"
> LRANGE myotherlist 0 -1
1) "three"
2) "one"

Belgelerin tarif ettiği güvenilir kuyruk deseni şöyledir: tüketici işi ana kuyruktan alırken aynı anda bir "işleniyor" listesine taşır. İş bittiğinde LREM ile işleniyor listesinden siler. Ayrı bir istemci işleniyor listesinde uzun süre kalan kayıtları izler ve gerekirse ana kuyruğa geri koyar. Böylece tüketici çökse bile iş kaybolmaz, yalnızca gecikir. Liste boşken beklemek için BLMOVE kullanılır; zaman aşımı yine saniye cinsinden ondalıklı sayıdır ve MULTI/EXEC ya da Lua betiği içinde LMOVE gibi davranır. Bu desenin uygulama düzeyindeki tasarımı için Redis iş kuyruğu yazısına bakabilirsiniz.

Kaynak ve hedef aynı anahtar olursa LMOVE bir döndürme (rotation) komutuna dönüşür: sondaki eleman başa geçer. Belgeler buna dairesel liste deseni der. Örneğin erişilebilirliği kontrol edilecek web sitelerinin listesini birden fazla işçi sürekli döndürebilir; işçiler farklı elemanlar alır, liste tamamen dolaşıldığında süreç baştan başlar ve araya yeni siteler eklenmesi de sorun çıkarmaz. Bir eleman kaybolsa bile liste içinde durduğu için bir sonraki turda yine işlenir.

LMPOP: birden çok listeden toplu çekme

LPOP/RPOP tek anahtar alır ama çok eleman döndürebilir; BLPOP/BRPOP çok anahtar alır ama tek eleman döndürür. LMPOP ikisini birleştirir: verdiğiniz anahtarlar arasında ilk dolu listeden, seçtiğiniz uçtan, COUNT kadar eleman çeker. İlk argüman kaç anahtar verdiğinizi belirten numkeys değeridir.

text
> LMPOP 2 non1 non2 LEFT COUNT 10
(nil)
> LPUSH mylist "one" "two" "three" "four" "five"
(integer) 5
> LMPOP 1 mylist LEFT
1) "mylist"
2) 1) "five"
> LMPOP 1 mylist RIGHT COUNT 10
1) "mylist"
2) 1) "one"
   2) "two"
   3) "three"
   4) "four"

Yanıt iki elemanlı bir dizidir: önce anahtarın adı, sonra çekilen elemanların dizisi. Hiçbir listeden eleman çekilemezse nil döner. Engelleyen sürümü BLMPOP'tur. Birden fazla anahtar alan bu komutlar cluster ortamında farklı davranabilir; belgeler bu konuda çok anahtarlı işlemler sayfasına yönlendirir, ayrıntı için hash slot ve CROSSSLOT yazısına bakın.

LMOVEM ve BLMOVEM ile çok elemanlı taşıma

Redis 8.10 ile gelen LMOVEM, LMOVE'un çok elemanlı sürümüdür; bu yüzden en az Redis 8.10 çalıştıran bir sunucu gerektirir. İsteğe bağlı "how-many" bloğu verirseniz hem bir seçici (COUNT ya da EXACTLY) hem de bir sıralama (OBO ya da BULK) yazmanız gerekir. COUNT n en fazla n eleman taşır; EXACTLY n ise kaynakta en az n eleman yoksa hiçbir şey taşımaz ve nil döner. Bu, ikişerli ya da üçerli gruplar halinde taşınması gereken kayıtlar için kullanışlıdır.

text
> rpush mylist 1 2 3 4
(integer) 4
> LMOVEM mylist myotherlist LEFT LEFT COUNT 2 OBO
1) "2"
2) "1"
> LRANGE mylist 0 -1
1) "3"
2) "4"
> LRANGE myotherlist 0 -1
1) "2"
2) "1"
> LMOVEM mylist myotherlist LEFT LEFT EXACTLY 5 BULK
(nil)

OBO (one by one) her elemanı tek tek çekip ekler; bu yüzden önce çekilen eleman hedefte sonra çekilenin "altında" kalır ve yukarıdaki örnekte sıra 2, 1 olur. BULK ise grubu bir bütün olarak taşır ve göreli sırayı korur. Kaynak ile hedef aynıysa BULK listeyi değiştirmez, OBO ise taşınan elemanları yerinde ters çevirir. Engelleyen sürüm BLMOVEM, COUNT ile yalnızca kaynak boşken, EXACTLY ile kaynakta istenen sayıda eleman birikene kadar bekler.

Ortadaki elemanlar: LINDEX, LPOS, LREM ve performans

Baş ve son uçtaki işlemler O(1)'dir, ortadaki elemanlarla çalışan komutlar ise genellikle O(n). Belgeler LINDEX, LINSERT ve LSET için büyük listelerde dikkatli olmanızı ister. LREM, verdiğiniz değere eşit elemanları siler: pozitif count baştan sona, negatif count sondan başa doğru o kadar eleman siler, 0 ise eşleşen bütün elemanları siler.

text
> RPUSH mylist "hello"
(integer) 1
> RPUSH mylist "hello"
(integer) 2
> RPUSH mylist "foo"
(integer) 3
> RPUSH mylist "hello"
(integer) 4
> LREM mylist -2 "hello"
(integer) 2
> LRANGE mylist 0 -1
1) "hello"
2) "foo"

LPOS bir elemanın indeksini bulur. RANK kaçıncı eşleşmenin isteneceğini (negatif değer sondan arar), COUNT kaç eşleşme döneceğini (0 hepsi), MAXLEN en fazla kaç elemanla karşılaştırma yapılacağını belirler. MAXLEN, eşleşmenin erken bulunmasını beklediğiniz ama yanılırsanız komutun uzun sürmesini istemediğiniz durumlarda karmaşıklığı sınırlar.

text
> RPUSH mylist a b c d 1 2 3 4 3 3 3
(integer) 11
> LPOS mylist 3
(integer) 6
> LPOS mylist 3 COUNT 0 RANK 2
1) (integer) 8
2) (integer) 9
3) (integer) 10

Anahtarların otomatik oluşması ve silinmesi

Örneklerde listeyi önceden oluşturmadığımızı fark etmişsinizdir. Belgeler bunu birden fazla elemandan oluşan bütün tipler (list, set, sorted set, hash, stream) için üç kuralla özetler. Birincisi, var olmayan bir anahtara eleman eklerseniz Redis önce boş bir yapı oluşturur. İkincisi, eleman silindikten sonra yapı boş kalırsa anahtar otomatik olarak silinir; stream bu kuralın tek istisnasıdır. Üçüncüsü, boş bir anahtar üzerinde LLEN gibi salt okunur bir komut ya da eleman silen bir komut, sanki o tipte boş bir yapı varmış gibi sonuç verir.

Bu kolaylığın bir sınırı vardır: anahtar zaten başka bir tipte değer tutuyorsa list komutu çalışmaz.

text
> DEL new_bikes
(integer) 1
> SET new_bikes bike:1
OK
> TYPE new_bikes
string
> LPUSH new_bikes bike:2 bike:3
(error) WRONGTYPE Operation against a key holding the wrong kind of value

Bu hatayı gördüğünüzde önce TYPE ile anahtarın gerçek tipini kontrol edin. Genellikle sebep, iki farklı kod parçasının aynı anahtar adını farklı amaçlarla kullanmasıdır. Anahtar adlandırma düzeni için anahtar isimlendirme ve keyspace yazısına, hata mesajlarının tamamı için Redis hata mesajları yazısına bakabilirsiniz.

Sık yapılan hatalar

Sık yapılan hatalar: Yön karışıklığı, Bitiş indeksini hariç sanmak, Yoklama döngüsü, Kaybolan işler
  • Yön karışıklığı: Üretici ile tüketicinin farklı yön kuralları kullanması kuyruğu yığına çevirir. Ekleme ve çekme yönünü tek bir yerde belgeleyin.
  • Bitiş indeksini hariç sanmak: LRANGE key 0 10 on bir eleman döndürür.
  • Yoklama döngüsü: Boş listeyi sürekli RPOP ile sorgulamak yerine BRPOP ya da BLMOVE kullanın.
  • Kaybolan işler: BLPOP ile alınan iş, tüketici çökerse kaybolur. Kayıp kabul edilemiyorsa LMOVE/BLMOVE ile işleniyor listesi deseni kurun.
  • Büyük listede ortaya erişim: LINDEX, LSET, LINSERT ve büyük aralıklı LRANGE eleman sayısıyla orantılı çalışır.
  • count ile dönüş tipini unutmak: LPOP key string, LPOP key 1 dizi döndürür.

List, stream ve diğer tipler arasında seçim

List tipi basit kuyruklar, yığınlar ve "son N kayıt" listeleri için idealdir. Belgeler, belirsiz uzunlukta bir olay dizisini saklayıp işlemeniz gerektiğinde alternatif olarak stream tipini önerir; stream'lerde tüketici grupları ve onaylama mekanizması hazır gelir. Elemanları puana göre sıralamanız gerekiyorsa sorted set, indeksle doğrudan ve seyrek erişim gerekiyorsa Redis 8.8 ile gelen array tipi daha uygundur.

İhtiyaçÖnerilen tipNeden
Basit FIFO iş kuyruğuListLPUSH ve BRPOP ile iki uçta O(1)
Olay günlüğü ve tüketici gruplarıStreamKalıcı kayıt, onaylama, birden çok okuyucu
Puana göre sıralı koleksiyonSorted setOrtaya hızlı erişim, aralık sorguları
İndeksle seyrek erişimArrayİndeksle O(1) erişim, boşluklar ucuz

Bu tiplerin ayrıntıları için Redis streams yazısına, array veri tipi yazısına ve tip seçimi için Redis veri tipi seçimi rehberine bakabilirsiniz.

Sık Sorulan Sorular

Redis listesinin boyutu sınırlı mı?

Belgelere göre bir liste en fazla 2^32 - 1 (4.294.967.295) eleman tutabilir. Pratikte asıl sınır sunucunun belleğidir; listeyi belirli bir boyutta tutmak istiyorsanız her eklemeden sonra LTRIM çağırın.

BRPOP zaman aşımı milisaniye mi, saniye mi?

Saniyedir ve ondalıklı yazılabilir; örneğin 0.5 yarım saniye anlamına gelir. 0 verirseniz komut eleman gelene kadar süresiz bekler. Zaman aşımı dolarsa nil döner.

Boş bir listeyi silmem gerekir mi?

Hayır. Son eleman çekildiğinde Redis anahtarı kendisi siler. Belgelerdeki örnekte üç LPOP sonrasında EXISTS bikes:repairs sıfır döndürür.

LMOVEM her sunucuda çalışır mı?

Hayır, LMOVEM ve BLMOVEM Redis 8.10 ile geldi. Daha eski sunucularda tek elemanlı LMOVE ya da LMPOP kullanmanız gerekir. Sürümünüzü INFO server ile kontrol edebilirsiniz.

Listeyi pub/sub yerine mesajlaşma için kullanabilir miyim?

Kullanabilirsiniz, ama davranışları farklıdır. Listeye eklenen eleman bir tüketici alana kadar bekler ve yalnızca bir tüketiciye gider. Pub/sub mesajı ise o anda abone olan herkese iletilir ve saklanmaz. Karşılaştırma için Redis pub/sub yazısına bakabilirsiniz.

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 iş kuyruğu · Redis streams · Redis sorted set · Veri tipi seçimi · Redis nedir, nasıl kullanılır