Önbelleksizlik kötü modeli

Anti-desenler, yazılımınızı veya uygulamalarınızı stres durumları altında bozabilen ve göz ardı edilmemesi gereken yaygın tasarım kusurlarıdır. Birçok eşzamanlı isteği işleyen ve aynı verileri tekrar tekrar getiren bir bulut uygulaması olduğunda önbelleğe alma kötü amaçlı bir kötü model oluşur. Bu, performansı ve ölçeklenebilirliği azaltabilir.

Veriler önbelleğe alınmazsa aşağıdakiler gibi istenmeyen bazı davranışlar ortaya çıkabilir:

  • G/Ç ek yükü veya gecikme süresi açısından erişilmesi pahalı bir kaynaktan aynı bilgileri tekrar tekrar getirme.
  • Birden çok istek için tekrar tekrar aynı nesneleri veya veri yapılarını oluşturma.
  • Hizmet kotası olan ve belirli bir sınırı aşan istemcileri kısıtlayan bir uzak hizmete aşırı sayıda çağrı yapma.

Bu sorunlar uzun yanıt sürelerine, veri deposunda çekişmenin artmasına ve ölçeklenebilirliğin düşmesine yol açabilir.

Önbelleğe alınmayan kötü model örnekleri

Aşağıdaki örnekte, Entity Framework kullanarak bir veritabanına bağlanılmaktadır. Birden çok istek tam olarak aynı verileri getiriyor olsa bile her istemci isteği için veritabanına bir çağrı yapılır. Tekrarlanan isteklerin G/Ç ek yükü ve veri erişimi ücretleri açısından maliyeti birikerek hızla artabilir.

public class PersonRepository : IPersonRepository
{
    public async Task<Person> GetAsync(int id)
    {
        using (var context = new AdventureWorksContext())
        {
            return await context.People
                .Where(p => p.Id == id)
                .FirstOrDefaultAsync()
                .ConfigureAwait(false);
        }
    }
}

Örneğin tamamını burada bulabilirsiniz.

Bu kötü model genellikle aşağıdaki nedenlerden ortaya çıkar:

  • Önbellek kullanılmamasını uygulamak daha kolaydır ve yükler düşük olduğunda sorunsuz çalışır. Önbelleğe alma kodu daha karmaşık hale getirir.
  • Önbellek kullanmanın avantajları ve dezavantajları net olarak anlaşılmamıştır.
  • Önbelleğe alınan verilerin doğruluk ve güncelliklerini korumaktan doğacak ek yük konusunda endişeler vardır.
  • Uygulama, ağdaki gecikme sürelerinin sorun olmadığı ve performansı yüksek, pahalı donanımlar üzerinde çalıştırılan şirket içi bir sistemden geçirilmiştir. Bu nedenle, önbelleğe alma özgün tasarımda dikkate alınmamıştır.
  • Geliştiriciler, belli bir senaryoda önbelleğe alma olanaklarının bulunduğunun farkında değildir. Örneğin, geliştiriciler bir web API'si uygularken ETag'leri kullanmayı akıl edemeyebilir.

Önbelleğe alma kötü amaçlı olmayan kötü modeli düzeltme

En popüler önbelleğe alma stratejisi, isteğe bağlı veya edilgen önbellek stratejisidir.

  • Uygulama, okuma sırasında verileri önbellekten okumaya çalışır. Veriler önbellekte değilse uygulama bunları veri kaynağından alır ve önbelleğe ekler.
  • Uygulama, yazma sırasında değişikliği doğrudan veri kaynağına yazar ve eski değeri önbellekten kaldırır. Bir daha gerekli olduğunda bu veri kaynaktan alınır ve önbelleğe eklenir.

Bu yaklaşım, sık sık değişen veriler için uygundur. Önceki örnek Edilgen Önbellek düzenini kullanacak şekilde güncelleştirilmiş haliyle aşağıdadır.

public class CachedPersonRepository : IPersonRepository
{
    private readonly PersonRepository _innerRepository;

    public CachedPersonRepository(PersonRepository innerRepository)
    {
        _innerRepository = innerRepository;
    }

    public async Task<Person> GetAsync(int id)
    {
        return await CacheService.GetAsync<Person>("p:" + id, () => _innerRepository.GetAsync(id)).ConfigureAwait(false);
    }
}

public class CacheService
{
    private static ConnectionMultiplexer _connection;

    public static async Task<T> GetAsync<T>(string key, Func<Task<T>> loadCache, double expirationTimeInMinutes)
    {
        IDatabase cache = Connection.GetDatabase();
        T value = await GetAsync<T>(cache, key).ConfigureAwait(false);
        if (value == null)
        {
            // Value was not found in the cache. Call the lambda to get the value from the database.
            value = await loadCache().ConfigureAwait(false);
            if (value != null)
            {
                // Add the value to the cache.
                await SetAsync(cache, key, value, expirationTimeInMinutes).ConfigureAwait(false);
            }
        }
        return value;
    }
}

GetAsync yönteminin artık doğrudan veritabanını çağırmak yerine CacheService sınıfını çağırdığına dikkat edin. CacheService sınıfı, öğeyi ilk olarak Redis için Azure Cache’ten almaya çalışır. Değer, önbellekte bulunamazsa CacheService kendisine çağıran tarafından geçirilmiş olan bir lambda işlevini çağırır. Lambda işlevi, verileri veritabanından getirmekten sorumludur. Bu uygulama, depo ile kullanılan önbellek çözümünü ve CacheService ile veritabanını birbirinden ayırır.

Önbelleğe alma stratejisiyle ilgili dikkat edilmesi gerekenler

  • Belki de geçici bir hata nedeniyle önbellek kullanılamıyorsa istemciye hata döndürmeyin. Bunun yerine, verileri özgün veri kaynağından getirin. Ancak, önbellek kurtarılırken özgün veri deposuna çok sayıda istek gidebileceğini, bu yüzden zaman aşımları ve bağlantı hataları yaşanabileceğini unutmayın. (Sonuçta, bu ilk etapta önbellek kullanmanın motivasyonlarından biridir.) Veri kaynağının aşırıya kaçmasını önlemek için Devre Kesici düzeni gibi bir teknik kullanın.

  • Dinamik verileri önbelleğe alan uygulamalar, nihai tutarlılığı destekleyecek şekilde tasarlanmalıdır.

  • Web API'leri için, istek ve yanıt iletilerine bir Cache-Control üst bilgisi ekleyerek ve ETag'ler aracılığıyla nesne sürümlerini tanımlayarak istemci tarafında önbelleğe almayı destekleyebilirsiniz. Daha fazla bilgi için bkz. API uygulaması.

  • Varlıkların tamamını önbelleğe almanız gerekmez. Varlığın büyük bir kısmı statikse ve yalnızca küçük bir parçası sık sık değişiyorsa statik öğeleri önbelleğe alın ve dinamik öğeleri veri kaynağından alın. Bu yaklaşım, veri kaynağına yönelik olarak gerçekleştirilen G/Ç hacminin azaltılmasına yardımcı olabilir.

  • Bazı durumlarda, geçici veriler kısa süreli ise bunları önbelleğe almak kullanışlı olabilir. Örneğin, sürekli olarak durum güncelleştirmeleri gönderen bir cihaz düşünelim. Bu bilgileri gelir gelmez önbelleğe almak ve asla kalıcı bir depoya yazmamak mantıklı olabilir.

  • Birçok önbellek çözümü, verilerin eskimesini engellemek için yapılandırılabilir sona erme sürelerini destekler. Böylece veriler, belirtilen bir sürenin ardından önbellekten otomatik olarak kaldırılır. Sona erme süresini senaryonuza uygun olarak ayarlamanız gerekebilir. Son derece statik olan veriler, daha hızlı eskiyebilecek olan geçici verilere göre daha uzun süre önbellekte kalabilir.

  • Önbelleğe alma çözümü yerleşik bir sona erme özelliği sağlamıyorsa önbelleğin sınırsızca büyümesini önlemek için, ara sıra önbelleği temizleyen bir arka plan işlemi uygulamanız gerekebilir.

  • Dış veri kaynaklarından gelen verileri önbelleğe almanın yanı sıra karmaşık hesaplamaların sonuçlarını kaydetmek için de önbelleğe alma işlemini kullanabilirsiniz. Ancak, bunu yapmadan önce uygulamanın gerçekten CPU tarafından sınırlanıp sınırlanmadığını belirlemek için uygulamayı izleyin.

  • Uygulama başlatıldığında önbelleği hazırlamak faydalı olabilir. Önbelleğe kullanılma olasılığı en yüksek olan verileri doldurun.

  • Her zaman, istenen verilerin önbellekte bulunduğu ve bulunmadığı durumları algılayan izleme işlevi ekleyin. Bu bilgilerden yararlanarak hangi verilerin önbelleğe alınacağı, verilerin süresi dolmadan önce ne kadar süre önbellekte tutulacağı gibi önbelleğe alma ilkelerini ayarlayın.

  • Önbelleğe alma özelliğinin olmaması bir performans sorunu oluşturuyorsa, önbelleğe almanın eklenmesi de istek hacmini web ön ucunun aşırı yüklenmesine neden olacak kadar artırabilir. İstemciler, HTTP 503 (Hizmet Kullanılamıyor) hataları almaya başlayabilir. Bu durum, ön ucun ölçeğinin genişletilmesi gerektiğine işaret eder.

Önbelleğe alınmayan bir kötü modeli algılama

Önbelleğe alma özelliği olmamasının performans sorunlarına neden olup olmadığını belirlemenize yardımcı olması için aşağıdaki adımları gerçekleştirebilirsiniz:

  1. Uygulama tasarımını gözden geçirin. Uygulamanın kullandığı tüm veri depolarının bir envanterini alın. Her bir veri deposu için, uygulamanın önbellek kullanıp kullanmadığını belirleyin. Mümkünse verilerin ne sıklıkta değiştiğini belirleyin. Önbelleğe alma konusunda ilk olarak düşünülebilecek adaylar, sık sık değişmeyen veriler ve sık okunan statik başvuru verileridir.

  2. Uygulamayı ve canlı sistemi izleyerek uygulamanın ne sıklıkta veri aldığını veya bilgi hesapladığını öğrenin.

  3. Bir test ortamında uygulamanın profilini oluşturarak veri erişim işlemlerinin veya sık yapılan diğer hesaplamaların getirdiği ek yükle ilgili alt düzey ölçümleri yakalayın.

  4. Bir test ortamında yük testi gerçekleştirerek sistemin normal bir iş yükü ve ağır yük altında nasıl yanıt verdiğini belirleyin. Yük testi, gerçekçi iş yükleri kullanarak üretim ortamında gözlenen veri erişimi kalıplarının benzetimini yapmalıdır.

  5. Temel veri depolarının veri erişim istatistiklerini inceleyin ve aynı veri isteklerinin ne sıklıkta tekrarlandığını gözden geçirin.

Örnek tanılama

Aşağıdaki bölümlerde, bu adımlar yukarıda açıklanan örnek uygulamaya uygulanmaktadır.

Uygulamayı ve canlı sistemi izleme

Uygulamayı izleyerek uygulama üretimdeyken kullanıcıların yaptığı belirli istekler hakkında bilgi edinin.

Aşağıdaki resimde, bir yük testi sırasında New Relic tarafından yakalanan izleme verileri gösterilmektedir. Bu durumda gerçekleştirilen tek HTTP GET işlemi, Person/GetAsync işlemidir. Ancak canlı bir üretim ortamında her bir isteğin hangi sıklıkta yapıldığının bilinmesi, hangi kaynakların önbelleğe alınması gerektiği hakkında size bir fikir verebilir.

New Relic showing server requests for the CachingDemo application

Daha ayrıntılı bir analiz gerekiyorsa bir test ortamında (üretim sisteminde değil) alt düzey performans verilerini yakalamak için bir profil oluşturucu kullanabilirsiniz. G/Ç istek hızları, bellek kullanımı ve CPU kullanımı gibi ölçümlere bakın. Bu ölçümler, bir veri deposu veya hizmete çok sayıda istek yapıldığını ya da aynı hesaplamayı gerçekleştiren yinelenen işlemler olduğunu gösterebilir.

Uygulamaya yük testi uygulama

Aşağıdaki grafikte, örnek uygulamanın yük testinin sonuçları gösterilmektedir. Yük testi, tipik bir dizi işlem gerçekleştiren 800 adede kadar kullanıcının aşamalı artan yükünün benzetimini yapmaktadır.

Performance load test results for the uncached scenario

Saniye başına gerçekleştirilen başarılı testlerin sayısı bir platoya ulaşmakta ve bunun sonucunda ek istekler yavaşlamaktadır. Ortalama test süresi, iş yükü arttıkça düzenli olarak artmaktadır. Kullanıcı yükü en üst noktaya ulaştıktan sonra yanıt süresi sabit bir hal almaktadır.

Veri erişim istatistiklerini inceleme

Bir veri deposu tarafından sunulan veri erişim istatistikleri ve diğer bilgiler, hangi sorguların en sık yinelendiği gibi yararlı bilgiler sağlayabilir. Örneğin, Microsoft SQL Server'daki sys.dm_exec_query_stats yönetim görünümünde yakın zamanda yürütülen sorgulara ilişkin istatistiksel bilgiler vardır. Her sorgunun metni sys.dm_exec-query_plan görünümünde bulunabilir. SQL Server Management Studio gibi bir araç kullanıp aşağıdaki SQL sorgusunu çalıştırarak sorguların ne sıklıkta gerçekleştirildiğini belirleyebilirsiniz.

SELECT UseCounts, Text, Query_Plan
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)

Sonuçlardaki UseCount sütunu her bir sorgunun ne sıklıkta çalıştırıldığını gösterir. Aşağıdaki resimde, üçüncü sorgunun diğer tüm sorgulardan çok daha fazla şekilde, 250.000 kereden fazla çalıştırıldığı gösterilmektedir.

Results of querying the dynamic management views in SQL Server Management Server

Bu denli çok sayıda veritabanı isteğine neden olan SQL sorgusu şudur:

(@p__linq__0 int)SELECT TOP (2)
[Extent1].[BusinessEntityId] AS [BusinessEntityId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [Person].[Person] AS [Extent1]
WHERE [Extent1].[BusinessEntityId] = @p__linq__0

Bu sorgu, yukarıda gösterilen GetByIdAsync yönteminde Entity Framework tarafından oluşturulur.

Önbellek stratejisi çözümünü uygulama ve sonucu doğrulama

Bir önbellek ekledikten sonra yük testlerini yineleyin ve sonuçları önbellek olmadan gerçekleştirilen önceki yük testleriyle karşılaştırın. Örnek uygulamaya bir önbellek ekledikten sonra yapılan yük testlerinin sonuçları aşağıdadır.

Performance load test results for the cached scenario

Başarılı testlerin hacmi yine bir platoya ulaşıyor, ancak bu plato daha yüksek bir kullanıcı yükünde gerçekleşiyor. Bu yükteki istek hızı önceki duruma göre çok daha yüksektir. Ortalama test süresi yükle birlikte yine de artar, ancak en fazla yanıt süresi 0,05 ms'dir ve önceki 1 ms ile karşılaştırıldığında 20× iyileştirmedir.