Eksiksiz MongoDB Geliştirici Rehberi
Özellikler, En İyi Uygulamalar ve Tasarım Kalıpları (Design Patterns)
İçindekiler
- Giriş ve Temel Kavramlar
- Veri Modelleme Temelleri
- Şema Tasarım Kalıpları
- CRUD İşlemleri
- İndeksleme Stratejileri
- Aggregation Framework (Toplulaştırma Çerçevesi)
- Transaction’lar (İşlemler)
- Replikasyon (Replication)
- Sharding (Parçalama)
- Performans Optimizasyonu
- Güvenlik En İyi Uygulamaları
- Change Streams ve Gerçek Zamanlı Uygulamalar
- Driver ve Uygulama Seviyesi En İyi Uygulamalar
- İzleme ve Operasyon
- Kaçınılması Gereken Anti-Pattern’ler
- Kontrol Listeleri
1. Giriş ve Temel Kavramlar
MongoDB, veriyi BSON (Binary JSON) dökümanları olarak saklayan doküman-tabanlı, dağıtık bir NoSQL veritabanıdır. Bu rehberdeki her şeyin temeli, temel yapı taşlarını anlamaktan geçer.
1.1 Temel Terminoloji
| MongoDB Terimi | İlişkisel Veritabanı Karşılığı |
|---|---|
| Database (Veritabanı) | Database |
| Collection (Koleksiyon) | Table (Tablo) |
| Document (Doküman) | Row (Satır) |
| Field (Alan) | Column (Sütun) |
| Index (İndeks) | Index |
| Embedded Document (Gömülü Doküman) | (Doğrudan karşılığı yok — iç içe join gibi düşünülebilir) |
_id | Primary Key (Birincil Anahtar) |
1.2 BSON vs JSON
BSON, JSON’u ek tiplerle genişletir: ObjectId, Date, Decimal128, Binary, Int32, Int64, Timestamp, Regex. Değerleri her zaman string olarak değil, BSON-native tipleriyle saklamayı tercih edin (örneğin tarihleri ISO string yerine Date tipiyle saklayın) — bu, doğru sıralama, aralık sorguları (range query) yapılmasını sağlar ve depolama alanını azaltır.
1.3 Doküman Modelinin Felsefesi
MongoDB’nin temel gücü, birlikte erişilen verinin birlikte saklanması gerektiği prensibidir. Bu, ilişkisel veritabanı normalizasyonundan kökten farklıdır. Modelleme sürecinde sorulması gereken asıl soru her zaman şudur:
“Uygulamam neyi sorguluyor ve bunu ne sıklıkla sorguluyor?”
Şu değil: “Bu verinin en normalize edilmiş hali nedir?”
2. Veri Modelleme Temelleri
2.1 Gömme (Embedding) vs Referans Verme (Referencing)
Bu, MongoDB şema tasarımındaki en önemli tek karardır.
Şu durumlarda gömün (embed):
- Veriler arasında bir “içerir” ilişkisi varsa (Sipariş, Sipariş Kalemlerini içerir)
- Alt veri neredeyse her zaman üst veriyle birlikte erişiliyorsa
- Alt veri sınırsız büyümüyorsa (sınırsız büyüyen dizilerden kaçının)
- Veri, bağımsız olarak güncellenmesinden çok daha sık okunuyorsa
Şu durumlarda referans verin (reference):
- Alt varlıklar büyükse veya sınırsız büyüyorsa (örneğin milyonlarca yorum)
- Alt veri birçok üst doküman tarafından paylaşılıyor/yeniden kullanılıyorsa (örneğin birçok “Sipariş” tarafından referans verilen bir “Ürün”)
- Veri, üst dokümandan bağımsız olarak sık sık güncelleniyorsa
- Alt varlığı sık sık tek başına sorgulamanız gerekiyorsa
// Gömme örneği — yorumları olan bir blog yazısı (sınırlı, her zaman birlikte gösteriliyor)
{
_id: ObjectId("..."),
title: "MongoDB En İyi Uygulamaları",
author: "Jane Doe",
comments: [
{ user: "alice", text: "Harika yazı!", date: ISODate("2026-01-10") },
{ user: "bob", text: "Çok faydalı", date: ISODate("2026-01-11") }
]
}
// Referans verme örneği — Müşteriye referans veren Siparişler (yeniden kullanılıyor, büyük koleksiyon)
{
_id: ObjectId("..."),
customerId: ObjectId("64f1a2..."),
items: [ { sku: "A100", qty: 2, price: 19.99 } ],
total: 39.98
}
2.2 16MB Doküman Limiti
Her BSON dokümanının 16MB’lık kesin bir sınırı vardır. Bu, modelleme kararlarını şekillendiren kritik bir kısıtlamadır — bir dizinin (array) veya gömülü yapının sınırsız büyüyebileceği bir şema asla tasarlamayın (örneğin bir kullanıcı dokümanına gömülü “bir kullanıcının tüm olayları” gibi).
2.3 Working Set ve RAM
MongoDB, working set (sık erişilen veri + indeksler) RAM’e sığdığında en iyi performansı gösterir. Dokümanlarınızı, sık erişilen alanları bir arada tutacak şekilde modelleyin ve nadiren kullanılan verilerle dokümanları şişirmekten kaçının (aşağıda anlatılan Extended Reference Pattern ile “sıcak” (hot) dokümanları yalın tutabilirsiniz).
2.4 Şema Versiyonlama
MongoDB şema esnekliğine sahip olduğundan, uygulamanız zamanla evrildikçe her zaman bir schemaVersion alanı ekleyin:
{ _id: ObjectId("..."), schemaVersion: 2, ... }
Bu, uygulamanın büyük bir tek seferlik (big-bang) migration yerine, dokümanları okuma/yazma sırasında tembel bir şekilde (lazily) migrate etmesine olanak tanır.
3. Şema Tasarım Kalıpları
MongoDB’nin resmi dokümantasyonu, kanıtlanmış bir dizi şema tasarım kalıbını (schema design pattern) kataloglar. Bunları isimleriyle bilmek, ekibinizle tasarım kararlarını net bir şekilde iletişim kurmanızı sağlar.
3.1 Attribute Pattern (Öznitelik Kalıbı)
Birçok benzer alanınız olduğunda ve bunlardan sadece bazıları herhangi bir dokümana uygulandığında, ayrıca bunlar arasında arama yapmanız gerektiğinde kullanılır.
// Şunun yerine: { color: "red", size: "M", material: "cotton", ... } (birçok opsiyonel alan)
{
name: "T-Shirt",
attributes: [
{ k: "color", v: "red" },
{ k: "size", v: "M" },
{ k: "material", v: "cotton" }
]
}
// İndeks: { "attributes.k": 1, "attributes.v": 1 }
3.2 Extended Reference Pattern (Genişletilmiş Referans Kalıbı)
Ekstra lookup işlemlerinden kaçınmak için, referans verilen bir dokümandan sık ihtiyaç duyulan birkaç alanı üst dokümana kopyalayın (kontrollü bir denormalizasyon).
// Sipariş dokümanı, "customers" koleksiyonuna join yapmadan görüntüleme için gereken alanları gömer
{
orderId: "ORD-1001",
customer: { id: ObjectId("..."), name: "Jane Doe", email: "[email protected]" }, // genişletilmiş referans
items: [...]
}
3.3 Subset Pattern (Alt Küme Kalıbı)
Bir dokümanın büyük bir dizisi olduğunda (örneğin binlerce yorum) ama uygulama genelde sadece en son N tanesine ihtiyaç duyduğunda, küçük bir alt kümeyi gömülü tutun, geri kalanını ayrı bir koleksiyonda saklayın.
{
productId: "P-100",
name: "Kablosuz Fare",
recentReviews: [ /* sadece son 10 yorum */ ]
}
// Tüm yorumlar, productId'ye referans veren ayrı bir "reviews" koleksiyonunda yaşar
3.4 Computed Pattern (Hesaplanmış Değer Kalıbı)
Hesaplanması pahalı olan değerleri (toplamlar, sayaçlar, ortalamalar) her okumada yeniden hesaplamak yerine önceden hesaplayıp saklayın. Bunları uygulama mantığı, trigger’lar veya zamanlanmış görevlerle güncelleyin.
{
productId: "P-100",
totalSold: 15234, // hesaplanmış/önbelleklenmiş
avgRating: 4.6, // hesaplanmış/önbelleklenmiş
reviewCount: 812
}
3.5 Bucket Pattern (Kova Kalıbı)
Zaman serisi veya akış verilerini, her okuma için bir doküman yerine “kovalara” (bucket) gruplayın (örneğin sensör başına saatlik bir doküman). Bu, doküman sayısını ve indeks yükünü ciddi şekilde azaltır.
{
sensorId: "S-42",
hour: ISODate("2026-08-17T09:00:00Z"),
measurements: [
{ ts: ISODate("2026-08-17T09:00:12Z"), temp: 22.1 },
{ ts: ISODate("2026-08-17T09:00:27Z"), temp: 22.3 }
// kova başına maksimum N adet
],
count: 2,
sum: 44.4
}
Not: Yoğun zaman serisi (time-series) iş yükleri için, elle yapılan bucketing yerine MongoDB’nin yerel Time Series Collections özelliğini tercih edin (
db.createCollection("sensors", { timeseries: { timeField: "ts", metaField: "sensorId", granularity: "seconds" } })).
3.6 Outlier Pattern (Aykırı Değer Kalıbı)
Normal gömme varsayımlarınızı bozan nadir dokümanları (örneğin 50 milyon takipçisi olan bir ünlü hesabı) bir bayrak (flag) ekleyerek ve fazla veriyi sadece o aykırı değer için ayrı bir koleksiyona taşırarak yönetin.
{ userId: "u1", followers: [...], hasOverflow: false }
{ userId: "celebrity1", followers: [...1000 gösterilen...], hasOverflow: true }
// 1001. takipçiden itibaren "followers_overflow" koleksiyonunda yaşar
3.7 Polymorphic Pattern (Çok Biçimli Kalıp)
Farklı ama ilişkili “şekillere” sahip dokümanları, bir type alanıyla ayırt ederek aynı koleksiyonda saklayın — uygulamanın bunları birlikte sorguladığı durumlarda faydalıdır.
{ type: "car", make: "Toyota", doors: 4 }
{ type: "motorcycle", make: "Harley", hasSidecar: false }
// İkisi de "vehicles" koleksiyonunda
3.8 Ağaç / Hiyerarşi Kalıpları
Hiyerarşik veriler için (kategoriler, organizasyon şemaları, yorum ağaçları), erişim modeline göre seçim yapın:
- Ebeveyn referansları (Parent references):
parentIdsaklayın — bir node’un doğrudan çocuklarını okumak için iyidir. - Çocuk referansları (Child references):
children: [ids]saklayın — bir node’un doğrudan çocuklarını okumak için de iyidir, farklı ödünleşimlerle. - Ata dizisi (Array of ancestors):
ancestors: [id1, id2, ...]saklayın — tüm yolu/breadcrumb’ı hızlıca almak için iyidir. - Materialized paths (Somutlaştırılmış yollar):
path: ",1,2,6,"saklayın — regex tabanlı alt ağaç sorguları için iyidir. - Nested sets (İç içe kümeler):
left/rightsınırlarını saklayın — hızlı alt ağaç sorguları için iyidir ama güncellemeler pahalıdır.
// Ata Dizisi — hızlı "tüm ataları getir" ve "tüm alt öğeleri getir" sorguları
{
_id: "electronics.laptops.gaming",
name: "Oyun Laptopları",
ancestors: ["electronics", "electronics.laptops"]
}
3.9 Approximation Pattern (Yaklaşıklık Kalıbı)
Mükemmel hassasiyetin gerekmediği analitikler için (örneğin sayfa görüntülenme sayaçları), sayacı olasılıksal olarak güncelleyerek (örneğin 100 yazımdan 1’inde, sonra 100 ile çarparak) yazma yükünü azaltın.
3.10 Şema Versiyonlama Kalıbı
§2.4’te ele alındı — migration’lar sırasında farklı şema versiyonlarının bir arada var olabilmesi için dokümanları her zaman bir versiyonla etiketleyin.
3.11 Single Collection Pattern (Tek Koleksiyon Kalıbı)
Her zaman birlikte sorgulanan ilişkili varlık tipleri, bir type ayırıcısıyla ayrılarak tek bir koleksiyonda yaşayabilir, bu da round-trip sayısını azaltır (polymorphic kalıba benzer, ancak tek bir koleksiyonda varlıklar arası sorgulara vurgu yapar — örneğin tek sorgulu mağaza sayfaları için product, category ve review tiplerini birlikte saklayan bir e-ticaret uygulaması).
4. CRUD İşlemleri
4.1 Ekleme (Insert)
db.users.insertOne({ name: "Jane", email: "[email protected]", createdAt: new Date() });
db.users.insertMany([
{ name: "Bob" },
{ name: "Alice" }
], { ordered: false }); // sırasız = tekil doküman hatalarında devam eder, genelde daha hızlıdır
4.2 Sorgu Temelleri
// Projection — sadece ihtiyacınız olan alanları projekte edin
db.users.find({ status: "active" }, { name: 1, email: 1, _id: 0 });
// Karşılaştırma operatörleri
db.orders.find({ total: { $gte: 100, $lt: 500 } });
// Mantıksal operatörler
db.orders.find({ $or: [ { status: "pending" }, { status: "processing" } ] });
// Dizi (array) sorguları
db.products.find({ tags: "sale" }); // dizi "sale" içeriyorsa eşleşir
db.products.find({ tags: { $all: ["sale", "new"] } });
db.products.find({ "reviews.rating": { $gte: 4 } }); // gömülü diziye dot notation ile erişim
// $elemMatch — birden fazla koşulun AYNI dizi elemanında eşleşmesi gerektiğinde
db.products.find({
reviews: { $elemMatch: { rating: { $gte: 4 }, verified: true } }
});
4.3 Güncelleme (Update)
// Dokümanı tamamen değiştirmek yerine hedefli operatörleri tercih edin
db.users.updateOne(
{ _id: id },
{ $set: { status: "active" }, $currentDate: { updatedAt: true } }
);
// Artırma / dizi operatörleri
db.products.updateOne({ _id: id }, { $inc: { stock: -1 } });
db.posts.updateOne({ _id: id }, { $push: { comments: newComment } });
db.posts.updateOne({ _id: id }, { $push: { comments: { $each: [c1, c2], $slice: -50 } } }); // dizi boyutunu sınırla
// Upsert
db.counters.updateOne(
{ _id: "orderId" },
{ $inc: { seq: 1 } },
{ upsert: true }
);
// Bulk write — verimlilik için birden fazla işlemi toplu halde gönderme
db.orders.bulkWrite([
{ updateOne: { filter: { _id: 1 }, update: { $set: { status: "shipped" } } } },
{ updateOne: { filter: { _id: 2 }, update: { $set: { status: "shipped" } } } },
{ deleteOne: { filter: { _id: 3 } } }
], { ordered: false });
4.4 Silme (Delete)
db.sessions.deleteMany({ expiresAt: { $lt: new Date() } });
4.5 findAndModify Ailesi
// Atomik olarak getir-ve-güncelle — kuyruklar, sayaçlar, kilitler için harika
db.jobs.findOneAndUpdate(
{ status: "queued" },
{ $set: { status: "processing", startedAt: new Date() } },
{ sort: { priority: -1 }, returnDocument: "after" }
);
5. İndeksleme Stratejileri
İndeksler, sorgu performansı için en büyük tek etkendir. Eksik bir indeks, O(log n) bir aramayı O(n) bir koleksiyon taramasına (collection scan) dönüştürür.
5.1 İndeks Tipleri
| Tip | Kullanım Alanı |
|---|---|
| Tekli alan (Single field) | Bir alan üzerinde basit eşitlik/aralık sorguları |
| Bileşik (Compound) | Birden fazla alanda filtreleme/sıralama yapılan sorgular |
| Multikey | Bir dizi alanı indekslendiğinde otomatik oluşturulur |
| Text | Tam metin arama (full-text search) |
| Geospatial (2dsphere) | Konum tabanlı sorgular |
| Hashed | Tek bir alanda sharding için eşit dağılım |
| Wildcard | Bilinmeyen/dinamik alan isimleri |
| TTL | Dokümanları otomatik sona erdirme (session’lar, log’lar, önbellekler) |
| Unique | Benzersizlik kısıtlaması uygular |
| Partial | Sadece filtreye uyan dokümanların bir alt kümesini indeksler |
| Sparse | İndekslenmiş alanı eksik olan dokümanları atlar |
5.2 Bileşik İndeksler için ESR Kuralı
Bileşik indeksler oluştururken, alanları şu sırayla düzenleyin: Eşitlik (Equality) → Sıralama (Sort) → Aralık (Range).
// Sorgu: bir müşteri için aktif siparişleri bul, tarihe göre sırala, fiyat aralığında
db.orders.find({ customerId: id, status: "active", total: { $gt: 50 } })
.sort({ createdAt: -1 });
// Optimal indeks: Eşitlik(customerId, status) -> Sıralama(createdAt) -> Aralık(total)
db.orders.createIndex({ customerId: 1, status: 1, createdAt: -1, total: 1 });
5.3 Covered Queries (Kapsanan Sorgular)
Bir sorgu, istenen tüm alanlar indeksin kendisinde mevcut olduğunda ve dokümanı hiç getirmeye gerek kalmadığında “covered” (kapsanmış) olur.
db.users.createIndex({ email: 1, name: 1 });
db.users.find({ email: "[email protected]" }, { email: 1, name: 1, _id: 0 }); // kapsanmış
5.4 Partial Index vs Sparse Index
Modern MongoDB’de sparse indeksler yerine partial indeksleri tercih edin — daha esnektirler.
// Sadece total > 0 olan aktif siparişleri indeksle — daha küçük, daha hızlı indeks
db.orders.createIndex(
{ customerId: 1 },
{ partialFilterExpression: { status: "active", total: { $gt: 0 } } }
);
5.5 TTL İndeksleri
db.sessions.createIndex({ lastAccess: 1 }, { expireAfterSeconds: 3600 });
5.6 İndeks Yönetimi En İyi Uygulamaları
- İndeks ekledikten önce/sonra etkisini doğrulamak için
explain("executionStats")kullanın. - MongoDB 4.2’den itibaren indeksler artık varsayılan olarak arka planda oluşturulur (bloke eden build’ler artık varsayılan değil).
- Aşırı indekslemeden kaçının: her indeks, yazma yükü ve RAM baskısı ekler.
$indexStatsile periyodik olarak inceleyin ve kullanılmayan indeksleri kaldırın. - Koleksiyon başına indeks sayısını makul tutun (yaygın bir kural: yazma-yoğun koleksiyonlarda ~10-15’ten az).
hint()‘i seyrek kullanın, sadece sorgu planlayıcısının (query planner) yanlış seçim yaptığından emin olduğunuz durumlarda geçersiz kılmak için.
6. Aggregation Framework (Toplulaştırma Çerçevesi)
Aggregation pipeline, MongoDB’nin karmaşık veri dönüşümü, analitik ve raporlama aracıdır.
6.1 Temel Aşamalar (Stages)
db.orders.aggregate([
{ $match: { status: "completed", createdAt: { $gte: ISODate("2026-01-01") } } }, // erken filtrele!
{ $group: {
_id: "$customerId",
totalSpent: { $sum: "$total" },
orderCount: { $sum: 1 },
avgOrder: { $avg: "$total" }
}},
{ $sort: { totalSpent: -1 } },
{ $limit: 10 },
{ $lookup: {
from: "customers",
localField: "_id",
foreignField: "_id",
as: "customer"
}},
{ $unwind: "$customer" },
{ $project: { _id: 0, customerName: "$customer.name", totalSpent: 1, orderCount: 1 } }
]);
6.2 Temel Aşama Referansı
| Aşama | Amaç |
|---|---|
$match | Dokümanları filtreler (mümkün olduğunca erken kullanın!) |
$project | Alanları yeniden şekillendirir / dahil eder-hariç tutar |
$group | Değerleri (toplam, ortalama, sayaç vb.) anahtara göre toplulaştırır |
$sort | Sonuçları sıralar |
$limit / $skip | Sayfalama (büyük ölçekte $skip konusunda dikkatli olun — bkz. §10.5) |
$lookup | Başka bir koleksiyona left-outer-join yapar |
$unwind | Bir dizi alanını birden fazla dokümana açar |
$addFields / $set | Yeni alanlar ekler veya hesaplar |
$facet | Birden fazla alt pipeline’ı paralel çalıştırır, örn. arama sonuçları + sayaçlar için |
$bucket / $bucketAuto | Dokümanları aralıklara kategorize eder |
$graphLookup | Hiyerarşik/grafik veriler için özyinelemeli (recursive) lookup |
$merge / $out | Pipeline sonuçlarını bir koleksiyona yazar (materialized view’lar) |
$replaceRoot | Bir alt dokümanı üst seviyeye yükseltir |
$setWindowFields | Pencere fonksiyonları (running total, sıralama) — MongoDB 5.0+ |
6.3 Aggregation Performans Kuralları
$matchve$sort‘u mümkün olduğunca erken kullanın — pipeline’ın ilk aşamalarda indeksleri kullanabilmesini sağlar.$project‘i erken kullanarak sonraki aşamalara akan doküman boyutunu azaltın (alanlara daha sonra ihtiyaç duyulmadığı sürece).- Büyük, indekssiz foreign koleksiyonlarda
$lookup‘tan kaçının — her zamanforeignField‘i indeksleyin. - Aşama başına 100MB bellek limitini aşabilecek büyük aggregation’lar için
allowDiskUse: truekullanın. - “Veri + sayaç” sorgularını tek bir round-trip’te birleştirmek için
$facetkullanın. - Pahalı, tekrarlayan aggregation’ları
$mergeile önceden hesaplanmış bir koleksiyona materialize edin (Computed Pattern’in bir biçimi).
6.4 Pencere Fonksiyonu Örneği (MongoDB 5.0+)
db.sales.aggregate([
{ $setWindowFields: {
partitionBy: "$region",
sortBy: { date: 1 },
output: {
runningTotal: { $sum: "$amount", window: { documents: ["unbounded", "current"] } },
rank: { $rank: {} }
}
}}
]);
7. Transaction’lar (İşlemler)
MongoDB, v4.0’dan itibaren (replica set’lerde) ve v4.2’den itibaren (sharded cluster’larda) çok-dokümanlı ACID transaction’ları destekler.
7.1 Ne Zaman İhtiyaç Duyarsınız
Gömme (embedding) sayesinde, MongoDB’nin her zaman garanti ettiği tek-doküman atomikliği çoğu kullanım senaryosunu kapsar. Çok-dokümanlı transaction’lara sadece birden fazla koleksiyondaki birden fazla dokümanı atomik olarak güncellemeniz gerektiğinde başvurun — örneğin bir banka transferi (bir hesaptan düş, diğerine ekle).
const session = client.startSession();
try {
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
await accounts.updateOne({ _id: fromId }, { $inc: { balance: -amount } }, { session });
await accounts.updateOne({ _id: toId }, { $inc: { balance: amount } }, { session });
await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
throw err;
} finally {
session.endSession();
}
7.2 Transaction En İyi Uygulamaları
- Transaction’ları kısa ömürlü tutun (varsayılan limit ~60 saniye) — uzun transaction’lar kilit tutar ve oplog baskısı biriktirir.
- Şemanızı (gömme yoluyla) çapraz-doküman transaction ihtiyacını en başından minimuma indirecek şekilde tasarlayın.
TransientTransactionErrorveUnknownTransactionCommitResultetiketli hatalar için her zaman yeniden deneme (retry) mantığı uygulayın — driver’lar bu etiketleri tam olarak bu amaç için sunar.- Çok sayıda dokümana dokunan transaction’lardan kaçının; bir transaction binlerce dokümana dokunuyorsa toplu işleyin veya yeniden tasarlayın.
8. Replikasyon (Replication)
Bir Replica Set, yüksek erişilebilirlik ve okuma ölçeklendirmesi sağlayarak aynı veri kümesini koruyan bir grup mongod sürecidir.
8.1 Topoloji
- 1 Primary (tüm yazmaları kabul eder)
- N Secondary (oplog aracılığıyla primary’den replike olur, okuma isteklerine hizmet edebilir)
- Opsiyonel Arbiter (seçimlerde oy kullanır, veri tutmaz — mümkünse production’da gerçek veri tutan node’lar lehine kaçınılmalıdır)
Önerilen minimum: 3 veri tutan node (temiz bir seçim çoğunluğu için tek sayı).
8.2 Write ve Read Concern
// Write Concern — bir yazmanın onaylanması için kaç node gerektiği
db.orders.insertOne(doc, { writeConcern: { w: "majority", wtimeout: 5000 } });
// Read Concern — okumalar için tutarlılık garantisi
db.orders.find().readConcern("majority");
// Read Preference — hangi member'lardan okunacağı
db.orders.find().readPref("secondaryPreferred");
| Read Preference | Davranış |
|---|---|
primary (varsayılan) | Her zaman primary’den okur — en güçlü tutarlılık |
primaryPreferred | Mümkünse primary, değilse secondary |
secondary | Her zaman bir secondary’den okur |
secondaryPreferred | Mümkünse secondary, değilse primary |
nearest | En düşük ağ gecikmesine sahip member |
Dikkat: Secondary’lerden okumak replikasyon gecikmesi / eski (stale) veri okuma riski taşır. Sadece analitik, raporlama veya nihai tutarlılığın (eventual consistency) kabul edilebilir olduğu kullanım senaryolarında kullanın.
8.3 En İyi Uygulamalar
- Kaybetmeyi göze alamayacağınız her yazma için
w: "majority"kullanın. - Replikasyon gecikmesini izleyin (
rs.printSecondaryReplicationInfo()). - Gerçek hata toleransı için replica set üyelerini farklı availability zone’lara/veri merkezlerine dağıtın.
- Uygulamaya görünür oylama topolojisini etkilemeden, özel yedekleme/analitik iş yükleri için Hidden üyeler kullanın.
9. Sharding (Parçalama)
Sharding, tek bir replica set’in veri hacmini veya throughput’u artık kaldıramadığı durumlarda, veriyi birden fazla makineye (shard) dağıtan MongoDB’nin yatay ölçeklendirme mekanizmasıdır.
9.1 Temel Bileşenler
- Shard: sharded verinin bir alt kümesini tutan bir replica set.
- Config Server’lar: cluster meta verisini saklar (bu da bir replica set’tir).
- mongos: uygulamaların bağlandığı sorgu yönlendiricisi; işlemleri doğru shard(lar)a yönlendirir.
9.2 Shard Key Seçimi — En Önemli Karar
İyi bir shard key şu özelliklere sahiptir:
- Yüksek kardinalite (cardinality) — çok sayıda farklı değer.
- Eşit dağılım — sıcak (hot) shard’lardan kaçınır.
- Sorgu izolasyonu — idealde, çoğu sorgu shard key’i içerir, böylece
mongostüm shard’lara yayın yapmak (“scatter-gather”) yerine tek bir shard’ı hedefleyebilir.
sh.shardCollection("app.orders", { customerId: "hashed" }); // hashed = eşit dağılım
// veya iyi izolasyona sahip aralık tabanlı sorgular için bileşik bir shard key:
sh.shardCollection("app.events", { tenantId: 1, createdAt: 1 });
9.3 Yaygın Shard Key Anti-Pattern’leri
- Monoton artan anahtarlar (örneğin
ObjectId, auto-increment, timestamp’ler) tek shard key olarak kullanıldığında → tüm yeni yazmalar aynı “son” chunk/shard’a çarpar (“hot shard” problemi). - Düşük kardinaliteli anahtarlar (örneğin
status: "active"/"inactive") → veri eşit dağıtılamaz, dev (jumbo) chunk’lar oluşturur.
Çözüm: bir hashed shard key kullanın, veya düşük kardinaliteli bir öneki (prefix) yüksek kardinaliteli, monoton olmayan bir sonek (suffix) ile birleştiren bir bileşik anahtar kullanın (bu, resmi “Hashed Shard Key” ve “Compound Shard Key” kalıplarının özüdür).
9.4 Zone’lar (Tag-Aware Sharding)
Belirli veri aralıklarını belirli shard’lara yönlendirin — yaygın olarak coğrafi tabanlı veri yerleşimi (data residency) gereksinimleri için kullanılır (örneğin AB kullanıcı verisinin AB’de bulunan shard’larda yaşaması gerektiğinde).
10. Performans Optimizasyonu
10.1 explain()‘i Dini Bir Görev Gibi Kullanın
db.orders.find({ status: "shipped" }).explain("executionStats");
COLLSCAN‘e (kötü — tam koleksiyon taraması) karşı IXSCAN‘e (iyi — indeks kullanılıyor) bakın. totalDocsExamined ile nReturned‘i karşılaştırın — birbirlerine yakın olmalılar.
10.2 Büyük Skip Değerlerinden Kaçının
Sayfalama için $skip kullanmak, büyük ölçekte yavaşlar çünkü MongoDB atlanan tüm dokümanların üzerinden geçmek zorunda kalır.
// Büyük ölçekte KÖTÜ:
db.posts.find().sort({ _id: -1 }).skip(100000).limit(20);
// İYİ — aralık tabanlı (keyset) sayfalama:
db.posts.find({ _id: { $lt: lastSeenId } }).sort({ _id: -1 }).limit(20);
10.3 Projection Disiplini
Sadece 2 alana ihtiyacınız varsa asla tam bir dokümanı find() etmeyin — ağ transferini ve bellek baskısını azaltır.
10.4 Connection Pooling (Bağlantı Havuzlama)
Uygulama süreci başına tek bir MongoClient örneği yeniden kullanın; driver’lar dahili olarak bir bağlantı havuzu yönetir. İstek başına yeni bir client oluşturmayın.
10.5 $where ve Ağır JavaScript’ten Kaçının
$where ve mapReduce, sunucu tarafında JavaScript çalıştırır ve indeksleri verimli bir şekilde kullanamaz — bunun yerine aggregation pipeline operatörlerini tercih edin.
10.6 Toplu (Batch) Okuma ve Yazma
Round-trip sayısını azaltmak için bulkWrite, cursor batching (batchSize()) ve insertMany kullanın.
10.7 Şema Doğrulama (Schema Validation)
Esneklikten ödün vermeden hatalı veriyi erken yakalamak için koleksiyon seviyesinde JSON Schema doğrulaması kullanın:
db.createCollection("orders", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["customerId", "total", "status"],
properties: {
total: { bsonType: "decimal", minimum: 0 },
status: { enum: ["pending", "shipped", "delivered", "cancelled"] }
}
}
},
validationLevel: "moderate" // migration sırasında mevcut dokümanları bozma
});
11. Güvenlik En İyi Uygulamaları
- Kimlik Doğrulama ve Yetkilendirmeyi Etkinleştirin (
--auth) — production’ı asla bunlar olmadan çalıştırmayın. - Rol Tabanlı Erişim Kontrolü (RBAC) — uygulama kullanıcıları için
readWriteAnyDatabase/rootyerine en az ayrıcalık ilkesiyle özel roller tanımlayın. - Transit sırasında şifreleme — tüm client-server ve cluster içi trafik için TLS/SSL’i etkinleştirin.
- Bekleme sırasında (at rest) şifreleme — WiredTiger şifreli storage engine’i veya disk seviyesi şifreleme kullanın.
- Ağ izolasyonu — özel IP’lere bağlanın, güvenlik duvarları/VPC güvenlik gruplarını kullanın, MongoDB’yi asla doğrudan public internete açmayın (
0.0.0.0/0bind’i klasik bir ihlal vektörüdür). - Yüksek hassasiyetli alanlar (PII, ödeme verisi) için Field-Level / Client-Side Field Level Encryption (CSFLE) — veri, uygulamadan çıkmadan önce şifrelenir.
- Audit logging (denetim günlüğü) — hassas koleksiyonlara erişimi izleyin (Enterprise/Atlas özelliği).
- Kimlik bilgilerini düzenli olarak rotate edin, secret manager’lar kullanın (bağlantı string’lerine asla parola hardcode etmeyin).
- Tüm kullanıcı girdilerini doğrulayın ve temizleyin — MongoDB SQL injection’a karşı savunmasız olmasa da, ham kullanıcı JSON’unu doğrudan sorgulara geçirirseniz NoSQL/operatör injection‘a karşı savunmasızdır.
// SAVUNMASIZ — kullanıcı girdisi doğrudan bir operatör olarak kullanılıyor
db.users.find({ password: req.body.password }); // eğer body.password = {"$ne": null} => atlatma!
// GÜVENLİ — sorgulamadan önce tipleri doğrulayın
if (typeof req.body.password !== "string") throw new Error("Geçersiz girdi");
12. Change Streams ve Gerçek Zamanlı Uygulamalar
Change stream’ler, uygulamaların polling yapmadan gerçek zamanlı veri değişikliklerine abone olmasını sağlar.
const changeStream = db.collection("orders").watch([
{ $match: { operationType: { $in: ["insert", "update"] } } }
]);
changeStream.on("change", (change) => {
console.log("Sipariş değişti:", change.documentKey, change.updateDescription);
});
Kullanım senaryoları: önbellek geçersiz kılma (cache invalidation), bildirim tetikleme, arama motorlarına senkronizasyon (Elasticsearch/Atlas Search), mikroservis olay hatları (outbox’sız CDC).
En iyi uygulamalar:
resumeToken‘ı kalıcı hale getirin, böylece tüketiciler bir bağlantı kopmasından sonra olayları kaçırmadan devam edebilir.- Sadece diff değil, tam güncelleme-sonrası dokümana ihtiyacınız olduğunda
fullDocument: "updateLookup"kullanın.
13. Driver ve Uygulama Seviyesi En İyi Uygulamalar
- Uygulama başına bir
MongoClient— thread-safe’tir ve havuzlamayı yönetir; istek başına yeniden örneklemekten kaçının. - Her zaman makul timeout’lar ayarlayın (
serverSelectionTimeoutMS,connectTimeoutMS,socketTimeoutMS) — takılıp kalmak yerine hızlıca başarısız olun. - Resmi driver’ları kullanın ve güncel tutun — sunucu yeteneklerini ve protokol değişikliklerini takip ederler.
- Idempotent yazmaları yeniden deneyin — geçici ağ kesintilerini otomatik olarak ele almak için
retryWrites=true‘yu etkinleştirin (modern driver’larda varsayılandır). - Bir ODM/ORM’i bilinçli olarak kullanın (Node için Mongoose, Python async için Motor, Java için Spring Data MongoDB) — şema doğrulama ve lifecycle hook’ları ekler, ama arka planda ürettikleri sorguları anlayın.
- Cursor’ları düzgün bir şekilde kapatın ve büyük sonuç kümelerini belleğe yüklemekten kaçının — sınırsız sorgularda
.toArray()yerine streaming/cursor iterasyonu kullanın. - Ortama özel bağlantı string’leri — kaynak koduna asla kimlik bilgileri içeren
mongodb://URI’lerini hardcode etmeyin; ortam değişkenleri veya bir secret manager kullanın.
14. İzleme ve Operasyon
db.currentOp()— şu anda çalışan işlemleri inceleyin, uzun süren veya bloke olmuş sorguları bulun.db.serverStatus()— üst düzey sunucu sağlığı (bağlantılar, bellek, opcounter’lar).$indexStats— hangi indekslerin gerçekten kullanıldığını görün.- Atlas Performance Advisor /
mongodslow query log (db.setProfilingLevel(1, { slowms: 100 })) — yavaş sorguları otomatik olarak bulun. - Yedekler (Backups) — Atlas için sürekli yedeklemeler veya kendi barındırdığınız kurulumlar için
mongodump/dosya sistemi anlık görüntüleri (snapshot) kullanın; her zaman geri yükleme prosedürlerini test edin. - Kapasite planlaması — disk kullanımını, WiredTiger cache hit oranını ve bağlantı sayılarını proaktif olarak izleyin.
15. Kaçınılması Gereken Anti-Pattern’ler
| Anti-Pattern | Neden Kötü | Çözüm |
|---|---|---|
| Bir dokümanda sınırsız dizi | 16MB limitine çarpar, ondan çok önce performans düşer | Subset Pattern / ayrı koleksiyon |
| Aşırı sayıda koleksiyon (kullanıcı/tenant başına bir tane) | Meta veri ve depolama yükü, yönetimi zor | Tenant/ayırıcı alanlı tek koleksiyon |
| Derin iç içe geçmiş dokümanlar (>3-4 seviye) | Sorgulaması/indekslemesi zor, garip güncellemeler | Düzleştirin veya referans verin |
İş mantığı için $where/JS kullanma | Yavaş, indeksleri kullanamaz | Aggregation pipeline |
| Sık filtrelenen/sıralanan alanlarda indeks olmaması | Tam koleksiyon taramaları | Uygun bileşik indeksler ekleyin |
| Dokümanlarda büyük binary blob saklama (resim/video) | Working set’i şişirir, sorguları yavaşlatır | GridFS veya harici nesne depolama (S3) + URL’yi saklayın |
| Kritik yazmalarda write concern’i göz ardı etme | Failover sırasında sessiz veri kaybı | w: "majority" |
| MongoDB’yi ilişkisel bir veritabanı gibi ele alma (aşırı normalizasyon) | Fazla $lookup, kötü performans | Erişim kalıplarına göre gömün |
| Büyük ölçekte skip tabanlı sayfalama | Sayfa başına O(n) maliyeti | Aralık/keyset tabanlı sayfalama |
| Geçici transaction hatalarını ele almama | Geçici kesintilerde uygulama seviyesi hatalar | Driver’ın sağladığı hata etiketleriyle yeniden deneyin |
16. Kontrol Listeleri
16.1 Yeni Koleksiyon Kontrol Listesi
- Şemayı tasarlamadan önce erişim kalıpları tanımlandı
- Her ilişki için gömme vs referans kararı verildi
-
schemaVersionalanı eklendi - JSON Schema doğrulayıcısı eklendi
- En sık kullanılan sorgulara uygun indeksler oluşturuldu (ESR kuralı)
- Dizi büyüme limitleri düşünüldü (16MB doküman sınırı)
16.2 Production Öncesi Kontrol Listesi
- Kimlik doğrulama + RBAC rolleri yapılandırıldı
- TLS etkinleştirildi
- İşlem kritikliğine göre uygun write/read concern’ler ayarlandı
- ≥3 veri tutan node içeren replica set
- Yedekler yapılandırıldı ve geri yükleme test edildi
- Yavaş sorgu profillemesi etkinleştirildi
- Driver’da bağlantı havuzlama ve timeout’lar yapılandırıldı
- Sorgu kalıpları
explain()ile yük testine tabi tutuldu
16.3 Ölçeklendirme Kontrol Listesi (Sharding Öncesi)
- Dikey ölçeklendirmenin / daha iyi indekslemenin yetersiz olduğu önce doğrulandı
- Yüksek kardinaliteli ve eşit dağılımlı shard key seçildi
- Sorgu kalıplarının mümkün olduğunca shard key’i içerdiği doğrulandı
- Config server’lar ve mongos router’lar uygun boyutlandırıldı
- Chunk migration davranışı yük altında test edildi
Bu rehber, MongoDB geliştirmenin pratik özünü kapsamaktadır. Belirli alanlarda derinlemesine bilgi için (Atlas Search, Vector Search, Time Series Collections, Client-Side Field Level Encryption iç yapısı), özellikler ve en iyi uygulamalar sürekli geliştiği için resmi MongoDB dokümantasyonuna başvurun.