Prodüksiyonda bir MongoDB kümeniz var ve uygulama yavaşlamaya başladı. Kullanıcılar şikayetçi, loglara bakıyorsunuz ama tam olarak neyin yavaş olduğunu göremiyorsunuz. İşte bu noktada MongoDB Profiler devreye giriyor. Profiler, veritabanınızda çalışan sorguları izlemenizi, yavaş olanları tespit etmenizi ve optimizasyon için gereken verileri toplamanızı sağlayan güçlü bir araçtır. Bu yazıda profiler’ı nasıl aktive edeceğinizden başlayarak, gerçek dünya senaryolarında nasıl kullanacağınıza kadar her şeyi ele alacağız.
MongoDB Profiler Nedir ve Nasıl Çalışır
MongoDB Profiler, veritabanı operasyonlarını izleyen ve bunları system.profile koleksiyonuna yazan dahili bir mekanizmadır. Her veritabanı için ayrı ayrı etkinleştirilebilir ve üç farklı seviyede çalışabilir.
Profiler seviyeleri şöyle özetlenebilir:
- Seviye 0: Profiler kapalı, hiçbir şey loglanmaz
- Seviye 1: Sadece yavaş sorgular loglanır (slowOpThreshold değerini aşanlar)
- Seviye 2: Tüm operasyonlar loglanır (dikkatli kullanın, yoğun sistemlerde tehlikeli olabilir)
system.profile koleksiyonu varsayılan olarak 1MB boyutunda bir capped collection’dır. Yoğun sistemlerde bu dolabilir ve eski kayıtların üzerine yazılabilir. Buna dikkat etmek gerekir.
Profiler’ı Aktive Etmek
Mevcut Durumu Kontrol Etmek
Önce hangi seviyede olduğunuzu kontrol edin:
mongosh --eval "db.getProfilingStatus()" --quiet
Ya da mongosh içinde doğrudan:
mongosh
use myapp_production
db.getProfilingStatus()
Çıktı şuna benzer olacaktır:
{
was: 0,
slowms: 100,
sampleRate: 1,
ok: 1
}
Burada was: 0 profiler’ın kapalı olduğunu, slowms: 100 ise yavaş sorgu eşiğinin 100ms olarak ayarlandığını gösterir.
Profiler’ı Açmak
Seviye 1 ile başlayın. Bu hem güvenli hem de yeterince bilgi verir:
mongosh --eval "
use myapp_production
db.setProfilingLevel(1, { slowms: 50, sampleRate: 1.0 })
" --quiet
Buradaki parametreler:
- slowms: Bu değerin üzerindeki sorgular loglanır (milisaniye cinsinden)
- sampleRate: 0.0 ile 1.0 arasında, kaç sorgunun örnekleneceği. 0.5 değeri yavaş sorguların %50’sini loglar
- slowOpSampleRate: MongoDB 4.0+ için bazı sürümlerde bu parametre kullanılır
Prodüksiyonda tüm sorguları loglamak istiyorsanız ama sistemi boğmak istemiyorsanız sampleRate: 0.3 gibi bir değer mantıklı olabilir.
Replica Set Ortamında Profiler
Replica set kullanıyorsanız profiler her node üzerinde ayrı ayrı etkinleştirilmeli. Bunu script ile yapabilirsiniz:
#!/bin/bash
# replica_profiler_enable.sh
HOSTS=("mongo1:27017" "mongo2:27017" "mongo3:27017")
DB_NAME="myapp_production"
SLOW_MS=50
for HOST in "${HOSTS[@]}"; do
echo "Profiler aktif ediliyor: $HOST"
mongosh "mongodb://${HOST}/${DB_NAME}" --eval
"db.setProfilingLevel(1, { slowms: ${SLOW_MS} })"
--quiet
echo "Tamamlandi: $HOST"
done
echo "Tum node'larda profiler aktif."
system.profile Koleksiyonunu Anlamak
Profiler etkin olduğunda sorgular system.profile koleksiyonuna yazılır. Bir profiler kaydının yapısını anlamak çok önemli:
mongosh
use myapp_production
db.system.profile.findOne()
Tipik bir kayıt şuna benzer:
{
op: 'query',
ns: 'myapp_production.users',
command: {
find: 'users',
filter: { email: '[email protected]', status: 'active' },
sort: { createdAt: -1 },
limit: 10
},
keysExamined: 15420,
docsExamined: 15420,
cursorExhausted: true,
numYield: 120,
nreturned: 3,
millis: 234,
planSummary: 'COLLSCAN',
ts: ISODate("2024-01-15T10:23:45.123Z"),
client: '10.0.1.45',
appName: 'myapp-backend'
}
Bu kayıtta dikkat edilmesi gereken alanlar:
- millis: Sorgunun kaç milisaniye sürdüğü
- keysExamined: İncelenen index anahtarı sayısı
- docsExamined: İncelenen döküman sayısı
- nreturned: Döndürülen döküman sayısı
- planSummary:
COLLSCANgörüyorsanız index yok demektir, tehlike! - numYield: Sorgunun kaç kez yield ettiği (yüksekse lock contention olabilir)
Kritik oran: docsExamined / nreturned oranı yüksekse verimsiz bir sorgunuz var. 15420 döküman inceleyip 3 tane döndürmek tam bir kırmızı bayrak.
Yavaş Sorguları Bulmak
Temel Analiz Sorguları
En yavaş 10 sorguyu bulmak için:
mongosh
use myapp_production
db.system.profile.find().sort({ millis: -1 }).limit(10).forEach(function(doc) {
print("---");
print("Süre: " + doc.millis + "ms");
print("Operasyon: " + doc.op);
print("Koleksiyon: " + doc.ns);
print("Plan: " + doc.planSummary);
print("Incelenen: " + doc.docsExamined + " dok / " + doc.keysExamined + " key");
print("Dönen: " + doc.nreturned);
if (doc.command) {
print("Sorgu: " + JSON.stringify(doc.command.filter || doc.command.query));
}
});
Zaman Aralığına Göre Filtreleme
Son 1 saatteki yavaş sorguları bulmak:
mongosh
use myapp_production
var oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
db.system.profile.find({
ts: { $gte: oneHourAgo },
millis: { $gte: 100 }
}).sort({ millis: -1 }).limit(20).pretty()
COLLSCAN Yapan Sorguları Bulmak
Bu sorgular index kullanmıyor ve en tehlikelileri:
mongosh
use myapp_production
db.system.profile.find({
planSummary: "COLLSCAN",
ts: { $gte: new Date(Date.now() - 3600000) }
}).sort({ millis: -1 }).forEach(function(doc) {
print("Koleksiyon: " + doc.ns);
print("Süre: " + doc.millis + "ms");
print("Incelenen dokuman: " + doc.docsExamined);
if (doc.command) {
printjson(doc.command.filter || doc.command.query || {});
}
print("---");
});
Koleksiyon Bazında Yavaş Sorgu Özeti
mongosh
use myapp_production
db.system.profile.aggregate([
{
$match: {
ts: { $gte: new Date(Date.now() - 3600000) }
}
},
{
$group: {
_id: "$ns",
avgMillis: { $avg: "$millis" },
maxMillis: { $max: "$millis" },
minMillis: { $min: "$millis" },
count: { $sum: 1 },
totalMillis: { $sum: "$millis" },
collscans: {
$sum: {
$cond: [{ $eq: ["$planSummary", "COLLSCAN"] }, 1, 0]
}
}
}
},
{ $sort: { totalMillis: -1 } }
])
Bu aggregation size hangi koleksiyonun toplam olarak en çok zaman harcadığını gösterir. Tek bir sorgu 500ms olabilir ama dakikada 1000 kez çalışan 50ms’lik sorgu çok daha fazla kaynak tüketir.
Gerçek Dünya Senaryosu: E-ticaret Platformu
Diyelim ki bir e-ticaret platformu yönetiyorsunuz. Saat 14:00-16:00 arası her gün yavaşlama var, kullanıcılar ürün arama ve sipariş listeleme sayfalarından şikayet ediyor.
Adım 1: Problemi Tespit Etmek
# Profiler'ı aktive et
mongosh "mongodb://mongo-primary:27017/ecommerce" --eval
"db.setProfilingLevel(1, { slowms: 30 })" --quiet
echo "Profiler aktif, 2 saat bekleniyor..."
sleep 7200
# Sonuclari analiz et
mongosh "mongodb://mongo-primary:27017/ecommerce" --eval "
db.system.profile.aggregate([
{
$match: {
millis: { $gte: 30 },
ts: { $gte: new Date(Date.now() - 7200000) }
}
},
{
$group: {
_id: {
ns: '$ns',
plan: '$planSummary'
},
count: { $sum: 1 },
avgMs: { $avg: '$millis' },
maxMs: { $max: '$millis' }
}
},
{ $sort: { count: -1 } },
{ $limit: 15 }
])
" --quiet
Adım 2: Spesifik Sorguları Analiz Etmek
Diyelim ki ecommerce.orders koleksiyonunun sorunlu olduğunu gördünüz. Bu koleksiyondaki en problematik sorguyu inceleyelim:
mongosh
use ecommerce
// En yavaş orders sorgusunu bul
var slowQuery = db.system.profile.findOne(
{
ns: "ecommerce.orders",
millis: { $gte: 200 }
},
{},
{ sort: { millis: -1 } }
);
// Sorguyu explain ile analiz et
var filterQuery = slowQuery.command.filter || slowQuery.command.query;
print("Yavaş sorgu filtresi:");
printjson(filterQuery);
// Explain çalıştır
db.orders.find(filterQuery).explain("executionStats")
Adım 3: Explain Çıktısını Yorumlamak
Explain çıktısında dikkat edilmesi gerekenler:
- COLLSCAN: Index yok, tüm koleksiyon taranıyor
- IXSCAN: Index kullanılıyor, iyi
- totalDocsExamined: İncelenen döküman sayısı
- nReturned: Döndürülen döküman sayısı
- executionTimeMillis: Gerçek çalışma süresi
Bu senaryoda orders koleksiyonunda userId + status + createdAt üçlüsünü sorgulayan bir sorgunun sadece userId indexi üzerinden çalıştığını ve gereksiz yere binlerce dökümanı taradığını buldunuz.
Adım 4: Index Oluşturmak
mongosh
use ecommerce
// Mevcut indexleri kontrol et
db.orders.getIndexes()
// Compound index oluştur
db.orders.createIndex(
{
userId: 1,
status: 1,
createdAt: -1
},
{
name: "idx_user_status_date",
background: true // Eski MongoDB sürümleri için
}
)
// MongoDB 4.2+ için
db.orders.createIndex(
{ userId: 1, status: 1, createdAt: -1 },
{ name: "idx_user_status_date" }
)
// Index oluşturma durumunu izle
db.currentOp({ "command.createIndexes": { $exists: true } })
Adım 5: Sonucu Doğrulamak
Index oluşturduktan sonra profiler verilerini tekrar kontrol edin:
mongosh
use ecommerce
// Onceki ve sonraki periyot karsilastirmasi
// Bu ornekte indexi olusturduktan 1 saat sonra
db.system.profile.aggregate([
{
$match: {
ns: "ecommerce.orders",
ts: { $gte: new Date(Date.now() - 3600000) }
}
},
{
$group: {
_id: "$planSummary",
count: { $sum: 1 },
avgMs: { $avg: "$millis" },
maxMs: { $max: "$millis" }
}
}
])
COLLSCAN sayısı sıfıra düşmüş ve ortalama sorgu süresi 234ms’den 8ms’ye gerilemiş olmalı.
Profiler Loglarını Dışa Aktarmak ve Analiz Etmek
Uzun vadeli analiz için profiler verilerini dışa aktarabilirsiniz:
#!/bin/bash
# export_slow_queries.sh
DATE=$(date +%Y%m%d_%H%M%S)
OUTPUT_FILE="/var/log/mongodb/slow_queries_${DATE}.json"
DB_NAME="myapp_production"
THRESHOLD_MS=50
mongoexport
--host localhost
--port 27017
--db "$DB_NAME"
--collection "system.profile"
--query "{"millis": {"$gte": ${THRESHOLD_MS}}}"
--sort "{"millis": -1}"
--out "$OUTPUT_FILE"
echo "Slow query logu disa aktarildi: $OUTPUT_FILE"
echo "Toplam kayit: $(wc -l < "$OUTPUT_FILE")"
Bu script’i cron ile çalıştırarak günlük raporlar oluşturabilirsiniz:
# crontab -e
# Her gece 02:00'de slow query raporu al
0 2 * * * /usr/local/bin/export_slow_queries.sh >> /var/log/mongodb/profiler_cron.log 2>&1
system.profile Koleksiyonunu Yönetmek
Varsayılan 1MB limit yetersiz gelebilir. Özellikle yoğun sistemlerde boyutu artırmak gerekir:
mongosh
use myapp_production
// Mevcut profil koleksiyonu boyutunu kontrol et
db.system.profile.stats()
// Profiler'i kapat, koleksiyonu yeniden oluştur, sonra aç
db.setProfilingLevel(0)
db.system.profile.drop()
// 50MB capped collection oluştur
db.createCollection("system.profile", {
capped: true,
size: 50 * 1024 * 1024, // 50MB
max: 100000 // Maksimum 100k kayit
})
// Profiler'i tekrar aktif et
db.setProfilingLevel(1, { slowms: 50 })
Profiler’ı Otomatize Etmek
Prodüksiyonda profiler’ı sürekli açık bırakmak tercih edilmeyebilir. İşte akıllı bir izleme scripti:
#!/bin/bash
# smart_profiler.sh
# Sistemi izle, yük yukseldiyse profiler'i ac
MONGO_HOST="localhost"
MONGO_PORT="27017"
DB_NAME="myapp_production"
LOG_FILE="/var/log/mongodb/smart_profiler.log"
SLOW_MS=50
CHECK_INTERVAL=60 # saniye
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
get_current_ops() {
mongosh "mongodb://${MONGO_HOST}:${MONGO_PORT}/admin" --quiet --eval
"db.currentOp().inprog.length"
}
enable_profiler() {
mongosh "mongodb://${MONGO_HOST}:${MONGO_PORT}/${DB_NAME}" --quiet --eval
"db.setProfilingLevel(1, { slowms: ${SLOW_MS} })"
log "Profiler AKTIF edildi (currentOp: $1)"
}
disable_profiler() {
mongosh "mongodb://${MONGO_HOST}:${MONGO_PORT}/${DB_NAME}" --quiet --eval
"db.setProfilingLevel(0)"
log "Profiler KAPATILDI (currentOp: $1)"
}
PROFILER_ACTIVE=false
while true; do
CURRENT_OPS=$(get_current_ops)
if [ "$CURRENT_OPS" -gt 50 ] && [ "$PROFILER_ACTIVE" = false ]; then
enable_profiler "$CURRENT_OPS"
PROFILER_ACTIVE=true
elif [ "$CURRENT_OPS" -lt 20 ] && [ "$PROFILER_ACTIVE" = true ]; then
disable_profiler "$CURRENT_OPS"
PROFILER_ACTIVE=false
fi
sleep "$CHECK_INTERVAL"
done
Aggregation Pipeline’larını Profil Etmek
Aggregation sorgular da profiler’a düşer ve bunlar genellikle en karmaşık olanlardır:
mongosh
use myapp_production
// Son 24 saatteki yavaş aggregation'lari bul
db.system.profile.find({
op: "command",
"command.aggregate": { $exists: true },
millis: { $gte: 100 },
ts: { $gte: new Date(Date.now() - 86400000) }
}).sort({ millis: -1 }).limit(5).forEach(function(doc) {
print("=== YAVAS AGGREGATION ===");
print("Koleksiyon: " + doc.ns);
print("Süre: " + doc.millis + "ms");
print("Incelenen: " + doc.docsExamined + " dok");
print("Pipeline:");
if (doc.command && doc.command.pipeline) {
doc.command.pipeline.forEach(function(stage, i) {
print(" Stage " + (i+1) + ": " + JSON.stringify(Object.keys(stage)));
});
}
print("");
});
Aggregation pipeline’larında yavaşlığın en yaygın nedeni $match stage’inin pipeline’ın başında olmamasıdır. $match her zaman en başa alınmalı ki MongoDB gereksiz dökümanları işlemesin.
İpuçları ve Dikkat Edilmesi Gerekenler
Profiler kullanırken bazı önemli noktalara dikkat etmek gerekir:
- Seviye 2’den kaçının: Tüm operasyonları loglamak ciddi performans cezasına yol açabilir. Sadece geliştirme ortamında veya çok kısa süreli testlerde kullanın.
- sampleRate kullanın: Prodüksiyonda yüksek trafik varsa
sampleRate: 0.1ile başlayın. Yavaş sorguların %10’unu yakalamak bile çok değerli veri sağlar.
- Profiler’ı geçici açın: Sorunu tespit ettikten sonra profiler’ı kapatmayı unutmayın. Kalıcı olarak açık bırakmak istemiyorsanız bir timer koyun.
- system.profile koleksiyonunu izleyin: Capped collection dolduğunda eski veriler kaybolur. Boyutu sisteminize göre ayarlayın.
- Atlas kullanıcıları için: MongoDB Atlas’ta Performance Advisor otomatik olarak yavaş sorguları tespit eder ve index önerileri sunar. Ancak kendi deployment’ınızda manuel yapmanız gerekir.
- Index oluştururken dikkatli olun: Prodüksiyonda index oluşturma işlemi I/O yoğundur. Büyük koleksiyonlar için bunu yoğun olmayan saatlerde yapın ve
mongosh‘dan çıkmadan önce işlemin tamamlanmasını bekleyin.
Sonuç
MongoDB Profiler, veritabanı performans sorunlarını tespit etmede en değerli araçlardan biridir. Doğru kullanıldığında hangi sorguların sorun çıkardığını, hangi koleksiyonların index’e ihtiyaç duyduğunu ve uygulamanızın veritabanını nasıl kullandığını net biçimde görmenizi sağlar.
Genel yaklaşımı şöyle özetleyebiliriz: Önce seviye 1 ile başlayın ve slowms değerini sisteminizin normal sorgu sürelerinin 2-3 katına ayarlayın. Bir kaç saat veri toplayın. system.profile üzerinde aggregation sorguları çalıştırarak problematik koleksiyonları ve sorguları bulun. Explain ile analiz edin, gerekli index’leri oluşturun ve sonucu doğrulayın.
Profiler tek başına yeterli değildir. db.stats(), db.collection.stats() ve mongotop ile birlikte kullanıldığında çok daha kapsamlı bir görünüm elde edersiniz. Ama yavaş sorgu analizinde başlangıç noktası her zaman profiler olmalıdır. Sisteminiz yavaşladığında sezgiyle değil, veriyle hareket edin.