Bir yazımda redisin transaction özelliğinden bahsetmiştim. Bu transaction özelliğini kullandığınız zaman, özellik çalışan command grubunun arasına başka bir command’ın giremeyeceğini hepsinin tek seferde çalışacağını garanti etmekteydi. Bunun dışında birden fazla command için tek tek server’a git gel yapmadan commandleri tek seferde servere göndererek önemli de bir performans kazancı sağlamaktaydı. Ancak her transactionun olduğu gibi redis transactionu da commandlerin arasına başka command sokmayarak yani operasyonu atomic yaparak diğer operasyonları bir süreliğine blocklayacaktır. Peki, transactionun commandleri tek seferde server’a yollamasından gelen performansı kazanmanın ancak diğer operasyonları blocklamadan bunu yapmanın bir yolu var mı? Evet tabiki var… Pipelining 🙂
Pipelineing bir network optimizasyonudur. Redisin aynı transcation gibi bir grup commandi arabelleğe aldığı ve bunları tek seferde sunucuya gönderdiği anlamına gelir. Ancak transactiondaki gibi komutların atomik yürütüleceğini yani araya başka bir command girmeyeceğini garanti etmez. Buradaki avantaj, her command için network gidiş dönüş süresinden tasarruf etmektir.
Aslında redis single threaded bir yapıdadır. Yani her individual command atomictir ve farklı iki istemciden gelen commandler dahi bir sırayla yürütülür. Transcationdaki Multi/exec, başka hiçbir istemcinin multi/exec dizisindeki komutlar arasında komut yürütmemesini sağlar.
Şimdi pipelining’i biraz daha derinlemesine inceleyelim.
Redis client-server üzerinde TCP server request/response protocolünü uygulayan bir caching tooldur. Bunun da anlamı şudur. Redisten giden request aşağıdaki stepleri izler.
- Client servera bir request gönderir ve soketten de okur. Bu da genellikle blocking yapacak şekildedir. Response için.
- Server da commandi işler ve geriye clienta response gönderir.
Somut bir örnekleme yapmak gerekirse
- Client: INCR X
- Server: 1
- Client: INCR X
- Server: 2
- Client: INCR X
- Server: 3
- Client: INCR X
- Server: 4
İstemciler ve Sunucular bir ağ bağlantısı üzerinden bağlanır. Böyle bir bağlantı çok hızlı (bir geri döngü arabirimi) veya çok yavaş (İnternet üzerinden iki ana bilgisayar arasında birçok atlama ile kurulan bir bağlantı) olabilir. Ağ gecikmesi ne olursa olsun, paketlerin istemciden sunucuya seyahat etmesi ve yanıtı taşıması için sunucudan istemciye geri dönmesi zaman alır. Bu süreye RTT (Gidiş-Dönüş Süresi) denir. Bir clientin arka arkaya birçok istek gerçekleştirmesi gerektiğinde bunun performansı nasıl etkileyebileceğini görmek kolaydır. Örneğin, Eğer, RTT 250 ms ise, server saniyede 100k operasyon handle edebilse bile, yine de saniye başına 4 request process edebilir oluruz.
Neyse ki bu kullanım durumunu iyileştirmenin bir yolu var. Pipelining ile her call ile RTT costu ödemekten kurtuluruz. Aşağıdaki örnek üzerinden düşünürsek, 4 kez RTT maliyeti yerine 1 RTT maliyeti ile işin içinden çıkabilir hale gelebiliriz.
- Client: INCR X
- Client: INCR X
- Client: INCR X
- Client: INCR X
- Server: 1
- Server: 2
- Server: 3
- Server: 4
NOT: Client tarafı requestleri pipelining ile gönderirken, sunucu, belleği kullanarak yanıtları kuyruğa almaya zorlanacak. Eğer, pipelining ile çok fazla command gönderme gibi bir ihtiyacınız varsa, her birini belli parçalarda batch olarak göndermeniz daha mantıklı olacaktır. Örneğin, 10K command yollayıp cevabını alıp bir 10k daha. Hız ise yine hemen hemen aynı olacaktır. Ancak kullanılan ek memory, bu 10klık commandlerin yanıtlarını queue’ya koymak için gereken maksimum miktarda olacaktır.
Bu sadece RTT meselesi değil
Pipelining sadece RTT’den kaynaklı latency costunu düşürmenin bir yolu değildir. Aynı zamanda redis serverinda saniyede işleyebileceğiniz operasyon sayısını da artırır. Pipelining kullanıldığında, bir çok komut genellikle single read() system callı ile okuma yapar ve multiple reply single write() sistem call’ı ile teslim edilir. Bu sebeple, saniye başına işlenen toplam query performansı uzun pipelinelar ile artar.