Laravel'de PostgreSQL vs MongoDB: Hangisi Ne Zaman?
"Veritabanı olarak ne kullanayım?" sorusu, projenin en başında sorulması gereken ama genellikle "MySQL kullanırız" diye geçiştirilen bir soru. Ben de yıllarca MySQL ile gittim, sonra PostgreSQL'i keşfettim, sonra bir projede MongoDB denedim. Her birinin yeri var ama hangisinin ne zaman doğru seçim olduğunu anlamak biraz deneyim gerektiriyor.
Bu yazıda PostgreSQL ve MongoDB'yi Laravel geliştiricisi gözüyle karşılaştıracağım. Teorik tanımlardan çok, pratikte ne işe yaradıklarını anlatmaya çalışacağım.
Temel Fark: İlişkisel vs Belge Tabanlı
PostgreSQL ilişkisel (relational) bir veritabanı. Veri tablolarda yaşar, tablolar arasında foreign key'lerle ilişkiler kurarsınız. Veriyi eklemeden önce yapısını tanımlamanız gerekir — migration yazarsınız, tablo oluşturursunuz, sonra veri girersiniz.
MongoDB belge tabanlı (document-oriented) bir veritabanı. Veri JSON benzeri BSON belgelerinde yaşar. Şema zorunlu değil — belgeyi eklersiniz, yapısı kendiliğinden oluşur. Farklı belgeler farklı alanlara sahip olabilir.
Şöyle düşünün:
- PostgreSQL: Excel tablosu gibi. Sütunlar belirli, her satır aynı yapıda.
- MongoDB: JSON dosyası gibi. Her belge farklı yapıda olabilir.
Veri Modelleme Farkı
Bir kullanıcı-sipariş ilişkisini düşünelim.
PostgreSQL'de: İki ayrı tablo, foreign key ile bağlı:
-- users tablosu
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255) UNIQUE
);
-- orders tablosu
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
total DECIMAL(10,2),
status VARCHAR(50)
);
MongoDB'de: Siparişler kullanıcı belgesinin içine gömülü:
{
"_id": "ObjectId(...)",
"name": "ProjeMan",
"email": "[email protected]",
"orders": [
{"total": 150.00, "status": "completed"},
{"total": 89.90, "status": "pending"}
]
}
PostgreSQL'de bir kullanıcının siparişlerini almak için JOIN yaparsınız. MongoDB'de tek bir sorgu ile belgenin tamamını çekersiniz. Okuma hızı açısından MongoDB burada avantajlı — ama bu avantajın bedeli var, birazdan değineceğim.
Sorgu Dili
PostgreSQL SQL kullanır — bildiğimiz, sevdiğimiz, 50 yıllık standart:
SELECT u.name, COUNT(o.id) as order_count, SUM(o.total) as total_spent
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.status = 'completed'
GROUP BY u.name
ORDER BY total_spent DESC
LIMIT 5;
MongoDB'de aynı iş aggregation pipeline ile yapılır:
db.users.aggregate([
{ $unwind: "$orders" },
{ $match: { "orders.status": "completed" } },
{ $group: {
_id: "$name",
order_count: { $sum: 1 },
total_spent: { $sum: "$orders.total" }
}},
{ $sort: { total_spent: -1 } },
{ $limit: 5 }
]);
SQL bilen herkes PostgreSQL sorgusunu okuyabilir. MongoDB'nin aggregation pipeline'ı ise ayrı bir dünya — güçlü ama öğrenme eğrisi var.
Laravel Entegrasyonu
PostgreSQL — Native Destek
Laravel PostgreSQL'i kutudan destekliyor. .env'de driver'ı değiştirmek yeterli:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=projeman
DB_USERNAME=postgres
DB_PASSWORD=secret
Eloquent, migration, seeder — her şey olduğu gibi çalışır. Ekstra paket gerekmiyor.
// Migration — MySQL ile neredeyse aynı
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 10, 2);
$table->jsonb('attributes'); // PostgreSQL'e özel: JSONB!
$table->timestamps();
});
// Eloquent — değişen hiçbir şey yok
$products = Product::where('price', '>', 100)
->orderBy('name')
->get();
// JSONB sorgulama — PostgreSQL'in süper gücü
$products = Product::whereJsonContains('attributes->colors', 'red')->get();
jsonb sütunu PostgreSQL'in en güzel özelliklerinden biri. İlişkisel yapının içinde esnek JSON verisi saklayabiliyorsunuz — ve üstüne index atabiliyorsunuz. MongoDB'nin esnekliğini ilişkisel yapının içinde kullanmak gibi düşünün.
MongoDB — Topluluk Paketi
MongoDB için mongodb/laravel-mongodb paketini kurmanız gerekiyor:
composer require mongodb/laravel-mongodb
DB_CONNECTION=mongodb
DB_HOST=127.0.0.1
DB_PORT=27017
DB_DATABASE=projeman
use MongoDB\Laravel\Eloquent\Model;
class Product extends Model
{
protected $connection = 'mongodb';
protected $collection = 'products';
public function reviews()
{
return $this->embedsMany(Review::class);
}
}
// Sorgu — Eloquent syntax'ı aynı
$products = Product::where('price', '>', 100)->get();
// Gömülü belge ekleme
$product->reviews()->create([
'user_id' => auth()->id(),
'rating' => 5,
'comment' => 'Harika ürün!',
]);
Eloquent syntax'ı büyük ölçüde aynı kalıyor, bu güzel. Ama bazı farklar var — embedsMany gibi ilişki türleri, migration yapısı, index tanımlama şekli farklı. Ve bazen Eloquent'in arkasından MongoDB driver'ına özgü davranışlarla karşılaşıyorsunuz.
Transaction ve Veri Tutarlılığı
Bu ikisi arasındaki en kritik fark burası.
PostgreSQL: Doğuştan ACID uyumlu. Transaction açarsınız, her şey ya tamamen olur ya hiç olmaz:
DB::transaction(function () {
$order = Order::create([...]);
$order->items()->createMany([...]);
Payment::create([...]);
// Herhangi bir hata olursa hepsi geri alınır
});
Isolation level'ları seçebilirsiniz (READ COMMITTED'dan SERIALIZABLE'a). Finansal veriler, stok yönetimi, ödeme sistemleri — hepsinde bu garanti şart.
MongoDB: Tek belge içi işlemler her zaman atomik. Ama birden fazla belgeyi kapsayan transaction'lar için session açmanız gerekiyor (MongoDB 4.0+):
$session = DB::connection('mongodb')->getMongoClient()->startSession();
$session->startTransaction();
try {
// Multi-document transaction
Order::create([...]);
Inventory::where('product_id', $id)->decrement('stock', $quantity);
$session->commitTransaction();
} catch (\Exception $e) {
$session->abortTransaction();
throw $e;
}
Çalışıyor, ama PostgreSQL'deki kadar doğal ve performanslı değil. MongoDB'nin felsefesi: "veriyi öyle modelle ki transaction'a ihtiyacın olmasın" — yani ilişkili verileri aynı belgeye göm. Pratikte her zaman mümkün değil ama tasarım yaparken bunu aklınızda tutun.
| Özellik | PostgreSQL | MongoDB |
|---|---|---|
| Atomiklik | Her zaman ACID | Tek belge her zaman; çoklu belge session ile |
| İzolasyon | Ayarlanabilir seviyeler | Session bazlı snapshot |
| Dayanıklılık | Write-ahead logging | Journaled writes + replica onayı |
| Rollback | Hata anında otomatik | Session içinde desteklenir |
| Performans etkisi | Minimal | Coordination maliyeti var |
Performans ve Ölçekleme
PostgreSQL dikey ölçeklenir: Daha güçlü sunucu, daha fazla RAM, daha hızlı disk. Read replica'lar ile okuma dağıtılabilir ama yazma tek sunucuda kalır. Karmaşık analitik sorgularda (JOIN, GROUP BY, window functions) çok iyi performans verir.
PostgreSQL'in index çeşitliliği muhteşem:
- B-tree: Standart — çoğu sorgu için
- GIN: JSONB, array, full-text search için
- GiST: Coğrafi veri (PostGIS ile)
- BRIN: Büyük, sıralı veri setleri için (log tabloları gibi)
MongoDB yatay ölçeklenir: Veriyi birden fazla sunucuya (shard) dağıtır. Replica set'ler ile yedeklilik ve otomatik failover sağlar. Yazma yükünü de dağıtabilir. Tek belge okumalarında çok hızlı.
MongoDB'nin index çeşitleri:
- Single-field / Compound: Temel aramalar
- Text: Kelime ve cümle araması
- Geospatial: Konum bazlı sorgular
- Wildcard: Dinamik alan yapıları için
Kısa özet: az sunucuyla çok iş → PostgreSQL. Çok sunucuyla çok veri → MongoDB.
Şema Tasarımı
PostgreSQL'de şema tasarımı normalizasyon üzerine kurulu. Veriyi tekrarlamaktan kaçınırsınız, her bilgi tek bir yerde yaşar:
users (id, name, email)
addresses (id, user_id, city, street)
orders (id, user_id, total, status)
order_items (id, order_id, product_id, quantity, price)
4 tablo, her biri kendi sorumluluğunda. Veri tutarlılığı yüksek ama bir siparişin tüm detayını almak için 3-4 JOIN gerekiyor.
MongoDB'de şema tasarımı erişim paternine göre yapılır. "Bu veriyi nasıl okuyacağım?" sorusu belirleyici:
{
"_id": "...",
"name": "ProjeMan",
"email": "[email protected]",
"addresses": [
{"city": "İstanbul", "street": "Bağdat Cad."}
],
"orders": [
{
"total": 150.00,
"status": "completed",
"items": [
{"product": "Laravel Kitabı", "quantity": 1, "price": 150.00}
]
}
]
}
Tek belge, tek sorgu, her şey burada. Ama adres değiştiğinde sadece bir yeri güncelliyorsunuz — normalizasyondan farklı olarak veri tekrarı kabul ediliyor.
Gerçek Dünya Senaryoları
Finansal Sistem / Ödeme
PostgreSQL seçin. Tartışmasız. ACID garantisi, foreign key kısıtlamaları, audit trail — bunlar olmazsa olmaz. Para söz konusu olduğunda "eventual consistency" kabul edilemez.
E-Ticaret
PostgreSQL: Müşteriler, siparişler, ödemeler, stok — çekirdek transactional veri. MongoDB: Ürün kataloğu (her ürün farklı özelliklere sahip olabilir), alışveriş sepeti, aktivite logları.
Hibrit kullanım burada en mantıklı yaklaşım.
İçerik Yönetimi (CMS)
PostgreSQL yeterli — özellikle jsonb sütunu ile farklı içerik tiplerinin farklı alanlarını tek tabloda tutabilirsiniz. Tam bir CMS için MongoDB'ye geçmenize gerek yok.
MongoDB avantajlı eğer içerik yapısı çok değişkense ve şema evrimi hızlıysa.
IoT / Olay Takibi
MongoDB öne çıkıyor. Yüksek yazma hacmi, yatay ölçekleme, esnek belge yapısı — sensör verileri, log kayıtları, event stream'ler için biçilmiş kaftan.
PostgreSQL ile de yapılır — TimescaleDB extension'ı zaman serisi verileri için PostgreSQL'i optimize ediyor.
Analitik Dashboard
PostgreSQL: Window function'lar, CTE'ler, materialized view'lar — karmaşık analitik sorguların evi. MongoDB: Aggregation pipeline iç içe ve polimorfik veriyle çalışırken güçlü.
Hibrit Yaklaşım
Gerçek dünyada çoğu ciddi proje ikisini birlikte kullanıyor. Laravel bunu destekliyor — config/database.php'de birden fazla connection tanımlayabilirsiniz:
// Çekirdek veri: PostgreSQL
$order = Order::on('pgsql')->create([
'user_id' => $user->id,
'total' => $cart->total(),
'status' => 'pending',
]);
// Analitik/log: MongoDB
DB::connection('mongodb')
->collection('events')
->insert([
'type' => 'order_created',
'order_id' => $order->id,
'user_id' => $user->id,
'cart_items' => $cart->items->toArray(),
'timestamp' => now(),
]);
PostgreSQL: Kayıt sistemi (system of record). Güvenilir, tutarlı, ilişkisel. MongoDB: Etkileşim sistemi (system of interaction). Esnek, hızlı, ölçeklenebilir.
Ekosistem ve Araçlar
PostgreSQL
- Masaüstü: pgAdmin, TablePlus, DBeaver
- CLI:
psql— scriptleme ve otomasyon için - Extension'lar: PostGIS (coğrafi), pgVector (AI vektörleri), pgcrypto (şifreleme)
- Hosting: Supabase, Amazon RDS, Google Cloud SQL, Railway, Render
- Laravel: Native destek, ek paket gerekmiyor
MongoDB
- Cloud: MongoDB Atlas (yönetilen servis)
- Masaüstü: MongoDB Compass — görsel şema analizi
- CLI:
mongosh— JavaScript destekli shell - Hosting: Atlas birincil, Docker/K8s ile self-hosted
- Laravel:
mongodb/laravel-mongodbpaketi
Hangisini Seçmeliyim?
PostgreSQL seçin:
- Veriler arası ilişkiler karmaşık ve korunmalı
- ACID garantisi şart (finans, ödeme, stok)
- Karmaşık analitik sorgular sık çalışacak
- Veri yapısı stabil ve iyi tanımlı
- Ekip SQL biliyor
MongoDB seçin:
- Şema sık değişecek, esneklik lazım
- Yatay ölçekleme zorunlu (çok yüksek trafik)
- Belge yapısı kayıttan kayıda farklılık gösteriyor
- Yüksek yazma hacmi var (IoT, log, event)
- Gömülü veri ile tek sorguda okuma öncelikli
Hibrit kullanın:
- Bazı veriler kesin tutarlılık istiyor (PostgreSQL)
- Bazı veriler esneklik ve hız istiyor (MongoDB)
- Farklı özellikler farklı ölçekleme gerektiriyor
Benim Tercihim
Çoğu Laravel projesinde PostgreSQL kullanıyorum. Sebebi basit: MySQL'in yaptığı her şeyi yapıyor, üstüne jsonb, window functions, CTE'ler, better index types ve daha iyi veri tutarlılığı sunuyor. Laravel'de geçiş maliyeti sıfır — .env'de driver değiştirmek yeterli.
MongoDB'yi sadece gerçekten ihtiyaç olduğunda ekliyorum — genellikle log/event verisi veya yapısı çok değişken katalog verileri için. "Her ihtimale karşı MongoDB de koyalım" yaklaşımından uzak duruyorum, çünkü her eklenen teknoloji bakım maliyeti demek.
Doğru veritabanı seçimi proje gereksinimlerine bağlı. Ama emin olamıyorsanız PostgreSQL ile başlayın — yanlış yapmış olmazsınız.