Mert Özen Her Satırda Daha İleri
Veritabanı Redis — #01

Redis Nedir, Cache'ten Fazlası mı? In-Memory Data Structure Server, Kullanım Alanları ve Lisans Durumu

Mert Özen 14 Kas 2026 13 dk 9 görüntülenme
Redis Nedir, Cache'ten Fazlası mı? In-Memory Data Structure Server, Kullanım Alanları ve Lisans Durumu

Redis, key'lerin altında string'den stream'e kadar data structure'lar tutan ve komutları tek bir main thread'de sırayla çalıştıran bir in-memory sunucudur; her komutun atomik olması da, tek bir uzun komutun herkesi bekletmesi de buradan gelir. Gerçek ölçümlerle 105 ms'lik KEYS'in başka bir client'ın PING'ini bekletmesi, lock'suz 100.000 INCR, sunucu süresi ile round trip farkı, cache dışı kullanım haritası, iyi olmadığı yerler, lisans tarihçesi ve Valkey farkı.

Sürüm notu: Bu yazıdaki örnekler Redis 8.10.3 (resmî redis:8 Docker image'ı), Valkey 9.1.3, .NET 10 ve StackExchange.Redis 3.4.35 ile çalıştırılmıştır.

Önce Tahmin Et

Bookstore'un (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) Redis'inde her kitabın görüntülenme sayacı ayrı bir key'de duruyor: bookstore:book:{id}:views. Katalog büyüdü, bir milyon key var. Bir gün ekipten biri "kaç kitabın sayacı var?" sorusunu cevaplamak için küçük bir script yazıyor ve production Redis'ine KEYS bookstore:book:* gönderiyor. Aynı anda API instance'ları normal trafiğe cevap veriyor.

Bunu iki ayrı bağlantıyla taklit edelim: instance-a her 10 ms'de bir PING atıp süresini ölçüyor, ops-script ise bir saniye sonra KEYS çalıştırıyor.

// İki ayrı multiplexer = iki ayrı uygulama instance'ı
await using ConnectionMultiplexer instanceA = await ConnectionMultiplexer.ConnectAsync(connectionString + ",name=instance-a");
// KEYS, StackExchange.Redis 3.x için bir admin komutu; instance B bir "ops script"
await using ConnectionMultiplexer instanceB = await ConnectionMultiplexer.ConnectAsync(connectionString + ",name=ops-script,allowAdmin=true");
IDatabase dbA = instanceA.GetDatabase();
IDatabase dbB = instanceB.GetDatabase();

List<double> pings = [];
Task pinger = Task.Run(async () =>
{
    for (int i = 0; i < 300; i++)
    {
        TimeSpan rtt = await dbA.PingAsync();
        pings.Add(rtt.TotalMilliseconds);
        await Task.Delay(10);
    }
});

await Task.Delay(1000);
Stopwatch sw = Stopwatch.StartNew();
RedisResult keys = await dbB.ExecuteAsync("KEYS", "bookstore:book:*");
Console.WriteLine($"instance B: KEYS returned {keys.Length:N0} keys in {sw.ElapsedMilliseconds:N0} ms");

KEYS yalnız script'i mi yavaşlatır, yoksa instance-a'nın PING'leri de etkilenir mi? Etkilenirse ne kadar?

docker run -d --name bookstore-redis -p 127.0.0.1:6379:6379 redis:8
dotnet run --project src/Bookstore.RedisLab -- seed-keys 1000000
dotnet run --project src/Bookstore.RedisLab -- keys-vs-ping
1,000,000 keys written in 1,339 ms
instance B: KEYS returned 1,000,000 keys in 471 ms
instance A: 300 PINGs, median 0.64 ms, p99 3.32 ms, max 102.58 ms

PING'lerin ortancası 0,64 ms, ama biri 102 ms sürmüş. Bu sayının nereden geldiğini Redis'in kendisi söylüyor. SLOWLOG, belli bir süreyi aşan komutları sunucu tarafında çalışma süresiyle kaydeder:

docker exec bookstore-redis redis-cli SLOWLOG GET 1
4
1794650692
105261
KEYS
bookstore:book:*
172.17.0.1:59812
ops-script
2

Üçüncü satır mikro saniye cinsinden süre: KEYS sunucuda 105 ms çalışmış. instance-a'nın en uzun PING'i de 102 ms. Bu tesadüf değil. PING o sırada başka bir bağlantıdan geldi, ama Redis'te komutları çalıştıran tek bir main thread var ve o 105 ms boyunca bir milyon key'i tarıyordu. PING sırasını bekledi. Script'in gördüğü 471 ms'nin geri kalanı, bir milyon key adının ağ üzerinden aktarılması ve client'ta parse edilmesi; o kısım sunucuyu meşgul etmedi. Bu yazıda Redis'i bu gözlemden başlayarak anlatıyoruz: veri modeli, tek thread'li çalışma ve bundan doğan atomiklik, latency'nin kaynağı, Redis'in cache dışındaki kullanım alanları, iyi olmadığı yerler ve 2024'ten beri herkesin konuştuğu lisans konusu.

Key ve Data Structure: Redis'in Veri Modeli

Redis'i "string tutan bir cache" diye tanıdıysan modelin yarısını görmüşsün. Redis'te her şey bir key altında durur, ama key'in değeri yalnız string değil; bir data structure olabilir: hash, list, set, sorted set, stream ve Redis 8 ile birlikte gelen JSON, time series, probabilistic yapılar ve vector set. Komutlar da bu yapılara göre ayrılır: INCR string'e, HSET hash'e, LPUSH list'e uygulanır. Sunucu, değeri okuyup client'a gönderip geri yazmak yerine işlemi yapının üstünde, yerinde yapar.

127.0.0.1:6379> SET bookstore:book:42:views 1250
OK
127.0.0.1:6379> HSET bookstore:cart:7 42 1 1984 2
(integer) 2
127.0.0.1:6379> LPUSH bookstore:customer:7:recent 42 1984 101
(integer) 3
127.0.0.1:6379> SADD bookstore:tag:classic 42 1984 101
(integer) 3
127.0.0.1:6379> ZADD bookstore:bestsellers:2026-11-14 37 42 12 1984
(integer) 2
127.0.0.1:6379> XADD bookstore:orders:events * type OrderPlaced orderId 1001
"1794650721775-0"
127.0.0.1:6379> JSON.SET bookstore:book:42 $ '{"title":"Dune","price":349.9}'
OK
127.0.0.1:6379> TYPE bookstore:cart:7
hash
127.0.0.1:6379> OBJECT ENCODING bookstore:cart:7
"listpack"
127.0.0.1:6379> OBJECT ENCODING bookstore:book:42:views
"int"
127.0.0.1:6379> OBJECT ENCODING bookstore:tag:classic
"intset"
127.0.0.1:6379> TYPE bookstore:book:42
ReJSON-RL
127.0.0.1:6379> HINCRBY bookstore:cart:7 1984 1
(integer) 3

İki ayrıntıya dikkat et. Birincisi TYPE ile OBJECT ENCODING farkı: tip, komutların gördüğü mantıksal yapı; encoding ise bellekteki gerçek gösterim. Küçük bir hash listpack olarak, yani tek bir bellek bloğunda sıkıştırılmış biçimde durur; büyüyünce hashtable'a çevrilir. Yalnız sayılardan oluşan küçük bir set intset olur. Bu geçişlerin bellek ve hız etkisini 04 ve 05'te ölçeceğiz. İkincisi, son satırdaki HINCRBY: sepetteki 1984'ün adedini artırmak için hash'i okuyup değiştirip geri yazmadın; tek komut gönderdin.

JSON.SET'in çalışması da bir ayrıntı. Redis 8 ile JSON, Query Engine, time series ve probabilistic yapılar, eskiden Redis Stack adıyla ayrı dağıtılan modüllerden Redis Open Source dağıtımının parçası oldu. Resmî image'da MODULE LIST bunları search, timeseries, bf, vectorset ve ReJSON olarak listeliyor.

Tek Thread, Sırayla Çalışan Komutlar

01-event-loop-tr.png

Redis binlerce bağlantıyı tek bir event loop ile yönetir: socket'lerden gelen veriyi okur, RESP (Redis Serialization Protocol; Redis'in client ile konuştuğu metin tabanlı protokol) mesajlarını parse eder ve komutları main thread'de birer birer çalıştırır. Bir komut çalışırken başka bir komut araya giremez. Önce Tahmin Et'teki 102 ms'lik PING bu modelin doğrudan sonucu.

"Redis tek thread'li" cümlesi bugün tam doğru değil. UNLINK ile büyük key'lerin arka planda silinmesi, disk işlemleri gibi bazı işler yan thread'lerde yapılır. Redis 6'dan beri socket okuma, yazma ve protokol parse işini I/O thread'lerine dağıtmak da mümkün. redis.conf'taki açıklamaya göre bu varsayılan olarak kapalı (io-threads 1), en az 4 çekirdekli makinelerde ve gerçekten CPU sıkıntısı varsa açılması öneriliyor; 4 çekirdekte 3 I/O thread gibi. Redis 8.4 sürüm notları I/O threading'de ciddi throughput artışı bildiriyor. Ama hiçbir durumda komutun kendisi paralel çalışmaz: I/O thread'leri yalnız veri taşır ve parse eder, keyspace'e dokunan kod hâlâ tek thread'dir.

127.0.0.1:6379> CONFIG GET io-threads
1) "io-threads"
2) "1"
127.0.0.1:6379> INFO server
redis_version:8.10.3
redis_mode:standalone
multiplexing_api:epoll
io_threads_active:0

Bu tasarım iki şey getirir. İyi tarafı: veri yapılarına lock gerekmez, context switch maliyeti yoktur, her komut kendi içinde atomiktir. Kötü tarafı: uzun süren her komut herkesi bekletir. Bir milyon key üstünde KEYS, on milyon elemanlı bir set'te SMEMBERS, dev bir hash'te HGETALL, uzun çalışan bir Lua script'i; hepsi aynı kuyruğu tıkar. Redis'te komutların karmaşıklığı dokümanda O(1), O(N), O(log N) diye yazılır ve bu bir ayrıntı değil, kapasite planının parçasıdır.

Atomiklik: Lock Olmadan 100.000 INCR

Tek thread'in iyi tarafını ölçelim. 100 eşzamanlı çağıran, her biri aynı sayacı 1.000 kez artırıyor. Client tarafında hiçbir lock yok:

const string key = "bookstore:book:42:views";
await db.KeyDeleteAsync(key);

// 100 eşzamanlı çağıran, her biri 1.000 kez artırıyor; client tarafında lock yok
Task[] callers = [.. Enumerable.Range(0, 100).Select(_ => Task.Run(async () =>
{
    for (int i = 0; i < 1_000; i++)
    {
        await db.StringIncrementAsync(key);
    }
}))];
await Task.WhenAll(callers);

Console.WriteLine($"{key} = {await db.StringGetAsync(key)}");
bookstore:book:42:views = 100000

Her seferinde 100.000. INCR sunucuda "değeri oku, bir ekle, yaz" adımlarının tamamını tek komut içinde yapar ve o sırada başka komut çalışmadığı için iki artış birbirini ezemez. Aynı işi client'ta GET sonra SET diye yapsaydın iki komutun arasına başka çağıranların komutları girerdi ve artışlar kaybolurdu; bu klasik hatayı 03'te gerçek sayılarla göstereceğiz.

Atomiklik tek komutla sınırlı. "Stok yeterliyse düş, değilse reddet" gibi birden fazla adımlı bir mantık tek komuta sığmıyorsa MULTI/EXEC, Lua script'i ya da Redis Functions gerekir; onları 08'de işliyoruz. Yine de Redis'in komut seti bu yüzden zengin: SET … NX, HINCRBY, LMOVE, ZINCRBY gibi komutlar, client'ta iki adımda yapılacak işi sunucuda tek adıma indirmek için var.

Latency Nereden Geliyor?

Redis "hızlı" diye bilinir. Ama uygulamanın gördüğü süreyle Redis'in harcadığı süre çok farklı şeyler. INFO commandstats, her komutun sunucuda çalıştığı ortalama süreyi tutar. Aynı makinedeki Docker container'ına 10.000 kez sırayla küçük bir GET, sonra 50 kez 4 MB'lık bir value için GET gönderip iki tarafı karşılaştıralım:

dotnet run --project src/Bookstore.RedisLab -- rtt
GET small (4 bytes)  client:      176 us/call   server (commandstats): 0.37 us/call
GET big (4 MB)       client:    17742 us/call   server (commandstats): 3.44 us/call

Küçük GET sunucuda 0,37 mikro saniye çalışıyor; uygulama ise 176 mikro saniye bekliyor. Aradaki farkın neredeyse tamamı round trip: isteğin socket'e yazılması, Docker'ın ağ katmanından geçmesi, cevabın dönmesi ve client'ın işlemesi. Aynı veri merkezindeki iki makine arasında bu süre genelde daha da uzun olur. Yani tek tek beklenen komutlar söz konusu olduğunda darboğaz Redis değil, round trip sayısıdır. Bu yüzden pipelining ve StackExchange.Redis'in tek bağlantıda multiplexing yapması önemli; 02'de ölçeceğiz.

4 MB'lık value'da tablo değişiyor: uygulama 17,7 ms bekliyor. usec_per_call ise 3,44 µs'de kalıyor, çünkü yalnız komutun çalışmasını ölçer; 4 MB'ın socket'e yazılması bu süreye girmez. O yazma da event loop'un (ya da varsa I/O thread'lerinin) zamanından yer, client output buffer'ında bellek tutar ve ağı doldurur. Büyük value'ların maliyeti bu yüzden metric'lerde ilk bakışta görünmez. Pratik kural: Redis'te value'ları küçük tut, büyük object'leri parçalara ya da daha uygun bir yapıya böl. Büyük ve sık erişilen key'leri bulmayı 12'de anlatacağız.

Cache Dışında Redis: Kullanım Haritası

Caching serisinde Redis'i IDistributedCache ve HybridCache'in arkasında, yani bir cache store olarak kullandık. Bu seride geri kalanına bakıyoruz. Bookstore'da Redis'in cache dışında üstlenebileceği işler ve seride nerede anlatıldıkları:

İşData typeTemel komutlarYazı
Kitap görüntülenme sayacıstringINCR, EXPIRE … NX03
Tek kullanımlık doğrulama kodu, idempotency keystringSET … NX EX, GETDEL03
Müşteri sepetihashHSET, HINCRBY, HGETALL04
Son görüntülenen 10 kitap, basit iş kuyruğulistLPUSH, LTRIM, LMOVE05
Kitap etiketleri, günlük tekil ziyaretçiset, HyperLogLogSADD, SINTER, PFADD05, 07
Haftanın çok satanları, sliding windowsorted setZINCRBY, ZRANGE06
Stok rezervasyonustring + Lua/FunctionsEVAL, FCALL08
Fiyat değişikliğini instance'lara duyurmakpub/subPUBLISH, SUBSCRIBE09
Sipariş olaylarını worker'lara dağıtmakstreamXADD, XREADGROUP, XACK10
Katalogda filtreli aramaJSON + Query EngineFT.CREATE, FT.SEARCH11

Tabloda olmayan iki iş daha sık sorulur. Rate limit için sayaç ve sorted set'ler kullanılır, ama tasarım tarafı System Design serisinin rate limiting yazısında ve ASP.NET Core Güvenliği serisinde anlatıldı. Distributed lock için de SET … NX PX kullanılır; neden tek başına yetmediğini ve fencing token'ı Distributed Systems Temelleri serisinin "Distributed Lock, Lease ve Fencing Token: Kilit Neden Yetmez?" yazısında anlattık. Burada komut tarafına bakacağız, teoriyi tekrar etmeyeceğiz.

Redis'in İyi Olmadığı Yerler

Redis'in hızının bedeli var; seçmeden önce şunları tart:

  • Bütün veri RAM'de durur. Bookstore'un bir milyon siparişini satır satır Redis'e koymak mümkün, ama gigabayt başına RAM maliyeti diskten çok yüksek. Redis'te tutulan veri ya küçük ve sıcak olmalı ya da bilerek parasını ödediğin bir kullanım olmalı.
  • Sorguyu key tasarımı belirler. JOIN yok, ad-hoc WHERE yok. "Kasım ayında İstanbul'dan gelen, 500 TL üstü siparişler" gibi bir soruyu cevaplamak için ya veriyi o soruya göre önceden yapılandırırsın ya da Query Engine kurarsın. İlişkisel veri ve raporlama için SQL Server ya da PostgreSQL doğru yer.
  • Dayanıklılık ayara bağlı ve varsayılan olarak zayıf. Persistence asenkrondur, replication asenkrondur; bir yazının "tamam" cevabı alıp sonra kaybolması mümkün senaryolar var. Siparişin tek kaydı Redis'te durmamalı. Bunun ayrıntıları 12 ve 13'te.
  • Uzun komutlar ve büyük value'lar herkesi bekletir. Önce Tahmin Et'teki 105 ms'yi hatırla.

Pratikte en sağlıklı kullanım şu: kalıcı ve doğru kabul edilen kayıt ilişkisel veritabanında, Redis onun yanında sayaç, sıralama, oturum, kuyruk ve hızlı okuma için çalışan ikinci bir veri katmanı.

Lisans, Valkey ve Bu Serinin Kuralı

Redis'i self-host edeceksen lisans bir mimari karar. Kısa tarihçe şöyle: Redis 7.2 ve öncesi BSD (kısıtlaması az bir açık kaynak lisansı) ile dağıtıldı. Mart 2024'te açıklanan kararla Redis 7.4'ten itibaren lisans RSALv2 (Redis'in kaynak kodu açık ama kullanımı kısıtlayan lisansı) ve SSPLv1 (servis olarak sunmayı kısıtlayan lisans) arasında seçim yapılan bir modele geçti; Redis'in lisans sayfası bu ikisinin açık kaynak lisansı olmadığını kendisi yazıyor. 1 Mayıs 2025'te çıkan Redis 8 ile üçüncü seçenek olarak AGPLv3 (ağ üzerinden sunulan değiştirilmiş yazılımın kaynağının da paylaşılmasını şart koşan açık kaynak lisansı) eklendi ve dağıtımın adı Redis Open Source oldu. Lisans tablosunu Caching serisinin "IDistributedCache ve Redis'i Cache Olarak İşletmek" yazısında vermiştik; burada tekrarlamıyorum.

2024 değişikliğine cevap olarak Linux Foundation altında Valkey başladı: Redis 7.2.4'ten fork, BSD lisanslı. Valkey 9.0 Ekim 2025'te hash field expiration, atomic slot migration ve cluster'da çoklu database ile çıktı; bu yazı yazılırken güncel kararlı sürüm 9.1.3. Microsoft Research'ün Garnet'i ise .NET ile yazılmış, MIT (çok az koşul koyan bir açık kaynak lisansı) lisanslı ve RESP konuştuğu için mevcut client'larla çalışan bir başka seçenek.

Managed tarafta da tablo değişti. Azure Cache for Redis emekliye ayrılıyor: Microsoft Learn'deki sık sorulan sorular sayfasına göre Basic, Standard ve Premium tier'ları 30 Eylül 2028'de, Enterprise tier'ları 31 Mart 2027'de; yerine Redis Enterprise yazılımı üzerinde çalışan Azure Managed Redis öneriliyor. Azure Managed Redis varsayılan olarak cluster modunda geldiği için multi-key komutlarda 13'te anlatacağımız slot kısıtları önem kazanıyor. AWS (Amazon Web Services; Amazon'un sunduğu bulut platformu), ElastiCache ve MemoryDB'de Ekim 2024'ten beri Valkey'i motor olarak sunuyor; Google Cloud'da Memorystore for Valkey var.

Bu seride komutları Redis 8'de gösteriyoruz ve Valkey'de olmayanları ayrıca işaretliyoruz. İki sunucuyu yan yana çalıştırınca ilk fark sürüm alanında görünüyor:

docker run -d --name bookstore-valkey -p 127.0.0.1:6380:6379 valkey/valkey:9
docker exec bookstore-valkey valkey-cli INFO server
redis_version:7.2.4
server_name:valkey
valkey_version:9.1.3
io_threads_active:0

Valkey, eski client'lar bozulmasın diye redis_version alanında fork ettiği sürümü, 7.2.4'ü bildiriyor; asıl sürüm valkey_version'da. Sürüme bakıp özellik açan kod bu yüzden yanılabilir. StackExchange.Redis de bunu hesaba katıyor: 3.4.20 sürüm notlarına göre client, HELLO cevabındaki version'ı Redis API sürümü saymadan önce server alanına bakıyor; Valkey'e bağlanınca IServer.Version'ın ne gösterdiğini 02'de göreceğiz. Komut seti de ayrışmaya başladı: Redis 8.10.3 COMMAND COUNT için 447, Valkey 9.1.3 257 döndürüyor; farkın çoğu Redis'e dahil edilen modüllerin komutları, ama çekirdekte de fark var:

docker exec bookstore-valkey valkey-cli DELEX k IFEQ v
(error) ERR unknown command 'DELEX', with args beginning with: 'k' 'IFEQ' 'v'

DELEX Redis 8.4'te geldi; Valkey'de benzer işi DELIFEQ yapıyor. Bu tür farkları her yazıda o komutun yanında not edeceğiz.

Üretimde Dikkat

Redis'i production'a koymadan önce birkaç şeyi baştan kararlaştır. Bir: bellek bütçesi. maxmemory ve eviction policy, Redis'i cache olarak mı yoksa veri tutan bir sunucu olarak mı kullandığına göre farklı ayarlanır; kuyruk ve stream tutan bir Redis'te key'lerin sessizce silinmesi veri kaybıdır. 12'de bunu ayrıntılı anlatacağız. İki: uzun komut disiplini. Uygulama kodunda O(N) komutları büyük key'lerde çalıştırma, KEYS yerine SCAN kullan, uzun süren her şeyi SLOWLOG ile izle. slowlog-log-slower-than varsayılan olarak 10.000 µs, yani 10 ms; Önce Tahmin Et'teki KEYS bu yüzden kaydedildi. Üç: client'lara isim ver. SLOWLOG çıktısında ops-script yazması, sorunlu komutun kimden geldiğini saniyeler içinde gösterdi; StackExchange.Redis'te bu name= ayarı. Dört: sürüm takibi. 2026'da 8.x sürüm hatlarının hepsinde aynı gün yayımlanan güvenlik sürümleriyle uzaktan kod çalıştırmaya yol açabilen açıklar kapatıldı (Mayıs 2026'daki 8.4.3 ve 8.6.3 gibi); bu yazıda kullandığımız 8.10.3 de bir güvenlik sürümü. Redis'i internete açmamak, ACL (Access Control List; kullanıcı bazında komut ve key izinleri) ve patch disiplini 14'ün konusu.

Sık Düşülen Tuzaklar

Redis'i her şeyin hızlı olduğu bir kara kutu sanmak. Komutun karmaşıklığına bakmadan büyük key'lerde O(N) komut çalıştırmak, tek bir isteğin bütün instance'ları yavaşlatmasına yol açar.

Ölçümü yalnız sunucu metric'lerinden okumak. usec_per_call düşük diye büyük value'ların ucuz olduğunu sanmak yanlış; maliyet ağda ve output buffer'da.

I/O thread'lerini açınca komutların paralel çalışacağını sanmak. Komut yürütme hâlâ tek thread; uzun bir komut I/O thread sayısından bağımsız olarak herkesi bekletir.

Redis'i siparişin tek kaydı yapmak. Persistence ve replication ayarlarını bilmeden kritik veriyi yalnız Redis'te tutmak, ilk failover'da veri kaybı demektir.

Valkey'i Redis'in birebir kopyası sanmak. 2024'ten beri iki proje ayrı gelişiyor; Redis 8.4 ve sonrasının komutlarını kullanan kod Valkey'de ERR unknown command alabilir.

Küçük Bir Deneme

★☆☆ keys-vs-ping deneyini bir milyon yerine 100.000 key ile çalıştır. SLOWLOG'daki süre ve instance-a'nın en uzun PING'i nasıl değişiyor? KEYS yerine DBSIZE kullanınca ne oluyor?

★★☆ rtt komutunu 4 MB yerine 64 KB ve 1 MB'lık value'larla tekrarla. Client tarafındaki süre value boyutuyla nasıl büyüyor? INFO clients'taki çıktıyla client-output-buffer-limit ayarına da bak.

★★★ INCR deneyini Valkey'e karşı çalıştır (BOOKSTORE_REDIS=localhost:6380). Sonuç aynı mı? Sonra iki sunucuda da CONFIG SET io-threads 2 dene: hangisi bu ayarı çalışırken değiştirmene izin veriyor, hangisi container'ı yeniden başlatmanı istiyor?

Bir sonraki yazıda lab'ı kalıcı hâle getiriyoruz: redis:8 ve valkey/valkey:9 yan yana, redis-cli ve Redis Insight, StackExchange.Redis'te tek ConnectionMultiplexer, multiplexing ve pipelining'in gerçekte nasıl çalıştığı ve Bookstore'un key isimlendirme kuralları.