MongoDB Kurulum, Konfigürasyon ve Basit CRUD işlemleri (MongoDB Öğreniyoruz 2)

Murat Çabuk
17 min readAug 8, 2022

Merhaba arkadaşlar

Makale serisinin diğer yazıları için alttaki linkleri kullanabilirsiniz.

MongoDb bütün işletim sistemlerine kurmak mümkün, resmi sayfasından Community Edition kuruluma ilgili bütün detaylara ulaşabilirsiniz. İnternette yapacağınız ufak bir araştırmayla Türkçe kurulum dokümanlarına da erişebilirsiniz.

  • Ayrıca isterseniz hiç MongoDb kurulumu yapmadan ilgili konu başlıklarında vereceğim linklerdeki eğitim amaçlı hazırlanmış online console’ları da kullanabilirsiniz.
  • Yada MongoDB Atlas’ın ücretsiz olan versiyonu için sayfasından hesap açarak kullanabilirsiniz. Atlas sayfasına şu linkten ulaşabilirsiniz.
  • Docker üzerinde çalışmak isterseniz Linux makineler için alttaki komutu çalıştırabilirsiniz.

port değiştirmek isterseniz ayrıca -p 27018:27017 eklemelisiniz. 27018 portunu istediğiniz bir portla değiştirebilirsiniz.

docker run -d --restart unless-stopped --name mongodb  -e MONGO_INITDB_ROOT_USERNAME=adminuser 	-e MONGO_INITDB_ROOT_PASSWORD=Abc123  mongo:5.0-focal
  • Windows makinelerde Docker ile çalıştırmak için alttaki komutu çalıştırabilirsiniz.
docker run -d --restart unless-stopped --name mongodb -e MONGO_INITDB_ROOT_USERNAME=adminuser 	-e MONGO_INITDB_ROOT_PASSWORD=Abc123  mongo:5.0-windowsservercore
  • Son olarak özellikle transaction başlığında göreceğimiz bazı konular için sharded bir cluster’a ihtiyacımız olacak. Amacımız database administrator’lük olmadığı için detaylarına girip vakit kaybetmemek adına konfigürasyonu yapılmış hazır bir docker compose kurulumu yapacağız. Bu kurulumu yapmak zorunda değilsiniz anlatacağım konuları makaleyi takip ederek de rahatlıkla anlayabilirsiniz. Ancak tabii ki benim test edebilmek için bu versiyonu kurmam gerekiyor.

Bu kurulum için Bitnami image’ını ve compose-file’lını kullanacağız.

git clone https://github.com/bitnami/bitnami-docker-mongodb-sharded.git

cd bitnami-docker-mongodb-sharded

docker-compose -f docker-compose-multiple-shards.yml up -d

En son makaleye kadar bütün yapacaklarımızı MongopDB Shell ile yapacağız. Bu nedenle bunu da kurmamız gerekiyor.

Bunun için MongoDB sayfasından işletim sisteminize göre indirip kurabilirsiniz. MongoDB Shell Node.js ile yazılmış Javascript ile tamamen uyumlu bir uygulamadır. Daha detaylı bilgi almak ve shell’in yeteneklerini öğrenmek için MongoDB Shell resmi sayfasını ziyaret edebilirsiniz.

Kurumunuzu yaptıktan sonra terminal, command prompt, powershell vb artık ne kullanıyorsanız alttaki komutla bağlanabilirsiniz. Bağlantı yaptıktan sonra alttaki gibi bir sonuç görüyor olmanız lazım.

mongosh "mongodb://localhost:27017"# sonuç olarak# Current Mongosh Log ID: 62cc73d0d9b7e3f4b66fb82e
# Connecting to: mongodb://localhost:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.5.0
# Using MongoDB: 5.0.9
# Using Mongosh: 1.5.0

# For mongosh info see: https://docs.mongodb.com/mongodb-shell/

# [direct: mongos] test>

Ancak bu şekliyle örneğin veritabanlarını listelemeye alıştığımızda hata alırız çünkü login olmadık.

show databases

//MongoServerError: command listDatabases requires authentication
  • Eğer MongoDB’yi doğrudan kurduysanız yani Docker kullanmadan kurduysanız, giriş yaptıktan sonra alttaki komutlarla admin kullanıcısı oluşturup MongoDb’yi yeniden başlatmalısınız.
use admin
db.createUser(
{
user: "adminuser",
pwd: "Abc123",
roles: [ { role: "userAdminAnyDatabase", db: "admin" },
{ role: "dbAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" } ]
}
)

Daha sonra alttaki komutla tekrar bağlanıp admin kullanıcımıza rollerini atamalıyız.

Giriş yaptıktan sonra alttaki komutla adminuser kullanıcısına yetkilerini vermiş oluyoruz.

mongosh "mongodb://localhost:27017" -u "adminuser" -p "Abc123"   --authenticationDatabase "admin"

Giriş yaptıktan sonra alttaki komutla adminuser kullanıcısına yetkilerini vermiş oluyoruz.

use admin
db.grantRolesToUser(
"myUserAdmin",
[ { role: "userAdminAnyDatabase", db: "admin" },
{ role: "dbAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" } ]
)
  • Eğer MongoDB’yi Docker ile kurduysanız zaten environment variable (-e MONGO_INITDB_ROOT_USERNAME=adminuser -e MONGO_INITDB_ROOT_PASSWORD=Abc123) olarak kullanıcı adı şifre vermiştik.
mongosh "mongodb://localhost:27017" -u "adminuser" -p "abc123" --authenticationDatabase "admin"
  • Eğer Docker compose ile cluster versiyonu kurduysanız zaten compose dosyasında kullanıcı adı ve şifre yazıyor.
mongosh "mongodb://localhost:27017" -u "root" -p "password123" --authenticationDatabase "admin"

Evet artık veri tabanlarını listeleyebiliriz.

show dbs

//admin 172.00 KiB
//config 4.03 MiB

MongoDB json kullandığı json dosyalarınızı düzenlemek için editör kullanabilirsiniz. Shell içinde edit komutu ile doğrudan kullandığınız editor ile çalışma yapabilirsiniz. Bunun için editörümüzü ayarlayalım.

config.set( "editor", "code" )

Eğitim boyunca tamamen MongoDB dokümanlarını takip edeceğiz. İlgili başlıklarda gerekli verililer de mevcut. Ayrıca isterseniz hiç MongoDb kurulumu yapmadan vereceğim linklerdeki eğitim amaçlı hazırlanmış online console’ları da kullanabilirsiniz.

Takip edeceğimiz dokümanları ana sayfası için şu linki ziyaret ediniz.

Ana başlıklar şu şekilde. Kırmızı kutu içindeki başlıklara kurguyu anlyacak kadar gireceğiz. Security kısmında ise kullanıcı ekleme ve role bazlı yetkşlendirme yapacak kadar gireceğiz. Amacımız bütün dokümanı uygulamak değil konsepti anlayıp yolumuz bulacak kadar öğrenmeye çalışacağız. Yani özetle balık tutmayı öğreneceğiz.

Developer bakış açısıyla devam edeceğiz yani amacımız administrative işler yapmak değil bu nedenle bol kod/query yazacağız. Son makalemizde Python ve Dotnet Core ile nasıl MongoDB kullanıldığını da göreceğiz. Shell ile yaptıklarınızdan çok farklı bir şey olmadığını şimdiden söyleyebilirim.

Evet artık çalışmaya başlayabiliriz. Bundan sonra yapacağımız bütün örneklerde kullanmak amacıyla bir database oluşturacağız.

use mymongodb

MongoDB’de database oluşturmak için aktif database değiştirmek yeterli.

Yukarıda bahsettiğimiz gibi MongoDB’nin kullanıdı veri yapısı json. Bu nedenle eğer json hakkında bilginiz yoksa bir sonraki makaleye geçmeden önce biraz çalışma yapınız. Youtube dahil birçok sitede kaynak bulabilirsiniz.

MongoDB’de Basit CRUD İşlemleri

MongoDB’de standart json’daki veri tiplerinden daha fazlası vardır. Bir nevi json’ın extend edilmiş halidir. Bütün BSON tiplerini görmek için şu sayfayı ziyaret ediniz.

Insert Document

Şu linkten insertOne ve InsetMany fonksiyonlarını inceleyeceğiz.

Daha önce oluşturduğumuz mymongodb veritabanına geçiş yapıp devam ediyoruz.

  • insertOne

Veri kaydetmek için bir collection’a (tablo) ihtiyacımız olacak. Bunun için sadece veri girişi yapacağımız collection adını vermemiz yeterli. Doküman kaydetmek için json kullanıyoruz. Süslü parantezle başlayıp biten alan dokümanımızı gösteriyor.

db.products.insertOne( { item: "card", qty: 15 } );

// [direct: mongos] mymongodb> db.products.insertOne( { item: "card", qty: 15 } );
// {
// acknowledged: true,
// insertedId: ObjectId("62cdd2893b4052b84cf2a4db")
// }

İleride daha detaylı göreceğiz ama şu an bilmesek de kaydettiğimiz dokümanı görmek için find fonksiyonunu kullanalım.

db.products.find()

//[
// { _id: ObjectId("62cdd2893b4052b84cf2a4db"), item: 'card', qty: 15 }
//]

MongoDb’de otomatik olarak _id adında bir alan oluşturulur bu alana unique olmak kaydıyla kendi değerimizi de yazabiliriz. Primary key olarak kullanılır.

db.products.insertOne( {_id:"unique_id", item: "pen", qty: 5 } );

// { acknowledged: true, insertedId: 'unique_id' }

Tekrar verileri kontrol edelim. Artık iki dokümanımız var.

db.products.find()

// [
// { _id: ObjectId("62cdd2893b4052b84cf2a4db"), item: 'card', qty: 15 },
// { _id: 'unique_id', item: 'pen', qty: 5 }
// ]
  • insertMany

Aynı anda birden fazla dokümanı kaydetmek için kullanılır.

db.products.insertMany( [
{ item: "card", qty: 15 },
{ item: "envelope", qty: 20 },
{ item: "stamps" , qty: 30 }
] );

// {
// acknowledged: true,
// insertedIds: {
// '0': ObjectId("62cdd61a3b4052b84cf2a4dc"),
// '1': ObjectId("62cdd61a3b4052b84cf2a4dd"),
// '2': ObjectId("62cdd61a3b4052b84cf2a4de")
// }
// }
  • bulkWrite

Eğer amacınız aynı ancak birçok dokümanı eklemek, değiştirmek veya silmekse o zaman bu fonksiyonu kullanmalısınız. Örnek bir bulkWrite fonksiyonu. Detaylar için şu sayfayı ziyaret ediniz.

db.characters.bulkWrite(
[
{ insertOne :
{
"document" :
{
"_id" : 4, "char" : "Dithras", "class" : "barbarian", "lvl" : 4
}
}
},
{ insertOne :
{
"document" :
{
"_id" : 5, "char" : "Taeln", "class" : "fighter", "lvl" : 3
}
}
},
{ updateOne :
{
"filter" : { "char" : "Eldon" },
"update" : { $set : { "status" : "Critical Injury" } }
}
},
{ deleteOne :
{ "filter" : { "char" : "Brisbane" } }
},
{ replaceOne :
{
"filter" : { "char" : "Meldane" },
"replacement" : { "char" : "Tanys", "class" : "oracle", "lvl" : 4 }
}
}
]
);
}

Update Document

Update metotları için şu sayfayı kullanacağız.

Öncelikle değiştireceğimiz dokümanı bulmak gerekiyor. Bunu standart SQL’de where clause ile yapıyorduk burada onun yerine filter kullanıyoruz.

  • updateOne

Önce birkaç doküman kaydedelim.

db.restaurant.insertMany( [
{ "_id" : 1, "name" : "Central Perk Cafe", "Borough" : "Manhattan" },
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "Borough" : "Queens", "violations" : 2 },
{ "_id" : 3, "name" : "Empire State Pub", "Borough" : "Brooklyn", "violations" : 0 }
] );
// sonuç olarak
// { acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }

Name alanına göre update yapalım. Görüleceği üzere bir adet doküman match etmiş ve bu dokümanın da bir alanı değişmiş. Bir diğer dikkat etmemiz gereken kısımda $set operatörü, aynı SQL’deki set gibi bu da ilgili alanın değerini değiştirmek için kullanılır.

db.restaurant.updateOne(
{ "name" : "Central Perk Cafe" },
{ $set: { "violations" : 3 } }
);

// {
// acknowledged: true,
// insertedId: null,
// matchedCount: 1,
// modifiedCount: 1,
// upsertedCount: 0
// }

Olmayan bir kaydı değiştirmek isterken eğer kayıt yoksa eklenmesini istersek upset opsiyonunu kullanmalıyız.

db.restaurant.updateOne(
{ "name" : "olmayan kaydı update ediyorum" },
{ $set: { "violations" : 3 } },
{ upsert: true }
);

Verileri kontrol edelim. Görüleceği üzere en altta eklenen kayıt görülebilir.

db.restaurant.find()

// [
// {
// _id: 1,
// name: 'Central Perk Cafe',
// Borough: 'Manhattan',
// violations: 3
// },
// {
// _id: 2,
// name: 'Rock A Feller Bar and Grill',
// Borough: 'Queens',
// violations: 2
// },
// {
// _id: 3,
// name: 'Empire State Pub',
// Borough: 'Brooklyn',
// violations: 0
// },
// {
// _id: ObjectId("62cde5b534e0f13b7eae76dd"),
// name: 'olmayan kaydı update ediyorum',
// violations: 3
// }
// ]

UYARI: Sharded distributed cluster yapısında bir dokümanı update ederken eğer upsert kullanmak isterseniz filtreye full sharded key’i de eklemek gerekiyor [ kaynak].

Numeric bir alanın değerini arttırmak istiyorsak $inc operatörünü kullanmalıyız. Diğer Field Update operatörleri için şu sayfayı ziyaret ediniz.

Aynı anda adı Central Perk Cafe olan restorandın hem violation değerini 3 arttırıp bir de aslında olmayan Closed alanını eklemiş oluyoruz.

db.restaurant.updateOne(
{ "name" : "Central Perk Cafe" },
{ $inc: { "violations" : 3}, $set: { "Closed" : true } }
);

Kontrol etmek için alttaki kodu çalıştırıyoruz.

db.restaurant.find({_id:1})

// [
// {
// _id: 1,
// name: 'Central Perk Cafe',
// Borough: 'Manhattan',
// violations: 6,
// Closed: true
// }
// ]

Dokümandan bir alanı çıkartmak için de $unset operatörünü kullanıyoruz.

db.restaurant.updateOne( { "name": "Central Perk Cafe" },  [{$unset: ["Closed"]}] );
// {
// acknowledged: true,
// insertedId: null,
// matchedCount: 1,
// modifiedCount: 1,
// upsertedCount: 0
// }

Kontrol ettiğimizde Closed alanının dokümandan silindiğini görebiliriz.

db.restaurant.find({"name": "Central Perk Cafe"})

// [
// {
// _id: 1,
// name: 'Central Perk Cafe',
// Borough: 'Manhattan',
// violations: 7,
// Review: true
// }
// ]
  • updateMany

Birden fazla dokümanı değiştirmek için kullanılır. Örneğin violation değeri 2'den büyük olan restoranlara review diye bir alan ekleyelim ve hepsinin violation değerini 1 arttıralım.

db.restaurant.updateMany(
{ violations: { $gt: 2 } },
{ $set: { "Review" : true }, $inc: { "violations" : 1} }
);

// {
// acknowledged: true,
// insertedId: null,
// matchedCount: 2,
// modifiedCount: 2,
// upsertedCount: 0
// }

Değişikliklere bakalım.

db.restaurant.find( { violations: { $gt: 2 } })

// [
// {
// _id: 1,
// name: 'Central Perk Cafe',
// Borough: 'Manhattan',
// violations: 7,
// Closed: true,
// Review: true
// },
// {
// _id: ObjectId("62cde5b534e0f13b7eae76dd"),
// name: 'olmayan kaydı update ediyorum',
// violations: 4,
// Review: true
// }
// ]

updateOne ile updateMany fonksiyonları arasında performans olarak bir fark yok denilebilir. Sadece updateMany’de multi opsiyonu true’dur ve tek bir doküman için hata olması durumunda updateOne tek doküman için daha faydalı bilgi verir.

Bir de document DB’lerin en büyük eksikliklerinden biri de data integrity konusunda zayıf olmaları. Bu nedenle updateOne kullandığınızda sadece bir dokümanın değiştiğinden emin olursunuz.

Delete Document

Delete işlemleri için şu sayfayı kullanıyor olacağız. İlgili başlığa tıklayarak detayları görebilirsiniz.

  • deleteOne

Daha önce restaurant collection’ınımıza kaydettiğimiz dokümanlardan birini _id’sine göre silelim. Bunun için öcelikle restaurantları sadece _id ve name alanlarını içerecek şekilde listeleyelim.

find fonksiyonunu içindeki boş süslü parantezler bir filtreleme yapmadığımızı gösteriyor. İkinci bölümde ise sadece name sütunu görmek istediğimizi söylemiş oluyoruz. 1 yerinde true da yazılabilirdi. _id sütununu da istemiyor olsaydık name gibi yazıp değerini -1 dememiz yeterli olacaktı.

db.restaurant.find({}, {name:1})

// [
// { _id: 1, name: 'Central Perk Cafe' },
// { _id: 2, name: 'Rock A Feller Bar and Grill' },
// { _id: 3, name: 'Empire State Pub' },
// {
// _id: ObjectId("62cde5b534e0f13b7eae76dd"),
// name: 'olmayan kaydı update ediyorum'
// }
// ]

_id: ObjectId(“62cde5b534e0f13b7eae76dd”) olan kaydı silelim.

db.restaurant.deleteOne({_id:ObjectId("62cde5b534e0f13b7eae76dd")});
//{ acknowledged: true, deletedCount: 1 }

Tekrar listeyi kontrol edelim.

db.restaurant.find({}, {name:1})

// [
// { _id: 1, name: 'Central Perk Cafe' },
// { _id: 2, name: 'Rock A Feller Bar and Grill' },
// { _id: 3, name: 'Empire State Pub' }
// ]
  • deleteMany
db.restaurant.deleteMany( { "_id" : { $gt : 1 } } );

//{ acknowledged: true, deletedCount: 2 }

Test edecek olursak tek doküman kaldığını görebiliriz.

db.restaurant.find({}, {name:1})

// [ { _id: 1, name: 'Central Perk Cafe' } ]

Read (Query) Document

Query metotları için şu sayfayı kullanacağız. Sayfa açıldığında örneklerin Compass üzerinde gösterildiğini görebilirsiniz.

Menüden Mongo Shell’i seçerek örnekleri görebilir hatta sanal shell üzerinde testlerinizi de yapabilirsiniz.

Query konusunda ayrıca tek başına detaylı göreceğimiz için burada basit bazı filtrelemeleri görüyor olacağız.

Öncelikle biraz kayıt girelim.

db.inventory.insertMany([
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);


// {
// acknowledged: true,
// insertedIds: {
// '0': ObjectId("62cf3797b8a5ff9793ca03e4"),
// '1': ObjectId("62cf3797b8a5ff9793ca03e5"),
// '2': ObjectId("62cf3797b8a5ff9793ca03e6"),
// '3': ObjectId("62cf3797b8a5ff9793ca03e7"),
// '4': ObjectId("62cf3797b8a5ff9793ca03e8")
// }
// }
  • Bütün Kayıtları Çekmek

İçerideki içi boş süslü parantezler bir filtreleme yapmadığımızı gösteriyor.

db.inventory.find( {} )
  • Eşitlik Şartı ile Filtreleme

Herhangi bir alanın değerine göre filtre yapmak için tek yapmamız gereken alan için beklediğimiz değeri json formatında yazmak.

db.inventory.find( {status: "D" } )

// [
// {
// _id: ObjectId("62cf3797b8a5ff9793ca03e6"),
// item: 'paper',
// qty: 100,
// size: { h: 8.5, w: 11, uom: 'in' },
// status: 'D'
// },
// {
// _id: ObjectId("62cf3797b8a5ff9793ca03e7"),
// item: 'planner',
// qty: 75,
// size: { h: 22.85, w: 30, uom: 'cm' },
// status: 'D'
// }
// ]
  • Query Operatörleri ile Filtreleme

Bütün Query Operatörleri için şu linki ziyaret ediniz.

Status alanının içereceği değerler için $in operatörü kullanılmış.

db.inventory.find( { status: { $in: [ "A", "D" ] } } )

// [
// kısaltıldı
// ,
// {
// _id: ObjectId("62cf3797b8a5ff9793ca03e5"),
// item: 'notebook',
// qty: 50,
// size: { h: 8.5, w: 11, uom: 'in' },
// status: 'A'
// },
// {
// _id: ObjectId("62cf3797b8a5ff9793ca03e6"),
// item: 'paper',
// qty: 100,
// size: { h: 8.5, w: 11, uom: 'in' },
// status: 'D'
// },
// kısaltıldı
// ]
  • AND Operatörü Kullanımı
SELECT * FROM inventory WHERE status = "A" AND qty < 30

Standart SQL’de yukarıdaki gibi yazılmış bir ifadenin MongoDB’de yazımı şu şekildedir.

db.inventory.find( { status: "A", qty: { $lt: 30 } } )
// veya and operatörünü açıkça yazarak
db.inventory.find( { $and: [ { status: "A" }, { qty: { $lt: 30 } } ] } )

// [
// {
// _id: ObjectId("62cf3797b8a5ff9793ca03e4"),
// item: 'journal',
// qty: 25,
// size: { h: 14, w: 21, uom: 'cm' },
// status: 'A'
// }
// ]
  • OR Operatörü Kullanımı
SELECT * FROM inventory WHERE status = "A" AND ( qty < 30 OR item LIKE "p%")

Standart SQL’de yukarıdaki gibi yazılmış bir ifadenin MongoDB’de yazımı şu şekildedir.

/^p/ ifadesi bir reqular expression ifadesidir. p harfi ile başlayan kelimeleri içerir. Regex hakkında daha detaylı bilgi için şu sayfayı ziyaret ediniz.

db.inventory.find( {
status: "A",
$or: [ { qty: { $lt: 30 } }, { item: /^p/ } ]
} )

// [
// {
// _id: ObjectId("62cf3797b8a5ff9793ca03e4"),
// item: 'journal',
// qty: 25,
// size: { h: 14, w: 21, uom: 'cm' },
// status: 'A'
// },
// {
// _id: ObjectId("62cf3797b8a5ff9793ca03e8"),
// item: 'postcard',
// qty: 45,
// size: { h: 10, w: 15.25, uom: 'cm' },
// status: 'A'
// }
// ]

Schema Kullanımı

Şimdi bu nereden çıktı diyebilirsiniz. Hani MongoDB’de schema zorunluluğu yoktu? Evet zorunluluk yok ancak seçenek olarak var. MongoDB json schema standartlarını destekler.

Amaç aslında verinin tutarlılığını sağlamak. Insert ve update işlemlerinde verinin belirtilen kurallara göre kaydedilip edilmediğini kontrol eder. Konu ile ilgili daha detaylı bilgi almak için şu sayfayı ve $jsonSchema operatörü için şu sayfayı ziyaret ediniz.

Schema collection create edilirken belirlenir. Örnek olarak ilgili sayfadan aldığımız aşağıdaki örneği test edelim.

Öncelikle collection’umuzu $jsonSchema operatörünü de kullanarak oluşturuyoruz.

db.createCollection("students", {
validator: {
$jsonSchema: {
bsonType: "object",
required: [ "name", "year", "major", "address" ],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
year: {
bsonType: "int",
minimum: 2017,
maximum: 3017,
description: "must be an integer in [ 2017, 3017 ] and is required"
},
major: {
enum: [ "Math", "English", "Computer Science", "History", null ],
description: "can only be one of the enum values and is required"
},
gpa: {
bsonType: [ "double" ],
description: "must be a double if the field exists"
},
address: {
bsonType: "object",
required: [ "city" ],
properties: {
street: {
bsonType: "string",
description: "must be a string if the field exists"
},
city: {
bsonType: "string",
description: "must be a string and is required"
}
}
}
}
}
}
})

Daha sonra students collection’ını üzerinde kurallarımızı test ediyoruz.

use students

// gpa alanı double olması gerekirken biz integer verelim.

db.students.insertOne( {
name: "Alice",
year: Int32( 2019 ),
major: "History",
gpa: Int32( 3 ),
address: {
city: "NYC",
street: "33rd Street"
}
} )

// aiağıdaki gibi bir hata alırız.

// Uncaught:
// MongoServerError: Document failed validation
// Additional information: {
// failingDocumentId: ObjectId("62d18e283d35a3afc00610a0"),
// details: {
// operatorName: '$jsonSchema',
// schemaRulesNotSatisfied: [
// {
// operatorName: 'properties',
// propertiesNotSatisfied: [ { propertyName: 'gpa', details: [ [Object] ] } ]
// }
// ]
// }
// }

Schema’ların bir ilginç yönü de bir collection üzerinde schema zorunluğu olmasa da schema’ya uygun dokümanların filtrelenmesine olanak tanımasıdır.

db.inventory2.insertMany( [
{ item: "journal", qty: NumberInt(25), size: { h: 14, w: 21, uom: "cm" }, instock: true },
{ item: "notebook", qty: NumberInt(50), size: { h: 8.5, w: 11, uom: "in" }, instock: true },
{ item: "paper", qty: NumberInt(100), size: { h: 8.5, w: 11, uom: "in" }, instock: 1 },
{ item: "planner", qty: NumberInt(75), size: { h: 22.85, w: 30, uom: "cm" }, instock: 1 },
{ item: "postcard", qty: NumberInt(45), size: { h: 10, w: 15.25, uom: "cm" }, instock: true },
{ item: "apple", qty: NumberInt(45), status: "A", instock: true },
{ item: "pears", qty: NumberInt(50), status: "A", instock: true }
] )

Daha sonra bir schema oluşturalım.

let myschema =  {
required: [ "item", "qty", "instock" ],
properties: {
item: { bsonType: "string" },
qty: { bsonType: "int" },
size: {
bsonType: "object",
required: [ "uom" ],
properties: {
uom: { bsonType: "string" },
h: { bsonType: "double" },
w: { bsonType: "double" }
}
},
instock: { bsonType: "bool" }
}
}

ve bunu filtrede kullanalım.

db.inventory2.find( { $jsonSchema: myschema } )

// [
// {
// _id: ObjectId("62d18fe83d35a3afc00610a6"),
// item: 'apple',
// qty: 45,
// status: 'A',
// instock: true
// },
// {
// _id: ObjectId("62d18fe83d35a3afc00610a7"),
// item: 'pears',
// qty: 50,
// status: 'A',
// instock: true
// }
// ]

Mesela schema’ya uymayan bütün kayıtları update edelim.

db.inventory2.updateMany( { $nor: [ { $jsonSchema: myschema } ] }, { $set: { isValid: false } } )

Ya da uymayan bütün dokümanları silelim.

db.inventory2.deleteMany( { $nor: [ { $jsonSchema: myschema } ] } )

Distributed Sharded MongoDB Ortamında CRUD işlemleri

Sharded bir cluster üzerinde çalışabilmek için node’larımız arasında proxy yapacak ve sharded cluster yapılarına özel komutları da çalıştırabilecek sunuclar ile uygulamamız arasında çalışacak mongos aracına ihtiyac vardır. Kurulumda bahsettiğimiz docker-compose aracı ile Bitnaminin sharded cluster image’larını kullanarak cluster ayağa kaldırdıysanız zaten bu sistem mongos üzerinden cluster ı yayına vermiş oluyor.

Mongosh ile bu port ile haberleştiğimizde zaten sharded cluster’a mongos üzerinden bağlanmış oluyoruz.

docker-compose-multiple-shards.yml dosyası içinde ilk image konfigürasyonunda mongodb-sharded container’ı 27017 portu üzerinden yayına verildiğini görebilirsiniz.

Cluster kurmak istemezseniz örnekleri yapmak için MongoDB Atlas free edition kullanabilirsiniz.

resim kaynak

Sharded bir cluster’ı kendiniz manuel kurmak isterseniz alttaki bazı kaynaklar işinizi görecektir.

Shard ile ilgili lazım olabilecek kaynaklar

Ayrıca daha önce sharded olara kurmadığını yada standalone olarka kurduğunuz shared kurumunuzu distributed sharded cluster’a dönüştürmek de mümkün.

Artık çalışmaya başlayabiliriz. Öncelikle sharded sharded bir veritabanı oluşturmalıyız.

Tekrar hatırlatayım sh ile başlayan bu komutları çalıştırabilmek için shard yapısının kurulu olduğu bir sisteme ihtiyacımız var. Sharding metodlar için şu sayfayı ziyaret ediniz.

sh.enableSharding(shardeddb)

// sonuç
// {
// ok: 1,
// '$clusterTime': {
// clusterTime: Timestamp({ t: 1659444132, i: 4 }),
// signature: {
// hash: Binary(Buffer.from("18e8865a97acb4261abcc3ebea8c68d6fe7c1739", "hex"), 0),
// keyId: Long("7119176866014953490")
// }
// },
// operationTime: Timestamp({ t: 1659444132, i: 3 })
// }

use shardeddb

Şimdi sharded collection oluşturmadan önce shard key kavramını anlamamız gerekiyor. Shard key bir veya birden fazla field ( compound key) ile oluşturulan ve dokümanların shard’lar arasındaki dağılımı belirleyen anahtardır.

resim kaynak

MongoDB shard key değerlerini örtüşmeyecek (tekil/unique) shard key aralıklarına böler. Her aralık bir chunk ile ilişkilendirilir ve shard’lara (node/server) eşit olarak bölümlendirilir.

Doğru shard key seçimi için şu sayfayı ziyaret ediniz.

Mesela aşağıdaki örnekte yaş aralığı bir shard key olarak seçilmiş.

Tabii ki bu bölümlendirme işlemleri için kullanılan hash algoritmları, aralıklar vb bir şok şeye müdahele etmek mümkün. Konu ile ilgili olarak resmi sayfasında birçok bilgi mevcut.

Sharded collection oluşturmak için alttaki komut yeterli.

sh.shardCollection("shardeddb.people", { age: 1 } )

// sonuç

// {
// collectionsharded: 'shardeddb.people',
// ok: 1,
// '$clusterTime': {
// clusterTime: Timestamp({ t: 1659466863, i: 26 }),
// signature: {
// hash: Binary(Buffer.from("8170b9ee76e9383502bc40f2b718bacea3096a6c", "hex"), 0),
// keyId: Long("7119176866014953490")
// }
// },
// operationTime: Timestamp({ t: 1659466863, i: 22 })
// }

Eğer daha önce varolan bir collection’ı sharded collection yapmak istersek öncelikle index oluşturmalıyız. Biz halihazırda her colleciton’da otomatik olarak index oluşturulan _id alanını kullanacağımız için buna gerek yok.

db.createCollection("unsharded")

db.unsharded.insertMany([
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);

sh.shardCollection("shardeddb.unsharded", { _id: 1} )

// sonuç

// {
// collectionsharded: 'shardeddb.unsharded',
// ok: 1,
// '$clusterTime': {
// clusterTime: Timestamp({ t: 1659470259, i: 6 }),
// signature: {
// hash: Binary(Buffer.from("e7ff1b9065f78faf2691566bd05f53e96a56981c", "hex"), 0),
// keyId: Long("7119176866014953490")
// }
// },
// operationTime: Timestamp({ t: 1659470259, i: 2 })
// }

Shard durumu hakkında bilgi alalım.

sh.status()


// database: {
// _id: 'shardeddb',
// primary: 'shard1',
// partitioned: true,
// version: {
// uuid: UUID("657c5a0f-3fcb-4ce4-a654-698fd791db94"),
// timestamp: Timestamp({ t: 1659444132, i: 2 }),
// lastMod: 1
// }
// },
// collections: {
// 'shardeddb.people': {
// shardKey: { age: 1 },
// unique: false,
// balancing: true,
// chunkMetadata: [ { shard: 'shard1', nChunks: 1 } ],
// chunks: [
// { min: { age: MinKey() }, max: { age: MaxKey() }, 'on shard': 'shard1', 'last modified': Timestamp({ t: 1, i: 0 }) }
// ],
// tags: []
// },
// 'shardeddb.unsharded': {
// shardKey: { _id: 1 },
// unique: false,
// balancing: true,
// chunkMetadata: [ { shard: 'shard1', nChunks: 1 } ],
// chunks: [
// { min: { _id: MinKey() }, max: { _id: MaxKey() }, 'on shard': 'shard1', 'last modified': Timestamp({ t: 1, i: 0 }) }
// ],
// tags: []
// }
// }
// }

UYARI: Sharded distributed cluster yapısında bir dokümanı update ederken eğer upsert kullanmak isterseniz filtreye full sharded key’i de eklemek gerekiyor [ kaynak].

Bundan sonra artık yukarıda gordüğümüz gibi CRUD işlemleri yapılabilir. Ancak sharded replicated bir yapıda yazma ve okuma yaparken read concern, write concern ve read preferences gibi dikkat etmemiz gereken konular var. Bu konu başlıklarını da transaction konusu ile yakın ilişki olduğu için ve bu makaleyi de çok fazla uzattığımız için ilgili yazıda işlemeyi uygun gördüm. Ayrıca aggregation başlıklarında da gerekli yerlerde bilgiler verildi.

Umarım faydalı olmuştur.

Makale serisinin diğer yazıları için alttaki linkleri kullanabilirsiniz.

Originally published at https://github.com.

--

--