Elasticsearch: Nest Performansına Etkili Bir İlaç
Geçtiğimiz günlerde süregelen bir Elasticsearch projesinin bir aşamasında Nest’in “High Level Client” nesnesinin performansla ilgili bir takım sorunları olabileceğine dair bazı kaygılarla karşılaştım.
Bu gibi durumlarda her zaman olduğu gibi öncelikle ürün konfigürasyonu üzerine yoğunlaşılır. Tabii biz de öyle yaptık. Versiyon uyumluluğu kontrolünden tutun da, Windows tarafında genel bir performans sorunu olup olmadığına; index yapısının doğru tasarlanıp tasarlanmadığından, sorgularda kullanılan filtrelerin yerlerine ve tiplerine kadar bir çok kontrol gerçekleştirdik ancak herhangi bir bulguya ne yazık ki rastlayamadık.
Aslına bakarsanız, Elasticsearch’ün RESTful servisinden gelen cevaplardaki süreler oldukça makul idi. Yani Nest ile yapılan sorgunun JSON çıktısını alıp servis üzerinden çağırdığımızda hiç de performans sorunu yaşarmış gibi durmuyorduk.
Kod tarafında ConnectionSettings nesnesinin DisableDirectStreaming() fonksiyonunu aktif hale getirirseniz, Search fonksiyonundan gelen cevabı bir değişkene atayıp ApiCall.RequestBodyInBytes özelliği ile sorguyu JSON formatında alabilirsiniz.
Sorun neydi?
5–6 property’si olan bir sınıfın Elasticsearch tarafında karşılığı olan bir index var ve yaklaşık 100 kadar kayıt bu sınıfı karşılayacak seviyede index içerisinde tutuluyor. Ancak API endpoint’inde uçtan uca operasyon (Query oluşturma, Search operasyonu, gelen cevabın deserialize işlemi, deserialize sonrası çıkan nesnenin response olarak iletilmesi) ortalama 550 ~ 650 ms. sürüyor. RESTful servisi çok daha hızlı yanıt verirken ve yazılımsal olarak aslında NEST dışında bir operasyon yürütülmezken işlemin bu kadar uzun sürmesi can sıkıcı elbette.
Çözüm olarak ne bulduk?
Genel kontroller sonrasında, Elasticsearch ile bağlantı kuran uygulamalarda ilk yapılan işlemi kontrol etmenin faydalı olduğunu düşündüm: Client ile sunucu arasındaki bağlantıyı kuran nesnelerin oluşturulması.
NEST kütüphanesini daha önce kullandıysanız bu bağlantı operasyonu iki temel nesne üzerinden yürüyor: ElasticClient ve ConnectionSettings. İsimlerinden de anlaşılacağı üzere bağlantı uçlarını ve bağlantıya dair temel özellikleri barındıran ve sunan ConnectionSettings nesnesi. ElasticClient nesnesi ise parametreden bu nesneyi alır ve bu bağlantı özelliklerini kullanarak sunucuya bağlantı operasyonunu gerçekleştirir.
Bunu kodlarken atlanan en temel noktalardan bir tanesi nesnelerin yaşam döngüsü. Nest dokümantasyonunda da (1) belirtildiği üzere bu iki nesnenin kullanımı için bir best-practice mevcut. ElasticClient nesnesi thread-safe bir nesne. Dolayısıyla bu nesnenin yönetilmesi için ekstra bir efora gerek yok. Ancak ConnectionSettings’in her defasında instance alınarak ElasticClient’a parametre geçilmesi bir felaketin ön habercisi.
Bunun sebebi — ve aslında yaşanılan performans sorununun da kaynağı — tüm bağlantı ve serialization işlemlerinde kullanılan cache işleminin bu nesne üzerinden yürütülmesi.
Çözüm ise oldukça basit: Nesneyi singleton instance ile yönettiğiniz takdirde, ElasticClient nesnesine bu singleton obje kopyasını vererek ciddi bir (bizim testlerimizde 4x ~5x performans artışı sağladı) performans katma değeri yakalayabilirsiniz.
Aşağıdaki örnek uygulama üzerinden yapılan Singleton ve PerRequest instance testlerindeki Postman sonuçları 20 istek için aşağıdaki gibi:
Sonuç
Bazen oldukça küçük görünen detaylar büyük farklar yaratabilir sözünün gerçekliğini — böyle bir söz var mı bilmiyorum, şu an ben uydurdum :) — gözlemleme fırsatına sahip olduk. Bir sonraki yazıya kadar tüm endpoint istek sürelerinizin threshold altında kalması dileğiyle :)
Kaynakça
(1) https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/lifetimes.html