Redis'i Veri Deposu Gibi Kullanmak: Bellek Politikası, Kalıcılık ve Kuyruk Olarak Sınırları
Redis önbellek olarak girer, oturumlar ve kuyruk da orada kalır. maxmemory-policy seçimi, RDB ile AOF'un bedeli, asenkron replikasyonun kaybı ve kuyrukta liste ile stream farkı.
Redis bir projeye önbellek olarak girer, birkaç ay sonra oturumlar, sayaçlar, kilitler ve iş kuyruğu da orada durur. Bu geçiş kasıtlı bir karar olarak alınmaz, kendiliğinden olur; sonra ilk kesintide "önbellek kaybolur, önemli değil" varsayımıyla kurulmuş bir sistemin aslında tek kopya veriyi orada tuttuğu anlaşılır. Soru Redis'in iyi mi kötü mü olduğu değil, hangi garantiyi verdiği ve o garantinin ürünün ihtiyacını karşılayıp karşılamadığıdır.
Bellek politikası: sessiz veri kaybının birinci kaynağı
Redis'te maxmemory ayarlanmamışsa süreç belleği doldurana kadar büyür ve işletim sistemi onu öldürür. Ayarlandıysa, sınıra ulaşıldığında ne olacağı maxmemory-policy ile belirlenir ve seçenekler birbirinden çok farklı davranır.
noeviction hiçbir şey atmaz, yazma komutlarını hata ile reddeder. Veri deposu gibi kullanılan bir Redis için doğru olan budur: veri sessizce kaybolmaz, uygulama hata alır. Bedeli şudur, uygulama bu hatayı gerçekten ele almalıdır; SET çağrısının dönüş değerine bakmayan bir kod yolu, bu politikada da veri kaybeder, sadece suç Redis'ten uygulamaya geçer.
allkeys-lru ve allkeys-lfu önbellek için doğru seçimdir, en az kullanılanı atar. Veri deposu olarak kullanılan bir örnekte bu politika, sayaçları ve kilitleri de atılabilir yapar.
En tehlikeli seçenek volatile-lru ailesidir: yalnızca TTL verilmiş anahtarları atar. Mantıklı görünür ama bir kullanım deseni bu politikayı sessizce felç eder. Anahtarların çoğuna TTL verilmemişse, atılacak aday kalmaz ve Redis noeviction gibi davranmaya başlar; yani aslında bir önbellek sandığınız sistem, yazma hatası vermeye başlar. Politikayı seçerken anahtarların ne kadarının TTL taşıdığını ölçün, varsaymayın. INFO keyspace çıktısındaki expires sayısı toplam anahtar sayısının yanında çok küçükse volatile-* yanlış seçimdir.
Sınırın kendisini belirlerken de pay bırakmak gerekir. maxmemory değerini makinenin bütün belleğine eşitlemek, Redis'in veri dışında da bellek kullandığını görmezden gelir: istemci giriş ve çıkış tamponları, kopya besleme tamponu ve yeniden yazma sırasında çoğaltılan sayfalar bu hesabın dışındadır. Pratikte veri için fiziksel belleğin yarısını hedeflemek, yazma yoğun bir örnekte makul bir başlangıçtır. INFO memory çıktısında used_memory veri boyutunu, used_memory_rss ise sürecin işletim sistemine göre gerçek ayak izini verir; ikisinin arasındaki farkın büyümesi, sınırı veri boyutuna göre ayarlamanın neden yetmediğini gösterir.
Kalıcılık: iki mekanizma, iki ayrı maliyet
RDB belirli aralıklarla tam bir anlık görüntü yazar. Ucuzdur, dosya küçüktür, geri yükleme hızlıdır. Kaybı penceredir: son anlık görüntüden sonraki her yazma, çökmede kaybolur.
AOF her yazma komutunu bir dosyaya ekler. appendfsync everysec varsayılanı, en kötü durumda bir saniyelik kayıp anlamına gelir; always her komutta diske senkronize eder ve verimi ciddi biçimde düşürür. AOF dosyası zamanla büyüdüğü için yeniden yazma (rewrite) gerekir ve bu da kaynak ister.
İkisini birlikte kullanmak çoğu durumda doğru düzendir: AOF kayıp penceresini küçültür, RDB hızlı geri yükleme ve taşınabilir yedek verir.
Burada en çok ısıran tuzak belleğe dair. RDB anlık görüntüsü ve AOF yeniden yazması fork ile yapılır. Kopyalama sırasında yazılan sayfalar çoğaltıldığı için bellek kullanımı kısa süreli olarak iki katına yaklaşabilir. Yazma yoğun bir örnekte, belleğin yüzde 60'ını kullanıyor görünen bir süreç tam da yedek alırken öldürülür. Bu yüzden vm.overcommit_memory=1 ayarı önerilir ve bu yüzden makinenin fiziksel belleği veri boyutunun iki katına yakın planlanır. Aynı nedenle INFO persistence çıktısındaki son yeniden yazma süresi ve rdb_last_bgsave_status izlenmeye değer alanlardır.
Replikasyon neyi garanti etmez
Redis replikasyonu asenkrondur. Birincil düğüm, yazmayı istemciye onayladıktan sonra kopyaya gönderir. Birincil, yazmayı onayladıktan hemen sonra düşerse ve bir kopya terfi ederse, onaylanmış ama kopyaya ulaşmamış yazmalar kaybolur. Bu bir hata değil, tasarım tercihidir ve hız buradan gelir.
WAIT komutu yazmanın belirli sayıda kopyaya ulaşmasını beklemenizi sağlar; kaybı azaltır, sıfırlamaz ve gecikmeyi artırır. Sentinel ya da Cluster kurmak da bu açığı kapatmaz; onlar kullanılabilirliği yönetir, dayanıklılığı değil. Para transferi, stok düşümü, idempotency kaydı gibi "kaybolursa bedeli para olan" veriler için doğru yer bir ACID veritabanıdır. Redis bu akışta hız katmanı olarak kalabilir, kaydın kendisi olmamalı.
Kuyruk olarak Redis: liste ile stream arasındaki fark
Redis'i iş kuyruğu yapan en yaygın desen LPUSH ve BRPOP ikilisidir. Basit ve hızlıdır. Kusuru şudur: BRPOP işi listeden çıkarır, işçi işi yaparken ölürse iş hiçbir yerde kalmaz. Onay (acknowledgement) kavramı yoktur.
Streams bu boşluğu kapatır. Tüketici grubu ile okunan her kayıt, XACK gelene kadar bekleyen listede kalır; ölen bir işçinin işleri XPENDING ile görünür ve XCLAIM ile başka bir işçiye devredilebilir. Yani Streams "en az bir kez" teslim kurabileceğiniz bir yapı sunar, ama bunu kendiliğinden yapmaz: işçinin ölümünü fark eden, bekleyen kayıtları sahiplenen ve ölü mektup kutusuna taşıyan kodu siz yazarsınız.
Üç seçenek arasında seçim yaparken şu çizgiyi öneriyorum. İşler kaybolabilir ve yeniden üretilebilirse (önbellek ısıtma, küçük resim üretimi, bildirim tekrarı) Redis listesi yeterlidir ve en az kodu gerektirir. Teslim garantisi gerekiyorsa ve iş hacmi orta ölçekteyse, zaten bir ilişkisel veritabanı kullanıyorsanız kuyruğu orada tutmak çoğu takım için en az sürprizli yoldur: aynı işlem içinde yazarsınız, kaybolan iş olmaz. Çok yüksek hacim, tekrar okuma ve birden çok tüketici grubu gerekiyorsa kalıcı bir mesaj altyapısı doğru araçtır. Redis Streams bu ikisinin arasında, iyi bir orta yol olmakla birlikte işletme yükünün bir kısmını uygulamaya bırakır.
Tuzaklar
Üretimde KEYS çağırmak bütün anahtar alanını tarar ve tek iş parçacıklı sunucuyu o süre boyunca bloklar; SCAN ile gezin. Tek bir büyük anahtar (milyonlarca elemanlı liste, yüz megabaytlık değer) hem bu anahtara dokunan komutları hem de yeniden yapılandırmayı yavaşlatır; büyük anahtarları --bigkeys ile arayın.
TTL vermeyi unutmak, kalıcı veri deposu niyeti olmayan bir örnekte en yaygın bellek sızıntısı sebebidir. Bellek parçalanması, INFO memory içindeki mem_fragmentation_ratio ile görünür; 1,5 üzerindeki değerler aktif birleştirme (activedefrag yes) için gerekçedir, ama bu da CPU ister.
Son olarak kopya çıkış tamponu: büyük bir veri kümesini yavaş bir ağ üzerinden besleyen kopya, birincilde tampon sınırını aşarsa bağlantı koparılır ve senkronizasyon baştan başlar. Döngüye girer. client-output-buffer-limit değerini veri boyutuna göre ayarlayın.
Ne zaman Redis'i veri deposu yapmamalı
Verinin tek kopyası Redis'te duruyorsa, kayıp penceresini saniye cinsinden kabul ediyor olmanız gerekir. Bunu kabul edemiyorsanız, kalıcılık ayarlarıyla değil mimariyle çözün: kaydın sahibi dayanıklı bir veritabanı olsun, Redis okuma hızını ve geçici durumu taşısın. Redis'i hızlı bir veritabanı gibi kullanmak işler, ta ki bir düğüm beklenmedik biçimde ölene kadar; o gün kaybın boyutu, aylar önce maxmemory-policy satırına yazdığınız kelimeyle belirlenmiş olur.