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).