27 Temmuz 2023 Perşembe

Logging & Monitoring Distributed System



Loglar, metricler ve traceler genellikle sistemleri gözlemlemenin üç ana unsuru olarak bilinir. Bunlar iyi anlaşıldıkları takdirde daha iyi sistemler oluşturmamızı sağlayacak güçlü toollardır.

Loglar bize sistemimizde gerçekte ne olduğunun bir kaydını verir. Doğaları gereği, tipik olarak kronolojiktirler ve diğer data sourcelerin  anlaşılması için context sağlarlar. Örneğin, bir metric'in neden yükseldiğini anlamak veya bozuk bir trace'te sorun gidermek için logları kullanabiliriz.

Metrikler, herhangi bir anda sistemimizin sağlığını ölçmek için bir yol sağlar. Metrikleri sorgulayarak “is the system up?”  veya "how many requests are currently being processed?" gibi sorulara cevap verebiliyoruz.

Traceler, requestlerin sistemimiz üzerinden nasıl aktığı konusunda bize görünürlük sağlıyor. Bir traceyi takip ederek darboğazların(bottlenecks) nerede oluşabileceğini görebilir ve sistemin hangi bölümlerinin requestleri process etmesinin en uzun sürdüğünü belirleyebiliriz.

Logları, metrikleri ve traceleri toplamak göz korkutucu olabilir, ancak yardımcı olabilecek bir dizi tool ve servis vardır. Özünde observability , sistemimizin davranışı hakkında ortaya çıkabilecek herhangi bir soruyu yanıtlayabilecek kadar iyi anlamakla ilgilidir. Logların, metriclerin ve tracelerin gücünden yararlanarak daha gözlemlenebilir ve sorun gidermesi daha kolay sistemler oluşturabiliriz.

Event Logging( Olay günlüğü tutmak)

Bir computer systeminde yada networkte meydana gelen eventlerin track edilmesi, storing edilmesi sürecidir. Event loggingin amacı,security konularını monitor etmenin yanı sıra problemleri track etmek ve tanılamak için kullanılabilecek aktivitelerin bir kaydını sağlamaktır.

Event loglar, bir sistemde veya networkte neler olup bittiğine dair detaylı bilgiler içerebilir. Bir kullanıcının ne zaman oturum açtığı veya kapattığı, hangi data sourcelere erişildiği ve dosyalarda veya diğer verilerde hangi değişikliklerin yapıldığı gibi ayrıntıları içerebilirler. Administratorlar , event logları analiz ederek sistemlerinin nasıl kullanıldığını daha iyi anlayabilir ve olası sorunları erkenden belirleyebilir.

Event log, sistemleri anlamak ve sorun gidermek için değerli bir tool olsa da, kötü amaçlar için de kullanılabilir. Saldırganlar, izlerini gizlemek ve tespit edilmekten kaçınmak için event logları kullanabilir. Bu nedenle, event logların düzgün bir şekilde güvenliğini sağlamak ve bunlara erişimi olan kişileri sınırlamak önemlidir.

Event loglar zaman damgası ve olayların kaydını paylaşırlar. En yaygın event log türü, bir bilgisayarda veya networkte meydana gelen olayları izleyen sistem günlüğüdür. Sistem günlükleri, işletim sistemleri, uygulamalar gibi cihazlar tarafından oluşturulabilir.

Diğer event log türleri arasında application logs, security logs ve access loglar bulunur. Uygulama günlükleri, belirli bir uygulamada meydana gelen olayları izlerken, güvenlik günlükleri, başarısız oturum açma girişimleri gibi güvenlikle ilgili olayları izler. Erişim günlükleri, kimin hangi kaynaklara ne zaman eriştiğinin kaydını sağlar.

Metrics

Metrikler, zaman aralıklarında ölçülen verilerin sayısal bir temsilidir. Metrikler, bir sistemin şimdiki ve gelecekteki zaman aralıklarındaki davranışı hakkında bilgi elde etmek için matematiksel modelleme ve tahminin gücünden yararlanabilir.

Metrikler, herhangi bir gözlemlenebilirlik stratejisinin hayati bir parçasıdır ve çok çeşitli performans göstergelerini izlemek için kullanılabilirler. Request latency, CPU usage, ve memory tüketimi gibi. Bunları ve diğer ölçümleri izleyerek darboğazların nerede oluşabileceğini görebilir ve sistemin hangi bölümlerinin istekleri işlemesinin en uzun sürdüğünü belirleyebiliriz.

Tracing ( izleme)

Tracing, distributed sistemin execution flowunu anlamaya yönelik bir tekniktir. Bir requestin kaynağından hedefine giderken process edilmesindeki her adımla ilgili bilgilerin kaydedilmesini içerir. Bu bilgi daha sonra request tarafından izlenen yolu yeniden oluşturmak ve darboğazları veya diğer sorunları belirlemek için kullanılabilir.

Tracing, gözlemlenebilirliğin önemli bir parçasıdır ve productiondaki sistemlerin davranışını anlamak için kullanılabilir. Traceleri analiz ederek, requestlerin sistemde tam olarak nasıl aktığını görebilir ve olası sorunları belirleyebiliriz.

Bazı toollar, karmaşık akışları anlamak için yardımcı olabilecek trace verilerinin görselleştirmelerini de sağlar.

26 Haziran 2023 Pazartesi

Static Metodlu Sınıf mı Yoksa Singleton mı ?

Nesne-merkezli diller bize nesne-merkezli kod yazmak için imkanlar sunmaktadır ama örneğin en azından nelerin nesne olacağı ve nesnelerin  aralarındaki ilişkilerin nasıl olacağı gibi kararlar tamamen biz geliştiricilerin/tasarımcıların sorumluluğundadır. Bu sorumluluğu yerine getirmede sıkıntılar yaşadığımız müddetçe kullandığımız programlama dilinin bize sağlıklı bir nesne-merkezli yapı oluşturmak için yardım etmesi mümkün değildir.   

Bir sınıf oluşturup, içine üç-beş tane private nesne değişkeni (instance variables) tanımlayıp, sonra da bu sınıfa set/get metotlarını koymak, kesinlikle kodunuzu nesne-merkezli yapmaz. Nesne-görünümlü yapabilir ama nesne-merkezli programlamanın, bundan daha fazla olduğunu söylememiz gereklidir. 

GoF’un Kitaplarındaki şablonlar, doğru nesneleri bulup doğru bir şekilde kurgulamak için farklı tasarım seçenekleri sunar. Bunu da geliştirdiğimiz yazılımın daha rahat değişebilmesi için yaparlar. “Design for change” (degişiklik için tasarla), GoF’un kitabının ilk bölümündeki üç temel prensiptenn birisidir. “Program to an interface, not an implementation” (arayüze program yaz, gerçekleştirmeye değil) ve “favor object composition over class inheritance” (nesne bileşimini sınıf kalıtımına tercih et) kitabın ilk bölümünde savunulan diğer iki prensiptir.

1-Sınıflarda aşırı miktarda statik metot kullanımı

Kodunuzdaki statik metotların sayısı, kodunuzun ne kadar nesne-merkezli olduğunun göstergesidir. Hiç statik metota sahip olmayan bir yazılım düşünmek pek de mümkün değildir ama statik metotların kullanımının istisnai durumlardan çıkarak genel bir hal kazanması, yaklaşımınızın nesne-merkezli olmaktan çıktığının kanıtıdır.

Nesne-merkezli programlamada esas olan, nesne kullanmaktır; statik metotlar ise nesne oluşturmadan, sınıf üzerinden programlama yapmanın yoludur ve bu yüzden de nesne-merkezli programlamanın varlık sebebine karşı bir durum oluşturmaktadırlar. Dolayısıyla aslolan, nesne metotlarının (instance methods) kullanılması, statik metotların ise tamamen istisnai sebeplere bağlı olarak yazılmasıdır. Ne kadar çok statik metot kullanırsanız nesne-merkezli programlamadan o kadar çok uzaklaşıp prosedürel programlamaya o kadar yaklaşmış olursunuz.

Statik metot kullanıp nesne metotu kullanmamak, yazılımı geliştirirken nesneye ihtiyaç duymamak demektir. Bunun birkaç sebebi olabilir:

Metot hakikatten de nesnenin kendisine özel olan durumunu değil, nesnelerin ortak değerlerde barınan durumuna bağımlıdır. Bu durumda nesnelerin ortak durumu, statik değişkenlerle ifade edilir ve bu ortak duruma erişen metotların da statik olmaları normaldir. Bu anlamda statik metot kullanımı, olmadığında sıkıntılı ve verimsiz olacak halin sağlıklı bir şekilde çözülmesinde ibarettir. Örneğin Singleton tasarım şablonunda bir tane yaratılmış olan nesnenin “static” olması ve o nesneye ancak yine “static” olan bir metottan ulaşılması bir zorunluluktur.

Nesnesiz programlama yapmanın başka bir sebebi ise nesne oluşturmanın anlamsız olduğu durumlardır. Bu gibi hallerde durum da vardır ve bu durum üzerinde çalışan metotlar da vardır ama durum ve metotların üzerinde tanımlandığı yapının nesne olması konusunda çok da açıklık yoktur. Böyle hallerde sınıfın kendisinin nesne olarak davranmasını bekleriz. Örneğin “Tanrı” diye bir sınıfımız olsa bu sınıftan kaç tane nesne üretmeyi düşünürdünüz? Eğer çok tanrılı bir dine inanmıyorsak, olsa olsa bir tane Tanrı nesnesi üretmek yeterli olurdu. Bu durumda hiç nesne oluşturmadan Tanrı sınıfını nesne gibi kullanmak dolayısıyla da bu sınıfın üzerindeki her türlü durumu ve davranışı statik yapmak son derece anlamlı olur.

Dolayısıyla bazen ya kavram o kadar soyuttur ki gerçekte bir nesne ile ifade etmezsiniz ve sınıfı nesne olarak kullanırsınız ya da kavramınızın verdiği hizmetler hiç bir şekilde o sınıftan üretilecek nesnelerin farklı olacak durunmlarına bağlı değildir. Java APIsindeki java.lang.Math sınıfı buna çok güzel bir örnektir. Math sınıfının nesnesini oluşturmanın anlamı yoktur, çünkü onun sınıfını yukarıdaki Tanrı sınıfında olduğu gibi olsa olsa ancak bir tane nesneye sahip olabilir (eğer tek bir evrenimiz varsa ve evrenin her yerinde aynı Matematik geçerliyse) bu yüzden sınıfını nesne olarak kullanabiliriz. Ayrıca Math sınıfının metotlarının davranışı bu sınıftan oluşturulacak nesnelerin durumuna bağlı değildir. Dolayısıyla Math sınıfından nesne oluşturmadan üzerindeki statik metotlar sayesinde ondan hizmet alabiliriz.

Bazen, gereksiz nesne oluşturmama gibi masum bir amacın, bol statik metot kullanımına yol açtığı görülmektedir. Gereksiz nesne oluşturma, belleğin gereksiz kullanımına dolayısıyla da, bellek kaçağı (memory leak), performans ve ölçeklenirlik (scalability) problemlerine yol açar. Ama bu durumun tedavisi “hiç nesne oluşturmamak” değildir. Yani “nesneler belleği şişiriyor dolayısıyla nesne oluşturmayıp statik metotlarla çalışıyoruz” demek daha büyük sorunlara yol açacaktır. Eğer hakikatten bellek kullanımı bu kadar önemli ve kritik ise, bunun mimari bir ihtiyaç olarak ortaya konması ve bu seviyede çözümünün aranması daha doğru bir yaklaşım olur. Böyle bir çalışma, nesne merkezli bir dil kullanmak yerine bellek konusunda daha cimri olan prosedürel bir dil kullanmak gibi bir karar ile de sonlanabilir. Ya da Java kullanarak ve bellek konusunda daha tedbirli davranarak da bu sorunu çözebilirsiniz. Nesne oluşturmaktan kaçınmanın ana sebebi nesnenin “gereksiz” değil olsa olsa “anlamsız” olmasıdır. Yukarıda bahsedilen Tanrı ya da java.lang.Math sınıfı gibi, modellenen işin bir parçası olmayan, dolayısıyla işle ilgili nesneden nesneye değişen bir duruma sahip olmayan, araçsal sınıflar ve genelde onların statik olan metotları olmasaydı (ve dolayısıyla da biz bu sınıfların metotlarını ancak nesneleri üzerinden kullanabilir olsaydık,) bellek probleminden çok daha önce kod yapımızla ilgili problemler yaşardık. Çünkü bu gibi sınıfların nesnelerini yaratmak çoğu zaman çok fazla bilgi gerektirir ve bu durumu aşmak için nesne üretici (factory) yapılar gibi başka tasarım desenlerine ihtiyaç duyardık.

  • Nesne yaratmanın anlamsız olduğu durumlar. Bu durumlarda sınıfı, nesne olarak kullanabilirsiniz. Nesne yaratılsa bile sadece bir nesneye ihtiyaç varsa ki bu durum Singleton tasarım şablonuna karşılık gelir, sınıfın kendisini o tek nesne yerine, sanki nesneymiş gibi kullanılabilir
  • Metodunuz, üzerinde çağrılacağı nesnenin hiç bir nesne değişkenini (instance variable) kullanmıyorsa, yani nesnenin durumuna ulaşmıyorsa, statik metot kullanabilirsiniz. Çünkü statik metotlar statik olmayan değişkenlere ulaşamazlar.
  • Metodun çağrılması için bir nesneye ihtiyacınız yoksa statik metot kullanabilirsiniz. Özellikle aynı nesnenin pek çok farklı bağlama geçilip, hepsinde kullanılması gibi durumlarda, farklı bağlama nesne geçmek yerine orada doğrudan sınıf üzerindeki statik metotları çağırmak son derece kolaylıktır. Özellikle araçsal davranışlara sahip (utility) sınıfları bu şekilde yazılımın her bölgesinde kullanmak anlamlıdır.
Statik Metotlu sınıf mı Yoksa Singleton mı ? 

Nesne-merkezli dillerde çok tartışılan konulardan birisi de hiç nesne oluşturmaya gerek bırakmadan kod yazmamızı sağlayan statik özellikler ve metotlar mı yoksa sadece bir tane nesne oluştuğundan emin olup, bu tek nesne üzerinde çağıracağımız nesne özellikleri ve metotları mı? Bu probleme kısaca statik-singleton (static vs. singleton) de denir çünkü tasarım kalıplarında, bir sınıftan sadece bir nesne oluşturmaya “singleton” yani “tekil” denir.

Statik kullanımı, tabi özellikle statik metot kullanımı, nesne-merkezli dillerin, yanlış kullanıma en fazla konu olanı ya da bir başka deyişle suistimale uğrayan alanı. Malum, loglama vb. utility metotlarını, üzerinde bulunduğu sınıfın nesnesini oluşturmadan hızlıca kullanmak isteriz. Örneğin aşağıda üzerindeki metotları statik olarak tanımlanmış ve bu yüzden de bu metotları kullanmak için nesnesine ihtiyaç duymayacağımız bir Log sınıfı vardır


Statik metotlara sahip böyle bir sınıfı kullanmanın kolaylığı çok açıktır: Sınıfın kendisini nesne gibi kullanmak. Dahası o tek nesneyi herhangi bir yere geçmenize bile gerek yok, doğrudan sınıfın üzerinden statik alanlara ve metotlara ulaşabilirsiniz.

Eğer yukarıdaki Log sınıfının metotlarını statik yapmak istemezsek ve bu sınıfın sadece bir tane nesnesinin olacağını planlıyorsak bu durumda Log sınıfını singleton olarak şöyle kodlardık:


Cevap
Nesne-merkezli dillerde aslolan nesne olduğuna göre, statik metotlu sınıf yerine tek nesneli sınıfı yani singletonı tercih etmemiz gerekir.  Nesnelerin yönetimi ve çok bellek tüketmesi gibi sorunlar, henüz nesne kavramını anlamamış ama nesne-merkezli bir dili iyi bildiğini düşünenlerin dile getirdiği sebeplerdendir. 

  • Singleton sınıfla bir miras hiyerarşisi kurup, metotlarını ezmeyi (override) tercih edebilirsiniz. Bu şekilde polymorphic davranış elde edebilirsiniz ki bu durum nesne-merkezli dillerin en temel özelliğidir. Fakat bu durum statik metotlar için geçerli değildir çünkü statik metotlar ezilemezler, sadece nesne metotları ezilebilirler. Bu yüzden polymorphic davranış elde etmek, örneğin “program to interface, not an implementation” gibi prensipleri uygulamak statik metotlu sınıflar ile mümkün değildir. Bir örnek vermek istersek, God sınıfını statik metotlu yaparsanız, örneğin farklı dinlere sahip olanlar, God sınıfından miras alan Allah, Yahweh vb. sınıflarlar oluşturup, üzerindeki “createUniverse()” gibi metotları ezemezler.  Bu durum statik metotlu sınıf kullanımının en ciddi kısıtıdır. Bu yüzden sınıflarınızın miras mekanizmasıyla genişletilip (extension) genişletilmeyeceğinden emin olmalısınız. Örneğin java.lang paketindeki Math sınıfındaki metotlar statiktir ve zaten bu sınıf final yapılmıştır. Çünkü hem Math sınıfının nesnesinin oluşturulmaması istenmiş hem de genişletilmesinin önüne geçilmiştir. Aynı durum java.lang.System sınıfı için de geçerlidir. Fakat aynı paketteki Runtime sınıfı, genişletilebileceği düşüncesiyle statik metotlu değil tek nesneli yapılmıştır. Bu durumda farklı amaçlarla Runtime sınıfınıdan miras devralıp metotlarını ezebilirsiniz. Bu yüzden bu tip sınıflarda “getRuntime()” türünden (java.awt.Desktop sınıfında da getDesktop()) metotlar vardır. (Java SE API’sinde “getInstance()” metotlu belki 100den fazla sınıf var.”getInstance()” metoduna bakarak bunların tek nesneli sınıflar olduğunu düşünmeyin. Java SE API’sindeki statik “getInstance()” metotları çoğunlukla yapılandırıcı metot alternatifi olarak kullanılmıştır ve yeni bir nesne döner. Örneğin java.util.Calendar sınıfı.)
  • Yukarıdaki maddeye çok benzer şekilde, singleton sınıflar, arayüzlerden türetilebilir dolayısıyla da devraldıkları metotlara kod sağlarlar. İlk maddedeki örnek, arayüz kullanımıyla da çözülebilir. Örneğin daha genel düşünüp, Creator isimli bir arayüz oluşurup, bunun GodAllahYahweh gibi sınıflarla genişletilmesini sağlamak daha güzel bir çözüm gibi görünüyor. Dolayısıyla “program to interface, not an implementation” prensibi uygulanmış olur.
  • Static metotlarda ki bir diger problem de unit test yazarken mock objelerde ortaya cikiyor.
    Bu yüzden static metotlarin ufak utility class larinda yazilmasi daha doğru olacaktır.
reference

13 Nisan 2023 Perşembe

Redis (Remote Dictionary Server)

Neden Redis Kullanırız ? 

Redis bir  open-source key-value database serverdır. Bunlara ek olarak data structure server olarak da bilinmektedir.

Redis diğer NoSQL veritabanlarına göre içinde data tipleri bulunduran bir yapıya sahiptir. Örneğin, rediste key’e karşılık gelen valuelerin string olma gibi bir zorunluluğu yoktur. Redis Lists, hash ve sets gibi veri tiplerini destekler. 



Öncelikle Redis, uygulama performansını artırmaya yardımcı olmak için MySQL veya PostgreSQL gibi databaselerin  önünde cache olarak kullanılan bir in-memory veritabanıdır. Redis hakkındaki bir diğer önemli konu ise verileri memory’de tutuyor olmasıdır. Redis dataları öncelikli olarak kendi koştuğu makinanın RAM’inde tutar. Performansının yüksek olmasının sebebi dataları memoryde tutmasıdır. Bunun dışında redis ile istenilirse RAM’de tutulan veriler disk’e de yazılabilir.

Kullanım alanları
  • Nadiren değişen ve sık sık talep edilen veriler için kullanımı uygundur. Caching
  • Sayaçlar
  • Oturum (Session Verileri)
  • Kuyruk işlemleri
  • publish-subscribe mechanisms


Transaction Yönetimi
Transaction: Bir transaction içerdiği SQL ifadelerinin ya tamamını gerçekleştirir, ya da hiçbirini gerçekleştiremez. İşlemlerin tamamı gerçekleşmediği sürece işlemlerin hiçbiri gerçekleşmemiş sayılır.
Bir müşteri kendi hesabından başka bir hesaba 1500 TL para transferi gerçekleştiriyor.
  • Önce kişinin hesabından transfer edilecek olan 1500 TL tutar düşülmelidir.
  • Sonra diğer kişinin hesabına transfer edilecek olan 1500 TL tutar eklenmelidir. 
Bu işlemlerden birincisi gerçekleştikten sonra herhangi bir sorundan dolayı İkinci işlem gerçekleşmezse hesaplarla ilgili ciddi sorunlar yaşanabilir.

Bu tür sorunları önlemek için transaction yapıları kullanılır.

Transaction her iki işlemi de tek bir işlem olarak ele alacağı için herhangi birisi gerçekleşmediği zaman diğer gerçekleşen işlemleri de yok sayacaktır. 

Yani gerçekleşen işlemi geri alacaktır(rollback).

Eğer işlemlerin tamamı sorunsuz bir şekilde gerçekleşirse, tüm İşlemleri kalıcı(commit) hale getirecektir.
 
Çoğu database gibi redis de transaction yönetimini destekler. Transaction kullanarak, birden fazla redis commandini gruplayabilir, böylece bu commandlerin araya herhangi bir başka komut girmeden bir kerede hepsinin çalışacağını veya çalışmayacağını garanti edebilirsiniz. 

Ancak redis transcationu ile ilgili bilinmesi gereken en önemli noktalardan biri rollback olmamasıdır. Bunun anlamı transcation içerisindeki komutlardan biri fail olursa kalanlar çalışmaya devam eder.

Örneğin, syntaxi doğru ama çalıştırılırken hata alınacak bir komut bu tip bir hataya sebebiyet verebilir. Burada bilinmesi gereken önemli husus, komutlardan biri hatalı olsa dahi diğer komutlar çalışmaya devam eder.

Redis Handling Failure

Redis, ilişkisel databaselerin aksine ACID prensibiyle çalışmayan bir NoSql çözümü olduğu için ve verileri memoryde tuttuğu için, olası bir felaket senaryosunda veri kaybetmemek için kendimizi her türlü felaket senaryosuna hazırlamamız gerekir

1.Persistency Ayarlarını Doğrulamak
Herhangi bir sıkıntı sebebiyle veri kaybıyla karşılaşma ihtimalimize karşı güvenlik önlemlerimizden biri persistence ayarlarımız olabilir. Redis persistence’ı iki farklı opsiyon ile destekler. Snapshotting ve Append-only file
  • Snapshotting bizim belirlediğimiz belli zaman aralıklarıyla redisteki verinin snapshotını alır ve diske kaydeder. RDB snapshottingi db backupları gibi düşünebilirsiniz. Snapshotting özelliğiyle veri kaybetme olasılığınız her zaman vardır. RDB snapshotın mantığı belli aralıkla rediste belli sayıda key’in değişip değişmediğini kontrol ederek bu da göre snapshot almak üzerine kuruludur. Örneğin, bu aralığın 5 dakikada bir olduğunu varsayarkan ilk snapshottan sonraki 5 dakikalık aralığın 4. dakikasında server down olursa o 4 dakikaya kadar olan verileriniz henüz backup alınamadığı için kaybolur.
  • Append-only file ise redise gelen her commandı kaydeder yani real time’a yakın bir veri koruyuculuğu sağlar. Bu sayede herhangi sebepten bir veri kaybı yaşadığımızda bu opsiyonlar ile verilerimizi geri yükleyebiliriz.
2.Çöken Bir Master Makineyi Değiştirmek
Bazı zamanlar makinaların bir sebepten down olma durumu söz konusu olabilir. Bu sebep kötü makine, kötü memory belki de elektriklerin gitmesi bile olabilir. Ancak sebepleri bir yana, en nihayetinde redis serveri değiştirmemiz gerekecektir. Eğer bir grup redis serverını replicasyon ve persistence ile koşturuyor isek bu durumla mücadele edebilecek şansımız var demektir.

Bir örnek senaryo üzerinden ilerleyelim.

Master olarak hareket eden A makinamız, bir de slave olarak davranan B makinamız olsun. A bir sebepten dolayı ağdan kopmuş olsun ve bir de C makinamız olsun. Bu durumda plan gayet basit. B makinasına SAVE keywordü üzerinden güncel snapshot üretmesini söyleyeceğiz. Bu snapshotu C makinasına kopyalayacağız. Ve daha sonra redisi C makinasında ayağa kaldıracağız. Ve en sonunda B makinasına Cnin slaveyi olmasını söyleyeceğiz. Ya da bir alternatif yol olarak da slaveyi yeni mastera çevirmek isteyebilirz.

Her iki durumda da, Redis kaldığı yerden devam edebilecektir. Bundan sonraki tek işimiz, istemci yapılandırmamızı uygun sunuculara okumak ve yazmak için güncellemek ve isteğe bağlı olarak Redis’i yeniden başlatmamız gerekirse disk üstü sunucu yapılandırmasını güncellemektir

Redis Scaling
Scaling tek bir makinenin gücünün sınırlarını gördüğümüzde veri ve performans ihtiyacımız artmaya devam ediyorsak karşımıza çıkan en net çözümdür. Scaling out yani yatayda ölçekleme, processleri birden fazla makinaya dağıtıp süreçleri paralelde işletmek üzerine kuruludur. Dolayısıyla bu da dağıtık bir sistem inşa etmek etmek demektir. Rediste scalingi read ve write kapasitesini artırmak için ayrı çözümler olarak kulanabiliriz. Read kapasitesini artırabilmek için read-only slave serverlar ekleyerek yani replicasyon özelliğinden faydalanarak, Write kapasitesinin sınırlarını artırmak için de sharding yönetimi kullanabiliriz.

Redis Read Scale Etmek

1- Redis Replication

Replikasyon dediğimiz yöntem aslında ana master serverimizde tuttuğumuz verimizin diğer serverlarda kopyasının tutulmasıdır. Redis de ölçeklenmeye yardımcı olmak açısından replikasyonu desteklemektedir. Redis her ne kadar çok hızlı olsa da zaman zaman darboğaza düştüğü durumlar olabilir ve bu gibi durumlarda da replikasyon özelliği yardımımıza koşabilir.

2- Master-Slave Replikasyonu
Redis master-Slave replikasyonunu destekler. Yani bir master serveriniz ana redis serverınızdır ve diğer slaveler bu mastera subscribe olup masterda veri güncellendikçe kendilerini update eder ve orjinal veriye sahip olur.

Slave serverlar iki ana amaç için kullanılabilir. İlki redis master serverınızın yavaş çalıştığı durumlarda yükü bölmek ve sistemi hızlandırmak. Redis master, serverınızda çok fazla operasyon olduğunda read operasyonlarını slavelere dağıtıp yükü hafifleterek serverı hızlandırabilirsiniz. Tabi slavelere güncel veriyi dağıtacak olan node master node olduğundan writelar yine master node üzerinden ilerlemeye devam eder.

İkinci bir kullanım amacı ise handling failure. Eğer master nodeunuz herhangi bir sebepten çökerse,  slave nodedaki veriyi kullanarak yeni bir master node’a taşıyabilir ya da direkt slave node’u master olarak kullanabilirsiniz.



Bazen bir mastera çok sayıda slave bağlandığında da performans kayıpları olabilir. Aslında redis serverlarının temelde master veya slave olması arasında fark yok. Dolayısıyla bu durumda biz master node’a slaveleri bağlarken slavelere de diğer slaveleri bağlayabiliriz. Yani master’ın slaveyi, slave’in de başka bir slave’i olabilir. Bu durumda bütün veriyi slavelere master üzerinden değil, bir kısmına master üzerinden diğer kısmına diğer slaveler üzerinden dağıtabiliriz. Ayrıca redis append-only özelliği ile replikasyonu bir arada kullanırsak veri kaybını sıfıra indirgeyebiliriz.

3- Redis Read Scale
Redis serverlarımızı scale etmeden önce, performans attırmak için gözden geçirebileceğimiz tüm seçenekleri gözden geçirmeliyiz  eğer ki elimizdeki seçenekler tükendiyse sclale etme sürecine girmemiz yerinde olacaktır.

  • Eğerki küçük yapılar kullanıyorsak, ziplist sizeımızın çok büyük olmadığından emin olmamız gerekir.
  • Kullandığıımız veri yapılarının performans etkilerini gözden geçirmemiz gerekir.
  • Eğer ki redise cachelemek için büyük objeler gönderiyorsak bu objeleri compress etmek ve netword bandwidth’ini read ve writelar için azalmayı düşünmeliyiz.
  • Redis pipelining ve connection poolingi özelliklerini de kullanmayı unutmamalıyız.
Eğer performansa dair herşeyi yaptığımızı düşünüyorsak geriye bir tek scale etmek kalıyor  . Read performansını arttırmanın en temel ve basit yolu redise read-only slave serverlar eklemektir. Bu read-only slave serverlar master servera bağlı olup masterın replicalarını alıp real-time'a yakın bir sürede güncel olurlar. Yani siz master server’a bir data yazarsınız ve master da bunu slaveleriyle paylaşır. Burada önemli nokta master çökerse ne olacağıdır.

Bir slave mastera bağlandığında masterın snapshotu yani bağlandığı anda masterdaki verinin kopyası alınır ve slave’e gönderilir. Eğer birden fazla slave mastera bu sırada bağlanırsa tüm slavere ayrı snapshot çekilmesi yerine hepsine aynı snapshot gider ve bu performans açısından oldukça fayda sağlar. Ancak çok fazla slave mastera bağlandığı takdirde, master slave arasında trafik artışı ve performans problemi çıkabilir. Bunu azaltmanın yolu da master’a slave bağlarken slave’e de başka slaveler bağlamaktır. Yani master-slave-slave şekilde bir ağaç yapısı kurmaktır.

Redisin replicasyon ve failover durumları için kullanılabilmek için yeni bir toolu olan redis sentinel de göz önünde bulundurulabilir. Aslında Redis sentinel bir redis server modudur ve bu nodeda redis normal bir redis server olarak hareket etmez. Bunun yerine master ve slavelerin davranışını ve statusunu takip eden bir yapı olarak hareket eder. Masterin fail olması durumunda redis sentinel var olan slaveler arasından bir master seçebilir. Bu slave master seçildikten sonra sentinel tüm diğer slaveleri yeni master üzerinden bağlayacaktır.
Redis Write ve Memory Kapasitesini Scale Emtek
Replication yöneti ile redisin read’ini ölçekleyebileceğimizden bahsettik. Write’ların ise hala master node üzerinden yürümeye devam edeceğinden  de bahsettik. Peki write yükü artığında ve write’ı güncellememiz gerektiğinde ne yapmalıyız? Burada artık sharding devreye girer. Normal şartlarda shardingle memory kapasitemizi artırmayı düşünürüz ancak bu yöntem eğer ki makinemizin gücü son noktaya ulaştuysa write performansımızı artırmak için, geçerli bir yöntemdir. Yani redis üzerinde tek makina sınırlarına ulaştıysa artık bu memoryi ve dolayısıyla write’ı scale edebilmek için sharding metodunu kullanabiliriz.
Redis’de veri parçalama (partitioning), tüm verileri birden çok Redis örneğine bölme tekniğidir, böylece her örnek yalnızca anahtarların bir alt kümesini içerecektir
Eğer ki memory’i optimize etmek ve performansı maximize etmek için her şeyi denediysek ve elimizdeki makinenin sınırlarını gördüysek, artık verimizi farklı instancelara sharding etmenin vakti gelmiş demektir.
Ne Zaman Sharding Kullanmalıyız ?
Çok büyük dataları yönetiyorsak ve tek bir makinenin memorysiyle yetinmek yerine çok fazla bilgisayarın gücüne ihtiyaç duyuyorsak, hesaplama gücünü birden çok CPU, birden çok bilgisayar arasında ölçeklendirip ve ağ bant genişliğini kullanmak istiyorsak shardinge başvurabiliriz. Tabi verileri yatayda parçalara ayırmak yani sharding yapmak ve bunları ayrı makinalara koymak yani dağıtık bir sistem oluşturmak kulağa kazandıran bir yöntem gibi gelse de bunun bazı trade-offları var ve sistemleriniz dağıtık hale geldiğinde sorunlarınız da dağıtık hale geliyor. Rediste shardinge başvurmadan önce shardingin getireceği bazı sıkıntılardan bahsedebiliriz.

  • Sharding sonucu farkı instancelarda bulunan multiple keylerle operasyon yapamazsınız.Farklı 
  • redis instancelarında bulunan keyler ile transaction işlemi yapamazsınız.
  • Key bazlı partitioning yapamazsınız. Mesela çok büyük bir list veya sorted list objesini farklı instance’a koymak gibi.
  • Backup ve persistence yönetimi eskiye göre çok daha komplex olur. Çünkü çok daha fazla RDB ve AO fileları ile uğraşmanız gerekir  backup involves aggregation (merging) of the RDB files from many instances.
  • Runtimeda clusterınıza instance eklemek veya silmek data misbalancingleri oluşturur ki bunun çözümü de preshardingtir.

PRESHARDİNG YÖNTEMİ
Rediste sharding yapısını kullanmaya başladığınızda runtimeda instance eklemek ve silmek oldukça zordur. Ancak bunun üstesinden gelmek için kullanabileceğiniz teknikler de var. Bunlardan biri preshardingtir.

Preshardingin mantığı oldukça basittir. Bir makinada başlangıçta birden fazla redis instanceı oluşturursunuz ki bu 32 ya da 64e kadar çıkabilir çünkü redis oldukça lightweight bir üründür. Bu sayede data storageın büyümesi gerektiğinde ve redisin bunu handle etmesi gerektiğinde redis instancelarını bir makineden başka makineye taşımak mümkündür. Eğer ki bir adet redis serverınız varsa ve bir adet daha eklemeniz gerekiyorsa redis instancelarınızın yarısını diğer servera taşırsınız. Bu işlemi her redis instanceı bir servera karşılık gelene kadar yapabilirsiniz.
Redis Architecture
  1. Single Redis Instance
  2. Redis High Availability
  3. Redis Sentinel
  4. Redis Cluster
Single Redis Instance
Single Redis İnstance, Redis'in en basit dağıtımıdır. Kullanıcıların servislerini büyütmelerine ve hızlandırmalarına yardımcı olabilecek küçük örnekler oluşturup çalıştırmalarına olanak tanır. Ancak dezavantajlarıda vardır. Örneğin,Redis  fail veya unavailable olursa yapılan tüm istemci çağrıları başarısız olur ve bu nedenle sistemin genel performansını ve hızı düşer. Redis, verileri kalıcı kılmak için ayarlanmamışsa, yeniden başlatma veya yük devretme durumunda veriler kaybolur.

Redis High Availability

Secondaryler mainin replicalarıdır. Birden fazla olabilirler. Redisten okuma durumunda read operasyonları için ölçeklenmeye fayda sağlarlar ve main instancesi fail yada unavailable olduğunda onun yerine main olarak atanabilirler.

31 Mart 2023 Cuma

Aggregations (Avg,Min,Max,Sum)

PUT ders/_bulk
  {"index":{"_id":1}}
  {"ders_adi":"MsSql","ders_suresi":"60","sinif":"A1","tipi":"Database","eğitmen":"Kaan","mevcut":"40"}
  {"index":{"_id":2}}
  {"ders_adi":"Css","ders_suresi":"45","sinif":"A2","tipi":"Frontend","eğitmen":"Kaan","mevcut":"12"}
  {"index":{"_id":3}}
  {"ders_adi":"Html","ders_suresi":"45","sinif":"A2","tipi":"Frontend","eğitmen":"alican","mevcut":"27"}
  {"index":{"_id":4}}
  {"ders_adi":"Java","ders_suresi":"25","sinif":"B1","tipi":"Backend","eğitmen":"karam","mevcut":"21"}
  {"index":{"_id":5}}
  {"ders_adi":"C sharp","ders_suresi":"50","sinif":"A2","tipi":"Backend","eğitmen":"ayşe","mevcut":"25"}
  {"index":{"_id":6}}
  {"ders_adi":"Oracle","ders_suresi":"50","sinif":"B2","tipi":"Database","eğitmen":"ayşe","mevcut":"32"}
    {"index":{"_id":6}}
  {"ders_adi":"Jquery","ders_suresi":"50","sinif":"B3","tipi":"Database","eğitmen":"ayşe","mevcut":null}

AVG
ilgili fieldin ortalamasını alır. İlgili fieldda null değerler varsa onları missing ile 0 değeri verebiliyoruz.
GET ders/_search
{
  "aggs": {
    "ortalama": {
      "avg": {
        "field": "mevcut",
        "missing": 0
      }
    }
  }
}

MİN
GET ders/_search
{
  "aggs": {
    "minimum": {
      "min": {
        "field": "mevcut"
      }
    }
  }
}

MAX

GET ders/_search
{
  "aggs": {
    "maximum": {
      "max": {
        "field": "mevcut"
      }
    }
  }
}

SUM
Kann öğretmeninin sınıf mevcudunun toplamını alacağız.
GET ders/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "eğitmen": "Kaan"
        }}
      ]
    }
  },
  "aggs": {
    "ortalama": {
      "sum": {
        "field": "mevcut"
      }
    }
  }
}
.

30 Mart 2023 Perşembe

Id, Wildcard ve Prefix ile sorgulama

PUT ders/_bulk
  {"index":{"_id":1}}
  {"ders_adi":"MsSql","ders_suresi":"60","sinif":"A1","tipi":"Database"}
  {"index":{"_id":2}}
  {"ders_adi":"Css","ders_suresi":"45","sinif":"A2","tipi":"Frontend"}
  {"index":{"_id":3}}
  {"ders_adi":"Html","ders_suresi":"45","sinif":"A2","tipi":"Frontend"}
  {"index":{"_id":4}}
  {"ders_adi":"Java","ders_suresi":"25","sinif":"B1","tipi":"Backend"}
  {"index":{"_id":5}}
  {"ders_adi":"C sharp","ders_suresi":"50","sinif":"A2","tipi":"Backend"}


Id ile Sorgulama
Idsi 2 ve 3 olan kayıtları getir.
GET ders/_search
{
  "query": {
    "ids": {
      "values": [2,3]
    }
  }
}



Wildcard ile sorgulama.
Sonu tipi alanında sonu end ile biten kayıtları getir.
GET ders/_search
{
  "query": {
    "wildcard": {
      "tipi": {
        "value": "*end"
      }
    }
  }
}


Prefix ile sorgulama
Prefix wildcarda oranla daha performanslı ve kaynakları daha az kullanan bir yapıdır.


Sorting ve Pagination

PUT ders/_bulk
  {"index":{"_id":1}}
  {"ders_adi":"MsSql","ders_suresi":"60","sinif":"A1","tipi":"Database"}
  {"index":{"_id":2}}
  {"ders_adi":"Css","ders_suresi":"45","sinif":"A2","tipi":"Frontend"}
  {"index":{"_id":3}}
  {"ders_adi":"Html","ders_suresi":"45","sinif":"A2","tipi":"Frontend"}
  {"index":{"_id":4}}
  {"ders_adi":"Java","ders_suresi":"25","sinif":"B1","tipi":"Backend"}
  {"index":{"_id":5}}
  {"ders_adi":"C sharp","ders_suresi":"50","sinif":"A2","tipi":"Backend"}


Pagination

Array gibi çalışır. 0 dan başla ilk iki kaydı getir.

GET ders/_search
{
  "size": 2,
  "from": 0
}


Sorting

GET ders/_search
{
  "sort": [
    {
      "ders_adi.keyword": {
        "order": "desc"
      }
    }
  ]
}

.. 

Uri ve Dsl query

PUT ders/_bulk
  {"index":{"_id":1}}
  {"ders_adi":"MsSql","ders_suresi":"60","sinif":"A1","tipi":"Database","eğitmen":"Kaan","mevcut":"40"}
  {"index":{"_id":2}}
  {"ders_adi":"Css","ders_suresi":"45","sinif":"A2","tipi":"Frontend","eğitmen":"Kaan","mevcut":"12"}
  {"index":{"_id":3}}
  {"ders_adi":"Html","ders_suresi":"45","sinif":"A2","tipi":"Frontend","eğitmen":"alican","mevcut":"27"}
  {"index":{"_id":4}}
  {"ders_adi":"Java","ders_suresi":"25","sinif":"B1","tipi":"Backend","eğitmen":"karam","mevcut":"21"}
  {"index":{"_id":5}}
  {"ders_adi":"C sharp","ders_suresi":"50","sinif":"A2","tipi":"Backend","eğitmen":"ayşe","mevcut":"25"}
  {"index":{"_id":6}}
  {"ders_adi":"Oracle","ders_suresi":"50","sinif":"B2","tipi":"Database","eğitmen":"ayşe","mevcut":"32"}
    {"index":{"_id":6}}
  {"ders_adi":"Jquery","ders_suresi":"50","sinif":"B3","tipi":"Database","eğitmen":"ayşe","mevcut":null}

Yukarıdaki gibi bulk insert ile elasticsearche kayıtlarımızı insert ettik.

Uri Query
GET ders/_search?q=ders_suresi:45

DSL Query
Match ile search ederken arama kriterini nasıl yazarsak yazalım match eder. örn. JAVA,JaVA
GET ders/_search
{
  "query": {
    "match": {
      "ders_adi": "Java"
    }
  }
}

Term ile serach ederken arama kriterini küçük harflerle yazmamız gerekir.örn java
GET ders/_search
{
  "query": {
    "term": {
      "ders_adi": "Java"
    }
  }
}


İndex içerisindeki sadece belli alanları getirmek istiyorsak 
GET ders/_search
{
"_source": ["ders_adi","eğitmen"]
}

Compand Query:

AND Query
GET ders/_search
{
  "query": {
    "bool": {"must": [
      {"match": {
        "sinif": "A2"
      }},
      {"match": {
        "tipi": "Frontend"
      }}
    ]}
  }
}

OR Query
GET ders/_search
{
  "query": {
    "bool": {"should": [
      {"match": {
        "sinif": "A2"
      }},
      {"match": {
        "tipi": "Frontend"
      }}
    ]}
  }
}

Must Not Query 

Sinifi A2 olan veya tipi frontend olan kayıtların hiç birisi gelmeyecek
GET ders/_search
{
  "query": {
    "bool": {"must_not": [
      {"match": {
        "sinif": "A2"
      }},
      {"match": {
        "tipi": "Frontend"
      }}
    ]}
  }
}

AMA Query
Sinif A2 olan ama Tipi frontend olmayan kayılar.
GET ders/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "sinif": "A2"
        }}
      ],
      "must_not": [
        {"match": {
          "tipi": "Frontend"
        }}
      ]
    }
  }
}

Filter Query
Ders süresi 10 ile 50 arasında olan kayıtlar
GET ders/_search
{
  "query": {
    "bool": {"must": [
      {"match_all": {}}
    ],
    "filter": [
      {"range": {
        "ders_suresi": {
          "gte": 10,
          "lte": 50
        }
      }}
    ]  
      
    }
  }
}


Ders tipi frontend olan, sinifi A2  olan ve ders süresi 10-50 arsında dersleri getir.
GET ders/_search
{
  "query": {
    "bool": {"must": [
      {"match": {
        "tipi":"Frontend"
      } },
      {
        "match": {
          "sinif": "A2"
        }
      }
    ],
    "filter": [
      {"range": {
        "ders_suresi": {
          "gte": 10,
          "lte": 50
        }
      }}
    ]
    }    
  }
}
.

Elasticsearch Optimistic Concurrency Control

Elasticsearch distributeddır. Bir document üzerinde  created,updated yada deleted operasyonları olursa documentin yeni versionu clusterdaki diğer replica nodelara gönderilir. Documentin eski bir versionun daha yeni bir versionu overwrite etmemesi için document üzeride gerçekleştirilen her işleme(CRUD), bu değişikliği koordine eden primary shard tarafından bir seq_no atanır. seq_no her operasyonda artar ve böylece daha yeni operasyonların eski operasyonlardan daha yüksek bir sıra numarasına sahip olması garanti edilir. Operasyonlar sırasında bu seq_no bilgisinide kullanarak eski versionların yeni versionları overwrite etmesi önlenmiş olur.

PUT products/_doc/1567
{
  "product" : "r2d2",
  "details" : "A resourceful astromech droid",
  "stock": 5
}

Yukarıdaki bir komut dizini product documenti oluşturacak ve ona primary_term ve seq_no atayacaktır.


Bir documenti get yaptığımızda bize belgenin en son seq_no ve primary_term değerleri gelir. Biz bu document üzerinde değişiklik yaptığımızda bu seq_no ve primary_term bizden önce değişikliğe uğramış mı onu kontrol ederiz. Bunuda if_seq_no ve if_primary_term paremetrelerini kullnarak yaparız. örneğin bizden önce birisi bu kayıt üzerinde işlem yaptıysa seq_no değişmiştir. Ozaman aşağıdaki işlem başarısız olur. Fakat kimse işlem yapmadıysa başarılı olur.

POST products/_update/1567?if_seq_no=0&if_primary_term=1
{
  "doc":{"stock": 4}
}

Yukarıdaki komutu stock bilgisinde değişiklik yaparak tekrar çalıştırırsak conflict hatası alırız. Çünkü artık seq_no değeri değişti.

POST products/_update/1567?if_seq_no=0&if_primary_term=1
{
  "doc":{"stock": 3}
}

29 Mart 2023 Çarşamba

Elasticsearch index,document ve mapping oluşturma

http://localhost:5601/app/dev_tools#/console


Create : ders isminde bir index ve buna ait bir document oluşturuyoruz.

PUT ders/_doc/1
{
  "ders_adi":"Css",
  "ders_suresi":60
}

.

Get: 1 nolu documetin bilgilerii getirir.

GET ders/_doc/1


Update:

POST ders/_update/1
{
  "doc":{"ders_suresi":45}  
}

Mapping: Schema oluşturmamız sağlar.

Elasticsearch schema-free bir yapıda iken neden her doküman bir type’a ait ve her type shcema gibi bir mapping içerir?

Elasticsearch schema-free’dir çünkü hiç bir şemaya sıkı sıkıya uymak durumunda değil ve tüm alanları içermek zorunda değildir.Üstüne üstlük documenetlar schemada olmayan bir alan da içerebilirler. Mapping ise o ana kadar indexlenmiş datalardaki en geniş property kapsamını tanımlar ve yeni bir alan geldiğinde elasticsearch otomatik olarak yeni alanı mappinge ekler. Bunu yaparken kendisi alanın tipini belirleyip daha doğrusu tahmin edip o şekilde ekler. Örneğin, değer 7 ise bunu long olarak varsayar. Ancak bu bazı durumlarda sıkıntı çıkarır.  Aslında alan string ise ve ilerleyen zamanda “hello world” eklemeye kalkarsanız fail olur. Bu sebeple productiondaki en safe yol mappingi indexlemeden önce belirlemek olacaktır.
PUT /ders
{
  "mappings": {
    "properties": {
        "ders_adi":{"type": "text"},
        "ders_suresi":{"type": "integer"},
        "eğitmen":{
          "properties": {
            "adi":{"type":"text"},
            "soyadi":{"type":"text"},
            "yasi":{"type":"integer"}           
          }
        }
    }
  }
}

oluşturduğumuz mappinge ulaşmak için 

GET ders/_mapping

mapping içerisindeki belli bir propertye ulaşmak için

GGET ders/_mapping/field/eğitmen.yasi

Delete İndex

DELETE /ders

Elk Docker Kurulum

docker-compose.yml isminde bir dosya oluşturup içerisine aşağıdaki komutları yapıştırıyoruz. 

"docker-compose up" ile çalıştırıyoruz

http://localhost:5601/app/dev_tools#/console

Yukarıdaki adresten kibana ya bağlanıyoruz.

version: '3.6'
services:
  Elasticsearch:
    image: elasticsearch:7.16.2
    container_name: elasticsearch
    restart: always
    volumes:
    - elastic_data:/usr/share/elasticsearch/data/
    environment:
      ES_JAVA_OPTS: "-Xmx256m -Xms256m"
      discovery.type: single-node    
    ports:
    - '9200:9200'
    - '9300:9300'
    networks:
      - elk

  Kibana:
    image: kibana:7.16.2
    container_name: kibana
    restart: always       
    ports:
    - '5601:5601'
    environment:
      - ELASTICSEARCH_URL=http://elasticsearch:9200  
    depends_on:
      - Elasticsearch  
    networks:
      - elk
volumes:
  elastic_data: {}

networks:
  elk:
..

Elastic Search

12 Mart 2023 Pazar

MongoDB

Schema Format

Github MongoDB Example

MongoDB'de Schema tasarımı ölçeklenebilir, hızlı ve uygun fiyatlı bir veritabanı oluşturmanın en kritik parçasıdır. Doğru schema tasarımı MongoDB yönetiminde en çok gözden kaçan durumlardan birisidir. Relational schema tasarımından sonra MongoDByi öğrenenler aynı relational schema tasarım gibi düşünerek schemaları tasarladığında MongoDB'nin sunduğu avantajlardan tam olarak yararlanamamaktadır. 

Schema Design Relational vs. MongoDB

MongoDB schema tasarlarken iç güdüsel olarak verilerimizi küçük tablolara bölmek isteriz. Bu gayet normal bir durumdur fakat schemaları bu şekilde tasarladığımızda MongDB'nin gerçek faydasını göremeyiz. Aşağıda farkları göreceğiz.

Relational Schema Design
Relational schema tasarlarken, developerlar genellikle schemaları querylerden bağımsız olarak modeller. Hangi dataların olduğunu belirleriz ve veri tekrarını önlemek için 3rd normal form ile verileri tablolara ayırırız.(Referencing)


Yukarıdaki örnekte User dataları ayrı Professions ve Cars olarak iki ayrı tabloya ayrılmıştır ve user_id foreingkey ile birbirine bağlanmıştır. Bu foreingkey kullanılarak veriler joinleyebiliriz.

MongoDB Schema Design
MongoDB schema tasarımı,relational schema tasarımından çok farklı çalışmaktadır.
  • Birlikte Erişilmesi Gereken Dataları Birlikte Depola. Tek bir MongoDB documentin boyutu 16 MB'ı aşamaz. Dizi alanlarını kullanarak birden çok documenti embedded etmek mümkün ve yaygın olsa da, nesnelerin listesi kontrolsüz bir şekilde büyürse document bu boyut sınırına hızla ulaşabilir. Ek olarak, embedded arraylerde büyük miktarda veri depolamanın sorgu performansı üzerinde büyük etkisi vardır.
  • Relational verilere seyrek erişim, genellikle documentleri embedded etmemeniz gerektiğine dair başka bir ipucudur.
  • Relational kurulurken eğer ki relation olan datanın boundarysi belliyse embedded uygulanabilir. Örneğin bir kişinin en fazla 2 yada 3 emaili olabilir. Bu gibi miktarı belli olan dataları embedded tutabiliriz. Fakat boyutu sürekli olarak artabilecek dataları ayrı bir schemada tutmalıyız.
MongoDB de schema desing ederken önemli olan tek şey applicationımız için iyi çalışan bir schema tasarlamaktır. Bu veritabanına yapacağımız queryleri düşünmek her zaman önceliğimiz olmalıdır. Sorgu ihtiyacına göre veritabanını tasarlamalıyız.
  • Store the data
  • Provide good query performance

Verilerimizi ayrı collections yada documentslere bölmek yerine, MongoDBnin document based tasarımından yaralanarak User objesinin içerisinde array ve obje olarak tanımlıyoruz.(Embedding) Artık tek bir query ile usera ait tüm datalara ulaşabiliyoruz.


Embedding vs. Referencing
MongoDB ile dataları embeded olarak yada reference kullanarak $lookup operatorü yardımıyla join edecek şekilde kurgulayabiliriz.

Embedding
denormalized data schema olarak bilinir.
Advantages:
  • İhtiyacımız olan tüm dataları tek bir query ile elde edebiliriz.
  • Kodumuz içerisinde join yada $lookup kullanmaya ihtiyacımız kalmaz.
  • Data read ve update için single database operation yeterlidir.
Limitations:
  • Dublicate dataya neden olur.
  • Large documentler daha fazla yük anlamına gelir.
  • MongoDB'de 16 MB document boyutu sınırı vardır. Tek bir documente çok fazla veri embeded ediyorsanız , potansiyel olarak bu sınıra ulaşabilirsiniz. 
Ne zaman kullanabiliriz?
  • Entityler arasında "contains" yada "has-a" relationship varsa kullanabiliriz.
  • Entityler arasında one-to-one, one-to-few  yada one-to-many ilişkisi varsa kullanabiliriz.

Referencing
Schema tasarlarken diğer bir seçenekte Unique Object Id kullanarak documentlerin bir birine $lookup operatörüyle bağlamaktır.. Referencing sql querydeki join operatörüne benzer şekilde çalışır. Daha verimli ve ölçeklenebilir queryler yapmak için verileri bölmemize ve aynı zamanda veriler arasındaki ilişkileri sürdürmemize olanak tanır. Normalized data schema olarakta bilinir.
Advantages:
  • Dataları split ettiğimiz için daha küçük documenlere sahip olacağız.
  • İhtiyacımız olmayan dataları query ile çekmemiş olacağız.
  • Dublicate olan veri miktarı azalmış olur. Fakat burada dublicate olduğu halde daha iyi bir schemaya sahip olacaksak verinin dublicate olduğu schemayı seçmeliyiz.(embedding)
Limitations:
  • Referenced documentte, ihtiyacmız olan dataları elde etmek için  minimum iki query ve $lookup kullanmamız gerekebilir.
Nezaman kullanabiliriz ?
  • many-to-many relationshiplerde kullanabiliriz.


Storing Together What Needs to be Accessed Together
Tipik bir relational databasede, veriler tablolarda tutulur. Örneğin üniversitedeki öğrencileri temsil eden bir tabloda, her öğrencinin adını, soyadını, doğum tarihini ve UniqId bilgisini tutarız. Her tablo single subjecti temsil eder. Bir öğrencinin mevcut çalışmalarını, burslarını veya önceki eğitim hayatıyla ilgili bilgileri depolamak istiyorsak, bu bilgileri ayrı bir tabloda tutmak mantıklı olabilir. Daha sonra bu tabloları bir birine foreingkey ile bağlayarak anlamlı bir connection sağlayabiliriz.

Örneğin, her öğrencinin burs durumunu açıklayan bir tablo, öğrencilere ait tc numarayı refere edebilir. Bu burs tablosuna öğrencinin adını veya adresini yada daha farklı bilgilerini  eklememize gerek kalmaz. Böylece veri tekrarını önlemiş oluruz. Öğrenciye ait tüm bilgileri almak istediğimizde bu foreingkeyler kullanılarak joinleme işlemi yapılır ve data elde edilir. Referanslar aracılığıyla ilişkileri tanımlamanın bu yöntemi, normalized data model olarak bilinir. Verileri bu şekilde depolamak - birbiriyle ilişkili birden çok ayrı, özlü nesne kullanmak - document-oriented veritabanlarında da mümkündür. Document-oriented veritabanları yukarıdaki yapılara ek olarak bu dataları embeded olarak da tanımlayabilme imkanı ve özgürlüğü vermektedir.

Document-oriented veritabanlarının altında yatan kavram "birlikte erişilecek olanı birlikte depolamaktır.". Üniversitenin, her öğrencinin iletişim bilgileriyle birlikte birden fazla e-posta adresini kaydetmek istediğini düşünelim.


Yukarıdaki örnekte document bir embedded email adress listesi içermektedir.
Tek bir documentte birden fazla konuyu temsil etmek, denormalize edilmiş bir veri modelini karakterize eder. Uygulamaların, birden fazla ayrı nesneye ve koleksiyona erişmeye gerek kalmadan, belirli bir nesne (burada bir öğrenci) için ilgili tüm verileri tek seferde almasına ve değiştirmesine olanak tanır. Bunu yapmak, bütünlüğü garanti etmek için çoklu belge işlemlerini kullanmak zorunda kalmadan böyle bir belge üzerindeki işlemlerin atomikliğini de garanti eder.

Embedded document kullanılarak birlikte erişilmesi gerekenleri bir arada depolamak, genellikle verileri document-based bir veritabanında temsil etmenin en uygun yoludur.

One-to-One
Bir nesnenin, başka bir türden nesneyle arasındaki bağlantıyı ifade eder. Yani iki farklı nesne arsındaki ilişkidir.
one-to-one verileri key-value pairs olarak modelleyebiliriz.

Her öğrencinin bir adet kimlik kartı vardır. Bir kart asla birden fazla öğrenciye ait olamaz ve hiç bir öğrencinin birden fazla kimlik kartı olamaz.Kimlik kartı öğrenci olmadan anlamsızdır.


Bu örnek belgenin id_card alanında, tek bir değer yerine, öğrencinin kimlik numarası, kartın veriliş tarihi ve kartın son kullanma tarihi ile açıklanan kimlik kartını temsil eden embedded document bulunduğuna dikkat edin. Kimlik kartı, gerçek hayatta ayrı bir nesne olsa da esasen öğrenci Sammy'yi tanımlayan belgenin bir parçası haline gelir. Genellikle, belge şemasını bu şekilde yapılandırmak, böylece ilgili tüm bilgileri tek bir sorgu aracılığıyla alabilirsiniz.


One-to-Few
Bir öğrencinin bir kaç adresi olabilir, bir arabanın çok sayıda parçası olabilir veya bir alışveriş siparişi bir kaç  öğeden oluşabilir. Bu örneklerin her biri bire çok ilişkiyi temsil eder.
Örneğin belirli bir öğrenciyle ilişkili bir kaç e-posta adresi saklamamız gerekebilir. Uygulamamız için bir kullanıcının birkaç farklı e-posta adresinden  daha fazlasına sahip olması olası değildir. Bunun gibi ilişkileri one-to-few olarak tanımlayabiliriz.

one-to-one ilişkilerde embedded olarak tanımlayabiliyorken one-to-many ilişkileri modellemenin birkaç yolu vardır.

Cardinality:Belirli bir kümedeki bireysel öğelerin sayısıdır. Bir sınıfta 30 öğrenci varsa o sınıfın Cardinality ölçüsü 30 dur. one-to-many ilişkilerde "çok"un boyutu, verileri nasıl modelleyeceğinizi etkiler. Bu boundedın sınırları belli mi ? soracağımız soru bu olmalıdır.

Independent access: Bazı related verilere ana nesneden ayrı olarak nadiren erişilir. Örneğin bir öğrencinin e-posta adresine diğer bilgiler olmadan erişmek alışılmadık bir durumdur. Öte yandan, bir üniversitenin derslerine, bu derse kaydolan öğrencilerden bağımsız olarak erişilmesi ve güncellenmesi gerekebilir. Related bir documente tek başına erişip erişmeyeceğimiz, verileri nasıl modelleyeceğimizi etkiler.

Bu yapıyı kullanarak, bir öğrencinin documentini her aldığınızda, aynı okuma işleminde embeded e-posta adreslerini de alırsınız.


One-to-Many With Child Reference

Öğrenciler ve e-posta adresleri arasındaki ilişkinin doğası, bu ilişkinin bir document-based veritabanında en iyi şekilde nasıl modellenebileceği konusunda bilgi verdi. Öğrenciler ve katıldıkları dersler arasındaki ilişki arasında bazı farklılıklar vardır, dolayısıyla öğrenciler ve dersleri arasındaki ilişkileri modelleme şekliniz de farklı olacaktır.


Yukarıdaki gibi Öğrenci documenti içerisine coursesları embedded olarak eklediğimizi düşünelim. 
Cardinality olarak ele aldığımızda bir öğrenci bir den fazla course katılabilir ve yıllar içinde daha bir çok course  kayıt olabilir. Ve böyle çok sayıda kayıt eklenirse öğrenci documenti çok büyür ve hantallaşmaya başlayabilir.
Independent access olarak ele aldığımızda coursların listesini öğrencilerden bağımsız olarak kendi başlarına query etme ihtiyacımız olabilir.  Örneğin bir pazarlama broşürü oluşturmak istiyoruz ve bu broşüre course isimlerini yazdırmak istiyoruz. Bunlara ek olarak coursların zaman içerisinde güncellenmesi gerekecektir. Dersi veren profesör değişebilir, ders programı değişebilir veya ön koşullarının güncellenmesi gerekebilir.

Coursları, öğrenci içerisine embedded olarak ekleseydik, tüm coursların listesine ulaşmak zahmetli olurdu. Ayrıca bir coursenin güncellenmesi gerektiğinde tüm öğrenci kayıtlarını gözden geçirmemiz ve kurs bilgilerini her yerde güncellemeniz gerekir. Bunların hepsi courseyi ve öğrenciyi ayrı olarak modellememiz için geçerli bir nedendir.(don't embed).

Courses


Course bilgilerini bu şekilde depolamaya karar verirseniz, hangi öğrencilerin hangi kurslara katıldığını öğrenebilmeniz için öğrencilerle bu kurslar arasında bağlantı kurmanın bir yolunu bulmanız gerekir. İlgili nesnelerin sayısının çok fazla olmadığı bu gibi durumlarda, özellikle one-to-many ilişkiler söz konusu olduğunda, bunu yapmanın yaygın yollarından biri child referansları kullanmaktır.



Mümkün olduğu kadar joins/lookups dan kaçınmalıyız. Ancak yukarıdaki gibi Öğrenci ve Courslara ayrı ayrı erişmemiz gerekiyorsa tüm bu yapıları embedded etmek yerine  ayrı ayrı schemalara koymalıyız.

One-to-Many With Parent Reference

Child referansları kullanmak, doğrudan parent documentin içine gömmek için çok fazla related nesne olduğunda işe yarar, ancak miktar hala bilinen sınırlar içindedir. Ancak, related documentlerin sayısının sınırsız olabileceği ve zamanla artmaya devam edeceği durumlar vardır.

 Örneğin,Öğrencilerin bir çok mecraya mesaj gönderebileceği bir mesaj panosu olduğunu düşünelim. Bu mesaj bir subject ve bir bodyden oluşsun.


Öğrencinin gönderdiği tüm mesajları Öğrenci documenti içerisinde embedded olarak tuttuğumuzu düşünelim.

Yukarıdaki şekilde embedded olarak tanımlarsak, Eğer öğrenci sürekli mesaj gönderirse öğrenci documentinin 16mb sınırını aşma ihtimali olacaktır. Aynı zamanda mesaj sayfası panosu üzerinden öğrenciler tarafından en son gönderilen mesajlar gösterilmesi gerekirse mesajların öğrenciler olmadan erişilebilir olması gerekebilir. Ayrıca öğrenci documentine her eriştiğimiz de bu mesajların yüklenmesi gerekmiyorsa bu mesajların öğrenci documentten ayrı modellenmesi daha yerinde bir karar olacaktır.

Bir başka çözüm olarakta child referenceleri kullanalım ve burada öğrenci documenti içerisinde mesajlara ait objectIdyi refere olarak bir collectionda tutalım.


Bu örnekte, message_board_messages alanı artık Sammy tarafından yazılan tüm iletilere yapılan child referenceleri depolar.Buna ek olarak artık mesajlara öğrenciden  bağımsız olarak erişmek mümkün olacaktır. Ancak, child reference yaklaşımı kullanılarak öğrencinin document boyutu yavaş yavaşta olsa büyüyecektir.Öğrenci 4 yıl boyun binlerce mesaj yazarsa bu öğrenci documenti 16MB  sınırını aşabilir. Bunlara ek olarakda boyut büyüdükçe Öğrenci sınıfı hanstallaşacaktır ve performans sorunu doğacaktır.

Bu tür durumlarda child referencenin tam tersi olarak mesaj documenti üzerinde öğrenciye ait reference bulanacak ve böylece parent reference olacaktır.

Parent referansları kullanmak için, mesajı yazan öğrenciye bir referans içerecek şekilde mesaj document şemasını değiştirmeniz gerekir:


Yeni posted_by fieldı öğrencinin documentinin nesne tanımlayıcısını içerdiğine dikkat edin. Artık öğrenci documenti, gönderdikleri mesajlarla ilgili herhangi bir bilgi içermeyecek:


Bir öğrenci tarafından yazılan mesajların listesini almak için, mesaj koleksiyonunda bir query atar ve post_by fieldına göre filtreleme yaparsınız. Mesaj ve Öğrenciyi ayrı schemalara koyarak öğrencinin gönderdiği mesaj listesinin büyümesi öğrenci doumentini hiç bir şekilde etkilemeyecektir.

Not: Documentlere bağımsız olarak erişilmesi gerekip gerekmediğine bakılmaksızın, ilgili documentin miktarının sınırsız olduğu one-to-many ilişkiyi modellerken , genellikle ilgili documentleri ayrı ayrı saklamanız ve bunları parent documente  bağlamak için parent referansları kullanmanız önerilir.

Many-to-Many
Bir to-do uygulaması olduğu düşünelim. Bir userın many task alabileceğini ve bir taskın many usera atanabileceğini düşünelim. 


Her User task isminde bir sub-arraye sahiptir. Aynı şekilde her Taskta owners isminde bir sub-arraye sahiptir.

Many-to-many Embedded Example
Product ve Customer arasında many-to-many ilişkisi olduğu düşünelim. Bunlar arasında Orders isminde bir tablo tutulur ve bu tablo Customer içerisinde embedded olarak eklenir. Bu şekilde yapılan bir tasarımda productın fiyatı güncellense bile biz hala sipariş verdiğimiz zamanki priceyi kaydetmiş oluyoruz. Bunun bize avantajı bu oluyor.



Use Indexes For Frequent Operations

MongoDB de verileri daha küçük parçalara bölmek(tablo) yerine birlikte erişilen parçları embedded tasarladığımız için bu documentlerin boyutunun oldukça büyük olmasıda normaldir. Bu durum doğal olarak performansı etkileyecektir, ancak indexlerle bunu çözebiliriz. MongoDB'deki indexler, relational veritabanlarıyla hemen hemen aynı şekilde çalışır. Bu özel veri structlar , sık kullanılan queryler için veri eşleşmesini(matching) hızlandırmak amacıyla tüm documentin küçük bir subsetini depolar.

Örneğin User datalarıyla order historysini tek bir document içerisinde embedded olarak tuttuğumuzu ve geçen ay X ürününü sipariş etmiş kullanıcıları bulmak istediğimizi düşünelim. Normalde (indeksler olmadan) MongoDB'nin tüm User collectionları  taraması, User documentlerden birer birer geçmesi ve her User için son sipariş verilerini kontrol etmesi gerekir. Bu korkunç değil; veritabanı bu şekilde birçok işlemi gerçekleştirir. Ancak veritabanından bu tür bir eşleştirmeyi sık sık sorarsanız, indeksleme size çok yardımcı olacaktır. Örneğimize geri dönersek - indexlerle MongoDB, verilere işaretçiler içeren(pointer to data ) ayrı, küçük bir liste depolar.(örneğin, kullanıcı kimliği, e-posta adresi veya son sipariş tarihi).