Elasticsearch Event-Driven Projection

Said Aydogan
hepsiburadatech
Published in
4 min readApr 12, 2021
Photo by sergio souza on Unsplash

Elasticsearch hepimizin de bildiği gibi açık kaynak kodlu lucene alt yapısını kullanan bir arama ve analitik motorudur. Bu yazımda Hepsiburada OMS (Order Management System) ekibi olarak elasticsearch deneyimimizi ve near real-time data projection u nasıl sağladığımızı ele alacağım.

Neden elasticsearch?

Full-text search yapacağımız alanlar olması, hızlı, performanslı ve ölçeklenebilir bir API sunması ve JSON document tutması tercih sebebi olarak sayabileceğimiz ilk maddeler.

Indexleme sorunu

Elasticsearch clusterı dağıtık bir yapıdadır. Dolayısıyla sunduğu API ler ile datayı içeriye alıp, güncelleme ve silme isteklerimizin her biri dokümanın yeni bir versiyonu olarak değerlendirilecek ve diğer replicalara paralel bir şekilde dağıtılacaktır. Neredeyse aynı anda ve aynı doküman için gelen istekler bir birini ezmesi mümkün olmaktadır. Bu noktada concurrency control yapacak yöntemlere ihtiyaç duyulmaktadır.

Ordering

Event-Driven bir yapınız varsa ve eventlerinizin sırası önem arz ediyorsa.

Örneğin, bir paket oluşturma ya da paket bozma sürecinde DeliveryCreated ve DeliveryDeleted eventleri oluştuğunu düşünelim.

1- DeliveryCreated event (version:1)
2- DeliveryDeleted event (version:2)
3- DeliveryCreated event (version:3)

Bu üç event ardışık bir şekilde publish edilecek ve projection için eventleri dinleyen consumerlar tarafından işlenecektir. Birden fazla consumerın yer aldığı benzer yapılarda olması gereken sıra ile eventi consume etmek her zaman kolay ya da mümkün olmayabilir. Bu durum projection datasının inconsistent olmasına sebebiyet verecektir. Event ordering için RabbitMQ kullanıyorsanız consistent hashing bir çözüm olabilir.

An example of lost message ordering when using RabbitMQ
Source: https://betterprogramming.pub/rabbitmq-vs-kafka-1779b5b70c41

Versioning

Eventleriniz sıra ile consume ediliyor olması onların sıralı işleneceğini garanti etmeyecektir. X bir sebepten işlenemeyen event dolayısıyla bir sonraki eventler projection datasının inconsistent olmasına neden olabilir. Bunu önlemek için eventlere number ya da version verilmektedir.
Sıralı consume edilmeyen ya da sıralı olsa bile projection datasındaki version+1 olmayan bir event geldiğinde ignore/retry ederek projection datasını garantiye alabiliriz. Bu tarz yaklaşımlar optimistic concurrency control başlığı altında incelenebilir.

Source: https://medium.com/@esdeveloper/optimistic-locking-in-distributed-system-using-elasticsearch-as-example-368938dfb8cd

Elasticsearch optimistic concurrency control sağlayabilmek için built-in versioning yapıları kullanılabilir.
Internal versioning yapısında if_seq_no ve if_primary_term parametreleri bu sorunun üstesinden gelmektedir. Yaptığımız isteklere bu alanları ekleyerek bu sayede bir index, update, delete vb işlemi devam ederken başka bir işlem araya girerse son dokümanı güncelleyeceği için parametrelerdeki bilgi ile doküman bilgisi tutmayacağından dolayı version conflict hatası almasını sağlayacaktır.

Fakat eğer hali hazırda versioning yaptığınız başka bir database varsa yani version bilgisini aslında elasticsearch e siz söylecekseniz bunun için de external versioning yapmanızı sağlayan version_type ve version parametreleri bulunmaktadır.

Bizim tercihimiz elasticsearch external versioning oldu. Çünkü hali hazırda event lerimizde ve document larımızda version bilgisi bulunmaktaydı. Burada version bilgisi aslında bir document version olduğunu her hangi bir event in başka bir varyasyonu olmadığını belirtmekte fayda olacaktır.

Ordering başlığı altındakine benzer olacak bir örnekle açıklayacak olursak;

1- DeliveryCreated event (version:1)
2- DeliveryDeleted event (version:2)
3- DeliveryCreated event (version:3)

Eventleri ardışık ve sıralı bir şekilde publish ve consume ediliyor olsunlar. Fakat version:2 olan event işlenirken herhangi bir sebepten hala alırsa ya da consume edilmesi daha fazla zaman alırsa; version:3 event i ile yeni bir delivery oluşarak çift paket oluşmasına neden olacaktır.

External versioning kullanımında ise;
version:2 → version_type=external&version=2
version:3 → version_type=external&version=3
olarak gidecektir ve eğer version:2 işlenmezse elasticsearch version:3 event i için version_conflict hatası verecektir. version_type=external ile gerçek zamanlı bir version kontrolü sağlanmaktadır var olan dokümanın version bilgisinden büyük version gönderilmek zorundadır eğer doküman hiç yok ise gönderilen version ile oluşur.

Version conflict hatalarını minimize etmek için Event-Carried State Transfer ve Event Notification yapıları mix bir şekilde kullanılabilir. Örneğe devam edecek olursak;
version:3 ile version_conflict hatası aldığımızda source dan (primary db, api vb) document in son version halini alıp projection datamızı tüm alanlar güncellenecek şekilde güncellersek (full update) event notification yaparak version conflictleri yaşansa bile datanın doğruluğunu hızlı bir şekilde (bu sayede rety vb süreler beklenmeyecektir) garanti etmiş oluruz.

Refresh

Eğer yoğun bir trafik altında projection yapıyorsanız refresh kullanırken dikkat edilmelidir. Bunun en büyük sebebi performans kayıplarına neden olmasıdır. Çünkü refresh=true ya da refresh=wait_for olarak gönderilen isteklerde elasticsearch bir süre isteğinizi bekletir ve dokümanın aramalarda görünür olmasını sağlar. Bu seçenek version conflictlerinizi azaltsa da yük altında ciddi performans kaybı yaşamanıza sebep olabilir. Bu sebeple refresh i default false değeri ile kullanmaya devam ediyoruz.

Sonuç olarak, event-driven bir elasticsearch projection için hali hazırda kullanmakta olduğumuz yapının neler içerdiğini ve kullanılan yöntemleri genel çerçeve içerisinde aktarmaya çalıştım. Konu hakkında fikir alışverişinde bulunmak ve tavsiyeleriniz için LinkedId üzerinden iletişime geçebilirsiniz.

Vakit ayırıp okuduğunuz için sizlere ve bu yazıyı yazmamda yardımcı olan tüm arkadaşlarıma saygılarımı sunar ve teşekkür ederim.

--

--