Caching ile ilgili Problemler ve Stratejiler

Metin İRDEN
ÇSTech
Published in
4 min readApr 26, 2021

Caching ile ilgili genel düşüncem, olabildiğince kullanmaktan kaçınmak. Ancak gerçek şu ki performans ve ölçeklenebilirliği arttırabilmek adına tech-stack’inize eklemek zorunda kalabilirsiniz. Fakat bunu yaptığınızda üstesinden gelmek zorunda kalacağınız bazı problemler ile karşı karşıya kalabilirsiniz.

Karşılaşacağınız problemlerden ilki, hangi seviyede caching yapacağınızdır.

1. Integrated-Cache

Amazon Aurora gibi bazı veritabanları, kendi içinde yönetilen ve write through yeteneklerine sahip entegre bir cache sunar. Gelen değişikliklere göre cache’i otomatik olarak günceller. Bunu kullanmak için application katmanında hiçbir şey yapmanız gerekmez.

Entegre cache’in dezavantajlarını düşünürsek kaşımıza ilk çıkacak şeyler, boyutları ve yetenekleridir. Boyut olarak veritabanı instance’si tarafından cache için ayrılan kullanılabilir memory ile sınırlıdır. Ek olarak internal cache değerlerini diğer instance’ler ile paylaşmak gibi özelliklere sahip değildir.

2. Local-Cache

Local cache, sık kullanılan verileri application seviyesinde depolar. Bu yaklaşım, ağ trafiğini ortadan kaldırdığı için alternatif yaklaşımlara göre veriyi daha hızlı elde etmeye yarar.

Önemli bir dezavantaj ise bu cache’in application node’larında bağımsız bir şekilde in-process olmasıdır. Ayrı cache’lerde depolanan bilgiler diğer application local cache’leri ile paylaşılamaz. Bu, bilgi paylaşımının kritik olduğu distributed ortamlarda zorluklar yaratır. Ek olarak, application seviyesinde bulunan cache yapılarının rehydrate etmek gibi problemler ile oluşturabilir ki buda cache sistemlerinin efektif olmayan bir kullanımına yol açar. Bu dezavantajların çoğu remote cache yaklaşımı ile hafifletilebilir.

3. Remote-Cache

Remote cache (Side cache), cache’e alınan verileri memory’de depolamak için ayrı çalışan instance’lardır (tekil veya çoğul). Remote cache’ler, genellikle Redis yada Memcached gibi key-value NoSQL kaynaklar ile oluşturulur. Distributed cache’deki bir node saniyede yüz binlerce isteğe cevap verebilir.

Remote cache’e yapılan bir isteğin ortalama süresi, herhangi bir disk bazlı veritabanından oldukça hızlıdır. Request’ler hızlarda karşılanabiliyorken, local cache yapıları neredeyse gereksiz durumdadır. Yine de network gecikmeleri sizin için endişeye yol açacak seviyedeyse, iki-seviyeli bir caching’i local ve remote cache’i bir arada kullanarak oluşturabilirsiniz.

Remote cache’lerdeki verilerin geçerliliğinin yönetilmesi ve düzenlenmesi, onu kullanan application’larınız tarafından yönetilir. Cache’in kendisi doğrudan veritabanına bağlı değildir, ancak veritabanı ile birlikte kullanılır.

Remote cache kullanmaya karar verdiğinizde karşılaşacağınız diğer bir problem ise, hangi caching pattern’i kullanacağınızdır.

1. Cache-Aside (Lazy Loading)

Cache aside en çok kullanılan caching pattern’lerinden biridir. Temel akış şu şekilde açıklanabilir:

  1. Application’ınızın veriye ihtiyacı olduğunda direkt olarak veri kaynağına gitmek yerine, bunun öncesinde cache’inizde bu verinin olup olmadığını kontrol edersiniz.
  2. Eğer veriyi cache’den alabiliyorsanız (cache hit) cache’i alıp cağıran kişiye dönebilirsiniz.
  3. Eğer ilgili veri cache’de yoksa (cache miss), ilgili veri kaynağından alınmalı/hesaplanmalı. Kaynaktan gelen veri cache’e yazılır ve çağıran kişiye dönülür.

Cache aside’ın bazı avantajlarından bahsedersek:

Cache sadece application’ın talep ettiği verileri içinde barındırır, buda cost-effective bir kaynak tüketimi konusunda size yadımcı olur.

İmplemente etmesi hazır framework’ler de kullansanız, kendinizde yazsanız basittir ve gözle görülebilir şekilde performans artışını direkt olarak sağlar.

Cache aside’ın alternatiflerine göre dezavantajına gelirsek, ilk veri cache miss’den sonra elde edildiği için ilk response’unuz her zaman bir overhead’e sahip olacaktır.

2. Write-Through

Write through cache, cache’in oluşturulma sürecini tersine çevirir. Veriyi ihtiyaç dahilinde cache miss’den sonra kaynaktan almak yerine proaktif olarak kaynağı sürekli takip ederek oluşturur. Akış ise şu şekildedir:

  1. Application, batch yada bir backend-proccess’i veri kaynağını bir command yollar.
  2. Command veri kaynağına yollandıktan sonra, aynı command ile cache’de güncellenir.

Write through cache çoğunlukla Cache aside pattern’i ile birlikte kullanılır. Application cache miss yaşadığında yada cache expire olmuşsa, cache’i tekrar oluşturmak adına lazy-loading cache’i günceller.

Write through yaklaşımın bazı avantajları/dezavantajları ise aşağıdaki gibidir:

  • Cache’in sürekli olarak veri kaynağı ile senkron kalmasından dolayı cache hit oranınız çok yüksek olacaktır. Buda hem veri kaynağında daha az yük yaratmayı hemde, kullanıcı deneyimini arttırmayı sağlar.
  • Veri kaynağına yapılacak git geller minimal seviyede tutulacağı için yük çok fazla o tarafa binmeyecektir. Bunun yanında, cache’de az request alan verilerinizinde yer alması kaçınılmazdır, buda size fatura olarak geri dönebilir.

Bu yaklaşımları bir arada kullanmak (write-through ve cache-aside) verinizi güncel ve yalın tutmak için muhtemelen en iyi yaklaşım olacaktır.

Cache Validity

Cache’lenmiş verilerinizin güncelliğini, time to live(TTL) yada expiration değerleri vererek sağlayabilirsiniz. Verilen süre geçtikten sonra ilgili değer cache’den silinir ve uyguladığınız yaklaşıma göre tekrar vakti geldiğinde oluşturulur.

TTL’inizi ve uygulayacağınız pattern’i belirlemenizde iki şey size yardımcı olaraktır.

  • Orijinal veri kaynağınızdaki verinin değişim hızı.
  • Cache’de atıl kalan verinin application’a yapacağı etki.

Örnek olarak, static yada referans olarak kullanılan verilerin uzun TTL değerlerine sahip olup, write-through olarak güncellenmesi mantıklı olabilir.

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton

Dinamik olarak sürekli değişen veriler için ise, kısa TTL’ler verilip orijinal kaynaktaki ortalama değişim süresi ile paralel giderek güncel olmayan bir veriyi dönme riskini minimize etmiş oluruz.

TTL uygularken diğer bir best practice ise, TTL’lerinize bir sapma değeri eklemektir. Bu şekilde cache expire olduktan sonra veri kaynağına ani yüklenmeleri engelleyebilirsiniz. Ek olarak cache servisinizin TTL’leri takip edip, değerleri silerken yüksek yük altında kalmasınada engel olabilirsiniz.

Fallbacks

Cache aside modelini kullanırken cache servisiniz fault durumda ise, sürekli olarak cache miss olarak çalışacaktır. Cache’den gerekli veriyi almadığınızda veri kaynağına fall back yaparak veriyi alabilirsiniz.

Tabi ki exception, timeout vb. şeyleri handle etmeniz gerekmekte. İşler burada karışıyor biraz;

Normalde cache tarafından cevaplanan yoğun bir request yükü altında kaldığınızda, cache servisini fault durumda kalırsa, bütün request’leriniz veri kaynağına fallback olacaktır. Yani muhtemelen karşılayamayacağı bir yük altında kalacaktır.

Cache’i sisteminize eklemek oldukça kolay ve hızlıda olsa, sisteminize kompleksite katacağı ve ilgilenmeniz gereken ayrı noktalar yaratacağını göz ardı etmemeniz gerekmektedir. Bunlar kimi zaman teknik, kimi zaman kaynak tüketimi ile ilgili olacaktır. Konunun bu kısmı ile ilgili ise, aşağıda biri Netflix, biri Facebook’dan olmak üzere iki video bırakıyorum.

--

--