MongoDB — Read Concern, Write Concern Kurcalamaları

Baştan söylemeliyim ki, konu hem MongoDB ürünü özelinde hem de — özellikler ve kavramlar değişse dahi — NoSQL sistemler genelinde deniz derya! Dokümantasyonlara, örneklere, blog yazılarına daldığımızda Casual Consistency, Eventual Consistency, Availability, Dirty Read, Replica Set, teoremler, pratikler, kullanım senaryoları gibi birbirinden kompleks durum ve kavramlarla karşılaşmamız an meselesi oluyor.

Bu yazıda ise mümkün olduğunca Keep It Simple, Stupid! prensibine uygun bir biçimde, bazı temel özelliklere değinip örneklemeler yoluyla, MongoDB’nin ilgili özelliklerini incelemeye çalışacağız. Belki konu konuyu açar ve diğer yazılarda diğer önemli detaylara da değinme fırsatı buluruz. Hazırsak başlayalım o halde.

Eric Brewer’ın Kulaklarını Çınlatalım

NoSQL sistemlerle çalışan hemen hemen herkesin ilk günden, henüz hangi sistemin kullanılacağı araştırılırken, karşısına çıkan bir kavram: Brewer (CAP) Teoremi. Hani şu;

Dağıtık kurgulanmış bir NoSQL sistem, birbiriyle haberleşemese dahi ayakta durabilen birden fazla düğümün iletişimsizliğini tolare edebilmek için (Partition Tolerance) ya verinin tutarlılığından (Consistency) ya da veriye ulaşılabilirlikten (Availability) feragat etmek durumundadır.

olarak özetlenebilecek teori. Konuyla ilgili referans verebileceğim, okuduğum en temiz yazılardan birini de buraya ekleyeyim.

MongoDB — CAP Teoremi Eşlemesi

Haliyle akla gelen ilk soru, MongoDB bunların (C, A) hangisinden vazgeçiyor, sorusu. İşte tam bu noktada MongoDB aslında seçimi kullanıcılara bırakıyor demek çok da yanlış olmayacaktır. Kullanıcılar ise — aslında bizler, geliştiriciler — iş kural dizilerimize, network yapılarımıza, uygulama mimarilerimize göre uygun olan feragat yöntemini seçiyor ve bunu da başlıkta bahsettiğim Read Concern ve Write Concern özellikleriyle deklare edebiliyoruz.

İş Kural Dizisi, Network Yapısı, Uygulama Mimarisi !?!

Bu kafa karışıklığını çözmeden önce MongoDB’nin temel çalışma mantığından bahsetmekte fayda var.

MongoDB cluster’ını Replica Set olarak kurguladığınızda 1 adet primary, N adet ise secondary olarak konumlanmış node ile çalışıyorsunuz demektir. Bu cluster’a bir yazma isteği gönderdiğinizde Primary node bu isteği karşılayıp verinin “içeride” olmasını sağlayarak size OK cevabı döner. Arka planda ise gelen yeni veriyi secondary node’lara iletir. Keza bir okuma isteği yaptığınızda da yine primary node bu okuma isteğini karşılayıp size geri dönecektir.

Oldu ya, primary node ulaşılmaz duruma geldi. Bu durumda birbirlerini heartbeat yöntemi ile dinleyen node’lar, bu node’a ulaşamadığını farkeder ve aralarında bir seçim yaparak içlerinden birini yeni primary olarak atar ve operasyonunun devam etmesini sağlar (bkz. Partition Tolerance).

İkinci bir yöntem ise veri tutmayan, Replica Set’e dahil mongod instance’ları — namı diğer Arbiter — kullanmak. Bunlar sadece oylamaya dahil olur ve çoğunluğun (ileride majority olarak ifade edilecek) oluşmasını sağlar.

İşte tam bu noktada sizin iş modeliniz, C-A seçimi konusunda yol gösterici olur. Yazma esnasında gönderdiğiniz verinin “kesinlikle yazıldığından” emin olmak istemeyebilirsiniz (kullanıcı davranışlarını takip eden uygulamalar gibi, arada bir iki scroll aksiyonunu kaybetsem sorun olmayabilir). Ya da benim twitter mesajım altına yazılan cevabı bir arkadaşım T anında görebilirken, diğeri aynı anda görmese de olur diyebilirsiniz (verinin replikalar arasında dağılımda oluşan gecikme).

Gerçek bir örnek vermek gerekirse, içerik yönetim sistemine girilen haberde yapılan değişikliğin tüm replikalara dağıldıktan sonra cevap vermesini istemek yerine (tüm client’lar aynı metni görsün), biri diğerinden farklı görse de olur; önemli olan metnin kendisine her ne olursa olsun ulaşmak diyebilirsiniz. İşte bu durumda tutarlılık yerine (Consistency) ulaşılabilirliği (Availability) seçmiş oluyoruz.

Read Concern

Yorum ve detaylandırmaya girmeden önce MongoDB’nin referans dokümantasyonunu şuraya iliştirelim. Her özelliğini tek tek açıklamak yerine kullanım senaryosu daha yaygın bir kaç özellik üzerine konuşacağımız için asıl referansı da gözden kaçırmayalım.

Gerçek senaryoya dönelim:

  • Habere bir istek geldiğinde veri tutarlılığının yüksek seviyede olmasını istiyorsam ve olası gecikmeyi (latency) tolare edebilecek durumdaysam tercihim şu olurdu: İstekte bulunduğum verinin, Secondary node’larımın çoğunluğunda aynı olmasını istiyorum! O halde Read Concern olarak majority seviyesi benim için uygundur.
  • Habere gelen isteği yönlendirdiğim okuma node’unda veri varsa, diğer node’larla olan eşlenikliği çok önemli değil. Ben hızlı bir şekilde o veriyi geri döndürebilirim. Böylece gecikmeyi de mümkün olan en kısa seviyede tutarım! diyorsam bu özelliği local olarak ayarlayabilirim.

Bunlar dışında, iki sonraki bölümde bahsedeceğim Read Preference özelliğine bağlı olarak bu alanı linearizable ayarlayabileceğim gibi, multi-document transaction kullanıyorsam snapshot olarak da ayarlayabilirim. Bunlar da başka bir yazının konusu olsun.

Write Concern

Konunun yine asıl referansı bu linkte.

Normal şartlar altında MongoDB’ye bir yazma isteği gönderdiğinizde size bir acknowledge dönecektir. Bu kabul ifadesiyle, verinin “içeride” olduğundan emin olunur. Kabul kriteri olarak MongoDB, Write Concern ifadesini kullanıyor. Yani verinin “içeride” olması için gereken koşul.

Bazı durumda verinin tüm node’lara dağıldığından emin olmak isteyebilirsiniz (latency artacaktır). Bazı durumlarda ise gönderdiğiniz verinin “kesin olarak” yazılıp yazılmadığından emin olmak istemeyebilirsiniz (şu kullanıcı davranışlarını loglayan uygulama gibi). Bazı durumlarda ise yine çoğunluğun veriye sahip olduğunu bilmek sizin için yeterli olacaktır.

4 node’lu bir cluster içerisinde 2 node’un bu veriye sahip olduğundan emin olmak istiyorsanız, majority olarak ayarlanmış Read Concern özelliğini node sayısı ile tamamlamanız yeterli olacaktır: WriteConcern.W2

Ya da veriyi fire-forget yöntemi ile kaydetmek istiyorsanız WriteConcern.Unacknowledged ifadesini kullanmanız yeterli olacaktır. Böylece kullandığınız driver size kayıtla ilgili bir olumlu/olumsuz ifade dönmeyecektir.

WriteConcern için dikkat edilmesi gereken en temel nokta ise, bir timeout ile tamamlanması gerekliliği. 4 node’lu cluster içerisinde 4'ünün de veriye sahip olması gerekliliğiyle ilgili kodu yazdığınızda ve bir timeout vermediğinizde; olası bir node iletişim kaybında o oturuma bir dead-lock armağan edeceğiniz aklınızda olsun derim.

Bonus: Read Preference

Yukarıda, standart bir okuma işlemini Primary node’un karşılayacağını ifade etmiştik ancak bu da yine değiştirilebilir bir özellik:

  • Mümkünse primary, değilse secondary’ler karşılasın demek için ReadPreference.PrimaryPreferred
  • Her zaman secondary’ler karşılasın demek için ReadPreference.Secondary
  • Mümkünse secondary’ler, değilse primary karşılasın demek için ReadPreference.SecondaryPreferred
  • Bir Data center mantığıyla çalışıyorum, haliyle en yakın veri merkezi isteği karşılasın demek için ReadPreference.Nearest

ifadelerini kullanmanız yeterli olacaktır. Burada da yine iş kurallarınız devreye girer. Örneğin bu değeri ReadPreference.Primary olarak ayarlarsanız, Read Concern değerini ne ayarlarsanız ayarlayın, sorguya cevap dönmek için yeni primary seçiminin tamamlanması beklenecektir. Bu da sık sık değindiğim latency’i ortaya çıkarır. Ya da bu değeri ReadPreference.Secondary olarak ayarladığınızda ve tüm secondary notlarınız ulaşılamaz hale geldiğinde, okuma operasyonlarınız cevap vermeyecektir. Bu durumdan kurtulayım, ReadPreference.SecondaryPreferred olarak ayarlayayım dediğinizde ise bu defa, Primary’e gelen okuma/yazma istekleri birbirleriyle öncelik yarışına girecek ve operasyonlarınız sekteye uğrayacaktır.

Sonuç

Bu concern ifadelerini komple bir session için ayarlayabileceğiniz gibi, ilgili op için de ayarlayabilirsiniz. Session boyunca geçerli ayarlarla yapılan bağlantı için aşağıdaki .NET kodunu örnek olarak buraya bırakıyorum:

Yazıyla ilgili görüş ve önerilerinizi, varsa sorularınızı iletmekten lütfen imtina etmeyin. Bir sonraki yazıya dek; okuma ve yazmaya dair endişelerinizin yerini, amaca ulaşmanın gurur ve mutluluğu alsın (yine sosyal mesaj oldu :).