
Performance Tracking On EF Core With Easy Profiler (Finally 😌)
Profesyonel kariyer hayatımda hemen hemen her zorlandığımda aklıma gelen, Mumin Sekman’ın TedX İzmir’de ki konuşmasında geçen cümlelerden bir tanesi ile bu yazıya başlamak istiyorum. Hazır mısınız? İşte geliyooor. 🧐🔥
“Başarının bedelini bir dönem için ödemeyenler, başaramamanın bedelini bir ömür boyu öderler.”
Bilişim Teknolojileri alanında görev alan biz geliştiriciler için başarı hemen hemen her görevimizde karşımıza çıkan bir parametre. Basit bir örnek vermek gerekirse, bir task üzerinde çalışırken bile başarı kriterlerini belirliyor ve onları yerine getirmeye çalışıyoruz. Yazının başlığında “finally” yazmasının bir sebebi var. Hemen hemen bir yıl önce geliştirdiğim ve üzerinde çok sayıda başarı kriterini yerine getirdiğim Easy Profiler
isimli kütüphanemin bir yıl sonra makalesini yazıyor olmak aslında bir başarısızlık. Open Source uygulamalar veya kütüphaneler geliştirmek biz geliştiriciler için önemli bir başarı ancak mevcut iş yoğunluğundan bu yapılara vakit ayıramamak ve yeni özellikleri kazandıramamak da aynı oranla bir başarısızlık göstergesi. Çok uzun bir girizgah olduğunun farkındayım. Daha fazla canınızı sıkmadan EF Core ile beraber sorgularamızın performansını nasıl izleyeceğimizi ve bunun yanı sıra uygulama içerisinde en yavaş ve en hızlı endpointlerimizi Easy Profiler
yardımıyla nasıl bulabileceğimizi incelemeye başlayalım. 🧐

“This is Sparta!!!” unutulmaz bir replik benim için. Özellikle backend ekosisteminde uygulama geliştiren bir geliştiriciyseniz şu cümleleri duyduğunuza eminim.
“xxx isimli endpointin cevap vermesi için x saniye veya milisaniye süre geçmesi gerekiyor. Burada bir sorun var! Bu endpointin daha hızlı cevap vermesi gerekiyor.”
Bu cümleleri duyduğumda kendimi tekme atan değil tekme yiyen adam gibi hissediyorum.
Örneğin getAllCustomer
isimli bir endpointe sahip olduğumuzu ve bu endpointin cevap vermesi için 2 saniye geçmesi gerektiğini düşünelim.
2 saniye, gerçekten çok uzun bir süre. Sadece son kullanıcı için değil, geliştirici için de çok uzun ve çok karmaşık bir süre.
Son kullanıcı veya testi gerçekleştiren takım arkadışınız neden 2 saniye sonra cevap verdiğini anlamaya çalışırken biz geliştiriciler neden 2 saniye içinde cevap veremediğini anlayamaya çalışıyoruz.
Temel anlamda ikisi de aynı soru gibi gözükebilir. Ancak biz geliştiriciler genelde tekmeyi yiyip o sonsuz çukara düşüyoruz. Tekmeyi atan ise genelde son kullanıcılar oluyor.

Bize tekmeyi atan Ayşe teyze uygulama içerisinde hangi adımlardan geçti kısaca özetlemek için çizdiğim sembolik resim üzerinden ilerleyelim.
- Ayşe teyzemiz bir request atıyor
- Bu istek uygulamamızın çalıştığı sunucuya geliyor
- Sonucun verilmesi için bir pipeline ayaklanıyor
- İstek bu pipeline içerisinde authentication, authorization ve tanımlanan başka adımlardan geçiyor
- İstenilen cevap kullandığımız herhangi bir cache provider üzerinde var mı diye kontrol ediliyor
- Sonuç varsa Ayşe teyze sonucunu alıyor ve dua ediyor 🤲🙏
- Eğer sonuç cache üzerinde yoksa relational veya document bir veri tabanı üzerinden sonuç işleniyor ve Ayşe teyzeye gösteriliyor
Bu adımlar artırılabilir veya azaltılabilir ama günün sonunda Ayşe teyze bize tekmeyi atıyor ve biz bu adımlar üzerinde nerelerde sorun olabileceğini çaresizce araştırırken o sonsuz çukura düşüyoruz.
Şimdi hep beraber Ayşe teyzenin yol haritasında nerelerde sorun olabileceğini inceleyelim;
- Ayşe teyze dış güçlerin bir ajanı olabilir ve bir request atmak yerine binlerce request atıp DDoS (Denial-of-service attack) başlatmış olabilir. Bu senaryoda sunucumuzun anlık olarak cevap verebileceği limitler kontrol edilmeli ve sunucumuzun anlık durumu kontrol edilmeli. (CPU, RAM kullanımı vb.)
- Request-Response arasında çalışan pipeline itemların veya bir diğer adıyla middleware olarak tanımladığımız kod blokları uzun sürüyor olabilir.
- Authentication için kullandığımız servis Ayşe teyzenin authenticate olup olmadığını kontrol ederken cevap vermekte zorlanıyor olabilir.
- Cache için kullandığımız sağlayıcıya (memory cache, redis vb.) bağlanırken veya cache üzerindeki veriler kontrol edilirken çok fazla zaman harcıyor olabiliriz.
- Relational veya document veri tabanına bağlanırken bir network problemi yaşıyor olabiliriz.
- Ayşe teyzenin istediği sonuç kullandığımız veri tabanı üzerinde hesaplanırken zaman harcıyor olabiliriz. (Index, View function, Stored Procedure)
- Veri tabanı üzerinden alınan sonucu uygulamamız içerisinde anlamlandırırken zaman harcıyor olabiliriz.
- Serialization and Deserialization gibi işlemleri gerçekleştirirken Ayşe teyzeyi uzun süre bekletiyor olabiliriz.
Yukarıda bahsettiğim bu sorunlar artırılabilir veya azaltılabilir. Gözlemlerim ve deneyimlerim doğrultusunda gördüğüm operasyon şu şekilde gerçekleşiyor;
Ayşe teyzenin gerçekleştirdiği istek sonucunda bu kadar uzun beklemesi kesinlikle veri tabanı üzerinde gerçekleşen bir problem olabilir. Bu sebeple;
- Indexleri kontrol etmeliyim
- Veri tabanı ile iletişim kurup, sorguyu ne kadar sürede execute ettiğine bakmalıyım
- Ayşe teyzenin sorgusunu bir de sunucuya bağlanıp ilgili veri tabanı üzerinde T-SQL olarak çalıştırıp ne kadar sürede yanıt verdiğini kontrol etmeliyim
- Gerekirse bu sorguyu stored procedure olarak hazırlayıp daha hızlı yanıt vermesini sağlamalıyım
- Sonuç yine istediğim seviyede olmazsa bu sorguyu cache üzerinde saklamayılım ve her seferinde veri tabanına gitmemeliyim
- Hala düzelmediyse yapacak bir şey yok sorun Ayşe teyzededir
Bir dakika! Neden hemen sorunun veri tabanı üzerinde olduğunu düşündün? Belki sorun authentication adımında, belki sunucumuz anlık bir yoğunluk yaşadı, belki serialization and deserialization adımında bir sorun var. 🤔
Uzun lafın kısası aslında sorun çok farklı yerlerde olabilirken biz geliştiriciler ilk olarak belki de son bakmamız gereken alana odaklanıyoruz. Gözlemlerime ve deneyimlerime göre doğru kurgulanmış bir veri tabanı varsa genellikle sorun, veri tabanına gelmeden önceki adımlarda ortaya çıkıyor.
🎉İşte tam bu noktada EasyProfiler
kütüphanesi hayatımızı kolaylaştırıyor.
EasyProfiler
bu ve buna benzer sorunlarda ilk odaklandığımız yer olan veri tabanı üzerinde gerçekleşen operasyonları bizim için raporluyor. Ek olarak sistem üzerinde kullanılan endpointler arasında en yavaş veri tabanı işlemini gerçekleştiren endpointi bulubaliyor.
Günlük hayattan örnekler vererek konuyu teknik detaylardan uzak bir dille anlattığımıza göre artık Ayşe teyzenin sorununu çözüp hayır duasını almak için EasyProfiler
kütüphanesinin nasıl çalıştığını anlayalım ve sonrasında uygulamamıza nasıl implemente edileceğini inceleyelim. 🙏🤲
Easy Profiler Kütüphanesinin Amacı Ne? 🤔❓
Easy Profiler Dotnet uygulamalarında kullandığınız veri tabanını EF Core yardımıyla track etmeye başlıyor. Context üzerinde gerçekleşen işlemleri raporluyor ve kütüphanenin son kullanıcısı olan geliştiricilere bazı raporlar hazırlıyor.
Easy Profiler Kütüphanesi Nasıl Çalışıyor? 🤔❓
EasyProfiler kütüphanesinin ilham kaynağı EF Core 3.x ile aramıza katılan Interceptor özelliğidir.
Kelime anlamı devreye girme, kesme, araya girme olan bu özellik ile database işlemleri için araya girip kendi komutlarınızı yazabilirsiniz.
SELECT [c].[CustomerId], [c].[CreateDate], [c].[IsActive], [c].[Name], [c].[PhoneNumber], [c].[Surname], [c].[UpdateDate]FROM [Customers] AS [c] order by 1 desc
“EF Core Database Providers” cümlesini arattığınızda karşınıza çıkan veri tabanları;
- PostgreSql
- SQL Server
- MySQL
- Oracle
- MariaDb
yukarıdaki gibi oluyor. O zaman işimiz kolay. Kullandığımız interceptor özelliği herhangi bir veri tabanına özel olmayacaktır. EF Core ile bu özelliği kullandığımız için istediğimiz bir provider üzerinde bu interceptor özelliğini çalıştırabiliriz.
Ama listede en çok kullanılan bir veri tabanı eksik. Mongo 🚀
Mongo Üzerinde Interceptor Nasıl Sağlanır? 🤔❓
Mongo için context objemizi yaratırken bazı ayarlamalar yapabiliyoruz.
Örneğin;
- ServerAddress
- ConnectionTimeout
gibi ayarları gerçekleştirdiğimiz yerde gizlenmiş bir feature var.
ClusterConfigurator
içerisinde CommandStartedEvent
ve CommandSucceededEvent
için araya girebiliyoruz.
Eğer bu iki event arasında veri paylaşımını çözebilirsek o zaman mongo kullanılan uygulamalarda da interceptor kurgusunu çalıştırabiliriz.
Yukarıda bahsettiğimiz CommandStartedEvent
ve CommandSucceededEvent
nesneleri üzerine extension metodlar yazarak ilgili command üzerinde bulunan;
- OperationId
- CommandName
- CommandText
- Command Duration
gibi bilgilere ulaşabiliyoruz. Artık önümüzde bir engel yok. Interceptor mantığını hem relational veri tabanlarında hem de mongo üzerinde sağlayabiliriz.
Easy Profiler Bu Verileri Nasıl Saklıyor 🤔❓
Kütüphanenin ilk sürümlerinde interceptor özelliği ile veri tabanı üzerinde yapılan operasyonlar anlık olarak belirttiğiniz veri tabanı üzerinde Profiler
isimli bir tabloya kaydediliyordu. Ancak kütüphanenin kullanımı arttıkça ve en önemlisi, production ortamlarında kullanılmaya başlandıkça Easy Profiler kullanımında, veri tabanı sorgularının performansının ortalama %6.2 oranında düştüğünü gözlemledik. Yani 100 ms süren bir veri tabanı sorgusu artık 106.2 ms sürüyordu. İnsan gözünün 40ms altında gerçekleşen olayları handle edemediğini bilsek de yoğun kullanılan servislerde anlık yük çok arttığı için interceptor içerisinde bulunan logic parçaları uygulamayı performans anlamında geri götürmeye başladığı inkar edilemezdi. Biraz daha basitleştirerek anlatmak gerekirse; Easy Profiler tarafından kullanılan bir interceptor, sorguyu yarıda kesip kendi logiclerini çalıştırıyor ve datayı işliyordu. Her sorguda böyle bir maliyet bizi ve son kullanıcıları rahatsız edince bu işlemin gerçekleşeceği zaman dilimini geliştiricilerin kararına bırakma kararı aldık.
Eğer 2.0.x ve üzeri bir sürümü kullanıyorsanız initialize esnasında bir resulation seçimi yapmanız gerekir.
- High (Her dakika)
- Medium (İki dakikada bir kere)
- Low (Beş dakikada bir kere)
seçtiğiniz bu yapılar arka tarafta bir background job oluşturuyor ve bu aralıkta gerçekleşen veri tabanı operasyonlarını logluyor.
Evet yine her işlemde araya giren ancak yoğun logic barındırmayan bir sürüm ile %6.2 olan bu işlemi %1.8 seviyesine indirmeyi başardık.
Arka tarafta çalışan background işleri için kendi geliştirdiğimiz bir kütüphaneyi kullandık. Her ne kadar hangfire bu konuda çok başarılı olsa da “Easy Profiler kütüphanesini kullanmak istiyorsan Hangfire kütüphanesini de kullanmalısın” gibi bir zorlama yapmak istemedik.
EasyCronJob kütüphanesine ve detaylı inceleme makalesine aşağıdaki linklerden ulaşabilirsiniz.
Ek olarak EasyProfiler
belirtilen veri tabanı içerisinde easy-profiler
isimli bir şemada verilerini saklar. Böylece kendi şemanızdan kolayca ayırabilirsiniz ve backup senaryolarınızı düzenleyebilirsiniz.
Easy Profiler Kütüphanesi Nasıl Kullanılır (Relational Database)🤔❓
Easy Profiler şimdilik;
- SQL Server
- PostgreSQL
- MariaDb
- MySQL
relational veri tabanlarını destekler. Kullandığımız veri tabanına göre ilgili paketi nuget üzerinden indirerek kuruluma başlayabilirsiniz.
Install-Package EasyProfiler.PostgreSQL -Version 2.2.0Install-Package EasyProfiler.MariaDb -Version 2.2.0Install-Package EasyProfiler.SQLServer -Version 2.2.0
Bu yazı üzerinde PostgreSQL üzerinde basit bir örnek yaparak ilerleyeceğim.
EasyProfiler
kendi şemasının içerisinde verilerini sakladığını artık biliyoruz. Startup.cs
içerisinde ilk olarak verilerin saklanmasını istediğimiz sunucu bilgilerini ve bu işlemin sıklığını belirliyoruz.
EasyProfiler
üzerinde Low
, Medium
ve High
tipinde hazır sıklıklar bulanmakla beraber isterseniz istediğiniz bir cron değerini vererek de işlem sıklığını ayarlayabilirsiniz.
Bir sonraki adım olarak kendi veri tabanınızı initialize ettiğiniz kısımda küçük bir extension metodunu çağırmalısınız.
Artık son adımdayız. EasyProfiler
uygulamanız ayaklandığında belirttiğiniz sunucu üzerinde migrationları kontrol eder ve eksik migration varsa uygular. Böylece yeni sürümlerde EasyProfiler
içerisindeki veri tabanı nesnelerinde değişiklik veya yeni eklenen nesneler varsa EasyProfiler
kendi kendini günceller. Bu işlemi yapabilmesi için Configure
içerisinde IApplicationBuilder
arayüzünü kullanan ApplyEasyProfiler{Provider}
extension metodunu çağırmanız yeterli olacaktır.
Configuration aşaması bu kadar. Hadi uygulamamızda ne oluyor izleyelim.🤓
Sonuç 🤔❓
Sonuçları görmenin 3 farklı yöntemi var. Hadi öğrenelim. 🤓
**Manuel**
Belirttiğiniz sunucu üzerinde easy-profiler
isimli şemaya gidin. Profilers
isimli tablonun içerisinde uygulamanızın verilerini görebilirsiniz.
**Az Manuel Az Otomatik (Tiptronik 🤣)**
Dependency Injection ile IEasyProfilerBaseService<TCONTEXT>
servisini alın.
Bu servis üzerinde bulunan AdvancedFilter
ve GetSlowestEndpoint
metodlarını kullanarak verilerinizi görebilirsiniz.
**Full Otomatik**
EasyProfiler.AspNetCore
kütüphanesini nuget üzerinden indirin.
Bu kütüphane ile built-in endpointlere sahip olacaksınız. Bu endpointleri kullanarak aşağıdaki sonuçları edinebilirsiniz.
- En yavaş endpoint
- En hızlı endpoint
- En çok kullanılan endpoint
- Advanced Filter
~En Yavaş Endpoint~
METHOD : GET
URL : {BASE_URL}/easy-profiler/slowest-endpoint
~En Hızlı Endpoint~
METHOD : GET
URL : {BASE_URL}/easy-profiler/fastest-endpoint
~En Çok Kullanılan Endpoint~
METHOD : GET
URL : {BASE_URL}/easy-profiler/most-requested-endpoint
~Advanced Filter~
METHOD : GET
URL : {BASE_URL}/easy-profiler/advanced-filter
~~ Sort Properties for Advanced Filter ~~
- {BASE_URL}/easy-profiler/advanced-filter?sort=Duration
- {BASE_URL}/easy-profiler/advanced-filter?sort=Query
- {BASE_URL}/easy-profiler/advanced-filter?sortBy=Ascending&sort=Duration
- {BASE_URL}/easy-profiler/advanced-filter?sortBy=Ascending&sort=Query
- {BASE_URL}/easy-profiler/advanced-filter?sortBy=Descending&sort=Duration
- {BASE_URL}/easy-profiler/advanced-filter?sortBy=Descending&sort=Query
~~ Filter Properties For Advanced Filter~~
- BASE_URL}/easy-profiler/advanced-filter?duration.min=1&duration.max=10
- {BASE_URL}/easy-profiler/advanced-filter?duration.min=1&duration.max=10&queryType=SELECT
- {BASE_URL}/easy-profiler/advanced-filter?duration.min=1&duration.max=10&queryType=SELECT&page=1&perPage=10
- {BASE_URL}/easy-profiler/advanced-filter?requestUrl=getAllCustomer&duration.min=1&duration.max=10&queryType=SELECT&page=1&perPage=10
- {BASE_URL}/easy-profiler/advanced-filter?requestUrl=getAllCustomer&duration.min=1&duration.max=10&queryType=SELECT&page=1&perPage=10&combineWith=Or
- {BASE_URL}/easy-profiler/advanced-filter?requestUrl=getAllCustomer&duration.min=1&duration.max=10&queryType=SELECT&page=1&perPage=10&query=customer
İlerleyen sürümlerde Advanced-Filter
üzerine yeni parametreler eklenebilir. Eğer kullanmak istediğiniz parametreler varsa bu yazıya yorum olarak yazabilirsiniz veya github üzerinden issue açabilirsiniz.
Roadmap & Product Backlog 📝
Yazının bu aşamasında sizlere planlanan işlerden bahsetmek istiyorum. Böylece hep beraber EasyProfiler
için hayal kurabileceğiz. Sizlerin de eklemek istediği yeni özellikler varsa yorum olarak belirtmeniz çok faydalı olacaktır. Günün sonunda EasyProfiler
bir ürün ve bu ürünün kullanıcıları geliştiriciler olduğu için developer use-case
dediğimiz metrikler bizim için çok önemli.
Supported Databases 🗃️
EasyProfiler
tamamiyle database based bir library olduğu için desteklenen veri tabanları oldukça önemli. Şu an desteklenen veri tabanlarına ek olarak dotnet dünyasında en çok kullanılan veri tabanlarını listeledik ve bir sıralama oluşturduk.
- Oracle
- Firebird
- SQLite
ilerleyen sürümlerde yukarıda bahsettiğim veri tabanlarını da destekleyecek yeni yapılar kurmak bizim öncelikli hedefimiz.
Report Module 📌
Veri varsa bu verileri anlamdırmak ve görselleştirmek genelde iyi bir yaklaşım olacaktır. Yukarıda bahsettiğim Sonuç
kısmı aslında bu özelliğe göz kırpıyor. 😉
Ancak ilerleyen sürümlerde rapor modülünü biraz daha geliştirmeyi düşünüyoruz. Şu an bulunan raporlara ek olarak;
- Toplam Read Sayısı
- Toplam Insert Sayısı
- Toplam Update Sayısı
- Toplam Delete Sayısı
gibi özellikleri de eklemek istiyoruz.
Rapor modülü ile birlikte Limit
ve Notification
modüllerini de kütüphane içerisinde konumlandırmayı düşünüyoruz. Hadi bu modülleri de inceleyelim.
Limit ve Notification 🙈🔊
7/24 uygulamanın başında olamayacağımız bir gerçek. Notification modülü ile belirlediğiniz saatlerde sistemin o aralıkta gerçekleştirdiği tüm işlemler desteklenen kanallar üzerinden sizinle paylaşılacak.
Her takım veya her şirket kendine göre bir channel kullandığı için burada dinamik bir channel mantığı oluşturmak istiyoruz. Ancak kağıt üzerinde karar verdiğimiz bazı kanallar aşağıda listelenmiştir;
- Slack
- Discord
- Sms
- Microsoft Teams
- Push Notification
- Web Hook
- Amazon SQS/SNS
- Kafka Event
- RabbitMQ Event
- Azure Service Bus
Limit özelliği aslında notification modülünü doğrudan kullanan bir modül olarak tasarlandı. Uygulamanız içerisinde her modül farklı bir önceliğe sahip olabilir. A modülü sizin için çok öncelikli iken B modülü normal öncelikli olabilir. Limit modülü ile belirlediğiniz veya sistemin genelinde gerçekleşen operasyonlar için bir limit tanımı yapabileceksiniz. Örneğin XXX
tablosunda gerçekleşen Select
sorguları 25 ms üzerinde olursa notification gönder gibi işlemleri gerçekleştirebileceksiniz. Limit ve Notification modülü ile sistem içerisinde alert mekanizmaları kurup kurallarınıza takılan bir işlem olursa anında bilgilendirileceksiniz.
Real-Time Dashboard 📸
En çok heyecanlandığımız modül olan dashboard özelliği ile sistem içerisinde gerçekleşen olayları real-time bir şekilde izleyebileceksiniz. Geliştirdiğimiz ürünlerde artık monitoring kavramı hiç olmadığı kadar büyük bir önem arz ediyor. Dashboard özelliği ile monitoring sorunun çözmekle kalmayıp isterseniz kendinize özel rapor ekranları oluşturabileceksiniz. Ek olarak limit değerlerinizi güncelleyip sisteminizi anlık filtreleyebileceksiniz.
Yazının son kısmında bahsettiğim bu özellikler birer hayalden ibaret. Hayallerimizi gerçekleştirmek için kolları sıvadık ve kağıt üzerinde planlamalarımızı gerçekleştirdik. Sizlerin de eklemek istediği özellikler olursa github üzerinde fork butonuna tıklayarak başlayabilirsiniz. Unutmayın “Başlamak bitirmenin yarısıdır” 😉
Eğer EasyProfiler
size yardımcı olduysa veya bakış açınıza farklılık kattıysa star vererek destek olabilirsiniz.
Kodlarınız production ortamında hatasız koşsun, testler sizi korusun. 🙏
Happy Coding 👩💻