Miras uygulamalarda performans iyileştirmeleri üzerine örnekler

Deniz Kutval
SabancıDx
Published in
7 min readDec 14, 2021

Herkese merhabalar.

Yazılım geliştiriciler olarak, “legacy (miras)” dediğimiz, hayatımızın bir döneminde büyüklerimizden bize miras kalan, eski zamanların vizyonu (bazen vizyonsuzluğu) ve teknolojileriyle yoğurulmuş, bu günlere kadar direnebilmiş, zaman zaman saç baş yolduran uygulamalar üzerinde çalışmamız gerekebiliyor.

Birçoğumuz bunu çoktan yaşamıştır ve yazılım geliştirme sektörüne yeni dahil olan arkadaşlarımızın da özellikle kurumsal hayatta böyle bir ortamı soluması kuvvetle muhtemeldir.

Günümüz dünyasında örneğin ortalama 10 yıl önce yazılmış bir uygulamayı düşünürsek; projenin oluşturulmasından bu güne, o uygulama için kod yazan developer sayısı işe giriş / çıkışlar sebebiyle oldukça fazla diyebiliriz. Çoğu zaman bu projelerin bir kod standardı, sonradan geliştirmeye uygun bir mimarisi veya business işleyişini güzelce anlatan bir dokümantasyonu da olmuyor. Hâl böyle olunca her yerde farklı yollar izlenmiş, bir sayfadan diğerine copy-paste ile birtakım elementler alınmış veya ihtiyaç duyulan sadece bir tek kolon olmasına rağmen, veritabanından bütün bir tablo çekilmiş olabiliyor. Bir çok kişinin “freestyle” kod yazdığı, copy-paste edip arkasına bakmadan iş bitirdiği, performans veya kalite testlerinin yapılmadığı uygulamalarda, kullanıcı sayısı arttıkça performans sorunlarının yaşanması gayet normaldir.

Bu gibi durumlarda zaten uygulama bir noktadan sonra “beni hafifletmeniz gerekiyor” diye bağırmaya başlayacaktır. Şansınız varsa, bu uyarıları çok kritik işlerin dönmediği zamanlarda farkedersiniz. Fakat genellikle uygulama üzerindeki trafik çok yoğunken ve bazı kritik işlemler yapılırken alırsınız ve kara kara düşünmeye başlarsınız.

Yukarıda özetlemeye çalıştığıma benzer miras uygulamalar özelinde yaşanan problemleri çözmek için, gerçek bir ekip çalışmasıyla oldukça detaylı bir şekilde durumun analiz edilmesi gerekiyor. Bu yazıda, kurtarma çalışmalarında doğru karaların alınabilmesi için nelerin kontrol edilmesi gerektiğine biraz değineceğim ve temel hatlarıyla bu adımları özetleyip, izlenmesi gereken yolları anlatmaya çalışacağım.

İhtiyaç listesi:

  1. Sabırlı ve dikkatli developerlar
  2. Kalite (QA) uzmanları
  3. Gerekli durumlarda bilgi verebilecek ve danışılabilecek bir veritabanı uzmanı (DBA)
  4. Uygulamanın business kurallarına ve kullanımına hakim bir analist

Aşağıdaki başlıklarda anlık yavaşlık krizleri yaşanmaya başlamış ve tekrar eder hâle gelmiş olan bir uygulama için yapılabilecekleri özetlemeye çalıştım. Başlamadan önce not olsun; bu sorunlarla karşılaştığım uygulamalar çoğunlukla Asp.Net ve MSSQL teknolojileri ile yazıldığı için örneklerim de bunlar üzerinden olacak.

İlk belirlenmesi gereken: Sorun ne ve nerede?

Başlangıç noktasında ilk yapılması gereken, sorunun kaynağını mümkün olduğu kadar netleştirmek ve alanı daraltmak. Örneğin bir mesajlaşma uygulamasını ele alacak olursak; “kullanıcının mesaj gönderdiği esnada mı hata / yavaşlık oluşuyor yoksa gönderim işlemi sonrasında devreye giren başka bir süreç mi bu hataya / yavaşlığa sebep oluyor” gibi senaryoları derinlemesine incelemek gerekiyor. Bu adımı ne kadar sıkı ve sağlıklı yaparsak, kaybedeceğimiz vakit o kadar azalıyor. Bu aşama sonrasında, “sorun şu sayfadaki butona basıldığında oluşuyor” gibi bir çıkarım bile yapabilmiş olmak oldukça değerli.

Yavaşlığın sebebi ne olabilir?

Çok uzun zaman boyunca, farklı developerlar tarafından geliştirme yapılmış uygulamalarda maalesef ki her şey mümkün. Bunları başlıca birkaç maddede toplamak istersek;

  1. Tekrar eden veritabanı işlemleri (aynı veriyi birden fazla defa çekmek)
  2. Veri tabanından çekilen fakat hiçbir zaman kullanılmayan veriler (saçma gelebilir ama bu gözler bunu da gördü.)
  3. Performans kaygısı olmadan yazılmış kodlar
  4. Veritabanındaki bir tabloda bulunan tek bir kolonun verisini kullanmak için bütün tablo verisini çekmek
  5. Veritabanı tablo indexlerinin yetersizliği veya yanlışlığı
  6. Cache üzerinde tutulabilir olan verilerin her seferinde veritabanından alınmaya çalışılması
  7. Asenkron ilerleyebilecek süreçlerin senkron olarak yürütülmesi
  8. Veritabanı sorgularında gereksiz kullanılan join’ler
  9. Veritabanında update’ler sebebiyle oluşabilecek deadlock’lar

ve şu an aklıma gelmeyen onlarcası şeklinde özetleyebiliriz.

Yukarıda saydığım maddelerin çok büyük bir kısmı doğrudan veritabanı ile bağlantısı bulunan konular.

Özet olarak; “veritabanından verileri çekerken mümkün olduğunca dikkatli olun” diyerek bu yazıyı sonlandırabilirim. 😊 Fakat biraz daha detaya gireceğim.

Madde madde özetleyeyim:

1) Tekrar eden veritabanı işlemleri (aynı veriyi birden fazla defa çekmek)

Örneğin User adında bir objeniz var ve kullandığınız bir metodun içerisinde, veritabanındaki User tablosundan tüm veriyi alıp bir liste oluşturuyorsunuz. Sonra da bu liste üzerindeki datayı kullanarak bazı business işlemleri yürütüyorsunuz.

Sonra bu metodun içerisinde, başka bir metot çağırıyorsunuz ve o da ne! Aynı User listesi bu metodun içerisinde de veritabanından çekilmiş.

Bu liste 10 elemanlı bir liste olduğunda performans anlamında çok fazla göze batan bir durum söz konusu olmayabilir. Fakat bir süre sonra bu liste 10000 elemanlı olmaya başladığında canınız yanabilir.

Eğer bu listeleri global bir yerde tutabiliyorsanız (class özelinde veya uygulama genelinde olabilir) bu şekilde tutarak aynı veriyi kullanabilirsiniz. Her senaryoyu karşılayacak bir çözüm değil tabii ki, fakat denenebilir.

2) Veri tabanından çekilen fakat hiçbir zaman kullanılmayan veriler

Yine ana metotlarınızdan birisinde zamanında yazılmış, kullanılmış bir User listesi var. Fakat zaman içerisinde bu listeyle ilgili olan business silinmiş ve artık bu listenin sizin için bir önemi yok. Fakat metodun bir yerinde veritabanına gidip, hiç ihtiyacınız olmayan bu listeyi çekmeye devam ediyorsunuz.

Bu gibi durumlarla karşılaşmamak için, herhangi bir değişiklik sonucu boşa çıkan değişkenlerin olup olmadığını düzgünce kontrol etmek gerekir.

3) Performans kaygısı olmadan yazılmış kodlar

Performanssız kod konusuna çok fazla örnek verilebilir. Sorun iç içe yazılmış birçok loop barındıran bir kod da olabilir, bilinçsizce kullanılmış bir LINQ sorgusu da olabilir. For döngüsüyle 1000 elemanlı bir listenin içerisinde veritabanına tek tek her eleman için gidip sorgu atmak da olabilir, EntityFramework’ün (veya başka bir ORM aracının) yanlış kullanımı da. Bu sebeple spesifik bir örnek vermeyi ve bakış açınızı sınırlamayı pek uygun görmüyorum. Bu gibi durumların önüne geçmek için, yazdığınız kodu gerçekten yük altında bırakarak test etmeniz. Eğer yüklü bir veriyle bu testi yaparsanız, performansının kabul edilebilir olup olmadığına önden karar verip gerekli önlemleri alabilirsiniz.

4) Veritabanındaki bir tabloda bulunan tek bir kolonun verisini kullanmak için bütün tablo verisini çekmek

Yine User listesi üzerinden örnek vereceğim. Diyelim ki zamanın birinde tüm kullanıcıların ad ve soyad bilgilerine ihtiyaç varmış ve bu veri, veritabanından çekilmiş. Bir loop içerisinde dönerek bunlar kullanılmış ve ilgili business işletilmiş. Bu şekilde anlatınca sanki bir sorun yok gibi görnüyor. Kodu incelerken de, bu sorun ilk bakışta fark edilecek bir sorun değil. Bu sebeple verinin çekildiği noktaları, atılan sorguları detaylıca incelemek gerekiyor. 40 kolonlu ve 10000 elemanlı User tablosuna bir sorgu atıp, gelen verinin içerisinden sadece ad ve soyad bilgisinin kullanılması senaryosu en çok karşılaştığım senaryolardan birisi. Uygulamanın veritabanına attığı sorguyu, ihtiyaç duyulan alanlarla sınırlamak; yani “Select * from User” yerine “Select Name, Surname from User” sorgusunu kullanmak ve gereksiz veriyi trafiğe dahil etmemek uygulamayı rahatlatmasının yanında gereksiz network trafiğinin de önüne geçecektir.

Kimi uygulamalarda kodun içerisinde doğrudan veritabanı sorgusunu bulabilirsiniz. Kimisinde bir stored procedure çağrılıyor olabilir. Kimisinde de EntityFramework ile sorgu hazırlanıyor olabilir. Bu sebeple, eğer mümkünse bir profiler ile veritabanına atılan sorguyu tüm çıplaklığıyla görmeniz ve yorumlamanız en iyi seçenek olacaktır.

5) Veritabanında tablo indexlerinin yetersizliği veya yanlışlığı

4. madde üzerinden devam edeyim: Profiler ile veya uygulama içerisinde gördüğünüz sorgunuzu Sql Server Management Studio üzerine alıp, “display estimated execution plan” butonu ile çalıştırırsanız, hangi indexlerin kullanıldığını görebilirsiniz. (Bu konuyla ilgili detaylı örnekleri ve anlatımları internetten rahatlıkla bulabilirsiniz.) Ek bir seçenek olarak eğer size yardımcı olabilecek bir veritabanı uzmanı var ise, sorguyu onunla paylaşıp, gerekli indexleri çıkarmasını isteyebilirsiniz. Performans söz konusu olduğunda indexlerin tüm veritabanları için hayati bir önemi var. Örneğin MsSql değil de MongoDb kullanıyorsanız, her yanlış oluşturulan veya hiç oluşturulmayan index CPU’nun gereğinden fazla kullanılmasına, hatta %100’e ulaşmasına sebep olabilir. Bu yüzden bir uzman eşliğinde bu kontrolleri yapmak ve eksikleri tamamlamak, kırıkları onarmak oldukça önemli.

6) Cache üzerinde tutulabilir olan verilerin her seferinde veritabanından alınmaya çalışılması

Neredeyse hiç değişmeyen bir veri kümesi düşünün. Örneğin “il/ilçe” bilgileri. Çoğu uygulama için bir kere sisteme girilen, sonrasında uzun bir süre, hatta belki hiçbir zaman güncelleme yapılmayan bir veriden bahsediyorum. (İl/ilçe bilgisinin kritik olduğu uygulamaları hariç tutuyorum.) Bu veriyi her seferinde gidip veritabanından toplu halde almak ya da önyüzden gönderilen değer bu listede var mı yok mu gibi kontrolleri yapmak gerçek bir performans kaybı olur. Bu gibi durumlarda eğer tek bir sunucuda çalışan ve scale ihtiyacı olmayan bir uygulamaysa, bu veriyi bir sefer çekip memory’de tutarak veya dağıtık mimariye sahip bir uygulamadan bahsediyorsak Redis gibi bir cache mekanizmasında tutarak veritabanı trafiğini azaltabilir ve performans kazanabiliriz.

7) Asenkron ilerleyebilecek süreçlerin senkron olarak yürütülmesi

Bu, eski uygulamalarda çokça rastlanan bir durum. Zira çok eski kodların içerisinde asenkron programlamaya dair çok fazla bir şey bulmak mümkün olmuyor. Uygulamada kullanılan framework versiyonundan, uygulamanın tasarımına kadar bir çok konu asenkron işlemler yapmanıza engel olabilir.

Fakat eğer mümkünse, kullanıcıyı o anlık yaptığı işlem esnasında hiç ilgilendirmeyen (örneğin kullanıcının başka bir kullanıcıya uygulama içi mesaj göndermesinden sonra mail atılması gibi) adımları asenkron olarak işletmeyi deneyebilirsiniz. Böylelikle uygulamada kullanıcıya yansıyan bekleme süreleri azalacaktır. Tabii bu örneği eski uygulamalarda yapılabilecek düzenlemeler olarak veriyorum. Günümüz teknolojileriyle yeni uygulamalar yazıyorsanız, bunlar için çok daha güzel çözümler mevcut.

8) Veritabanı sorgularında gereksiz kullanılan join’ler

Bu sorun da yine zamanında yazılan ve sonradan değiştirilen sorgular neticesinde ortaya çıkıyor. Örnek olarak; günün birinde biri, ihtiyaç duyduğu Address tablosunu User ile joinlemiş ve User verilerini bu şekilde almış, bu verileri de bir şekilde uygulama içerisinde kullanmış olsun. Uygulama üzerinde adres verileriyle bağlantılı olan business işletilmekten vazgeçildiğinde ve o kod bloğu silindiğinde, yine 2. maddede olduğu gibi elimizde fazladan parça kalıyor. Address verilerine hiç ihtiyaç duymamama rağmen, bu veri sorgumun içerisinde duruyor. Bu durumda da yine hangi verinin kullanılıp hangisinin kullanılmadığı net bir şekilde belirlenmeli ve sorgular buna göre tekrar düzenlenmeli.

9) Veritabanında update’ler sebebiyle oluşabilecek deadlock’lar

Bu konu da oldukça kritik. Bir tablo üzerinde birden fazla işlem yapıldığında, veritabanınızın ISOLATION LEVEL ayarına göre çok farklı tepkiler alabilirsiniz. Bir izolasyon seviyesinde, çekmeye çalıştığınız veri üzerinde işlem yapılıyorsa inanılmaz bekleme sürelerine ulaşabilir, bir diğerinde sürekli kirli veriyle karşı karşıya kalabilirsiniz. Burada da yine bir veritabanı uzmanından yardım alarak performans iyileştirmeleri sağlayabilirsiniz. Veritabanı uzmanınız size hangi tablolarda, hangi stored procedure’ler çalıştırılırken deadlock oluştuğunu söyleyebilir ve bu bilgiye dayanarak bazı düzenlemeler yapabilirsiniz. Veritabanı uzmanınız yoksa da, yeterince ararsanız deadlock’ları görüntüleyebilmek için gerekli scriptleri bulabilirsiniz ve isolasyon seviyeleri hakkında bilgi sahibi olup, sorgularınızı çok daha performanslı hale getirebilirsiniz. Fakat bir daha uyarmayı gerekli görüyorum; izolasyon seviyesini değiştirdiğinizde performans kazandığınızı sanıp, henüz bulunduğu transaction tamamlanmamış olan bir veriyi bambaşka bir şekilde görebilir ve çok başka sorunlara yol açabilirsiniz. Bir kere daha uyarırdım, ama çok uzatmayayım 😊

Son olarak; en az yukarıda saydığım maddeler kadar önemli bir konu daha var:

Kalite (QA) uzmanları, süreç boyunca yaptığınız iyileştirmeleri test etmek üzere, sisteminizi siz kodlarda değişiklik yaptıkça yük altına alarak kontrol etmeliler. Bu sayede yaptığınız değişikliklerin performansa olan etkilerini kısa süre içerisinde görebilirsiniz. Eğer performansta herhangi bir değişiklik yoksa da sorunu yanlış yerde aradığınızı anlayıp bakış açınızı değiştirebilirsiniz. Otomasyonlarla, manuel olarak yaptığınız testlerden çok daha kısa sürede sonuç alabilir, bu sonuçları raporlayabilir ve ciddi boyutta zaman kazanabilirsiniz.

Yukarıdaki senaryolarla hiç karşılaşmamanız dileğiyle… 😊

--

--