İçeriğe atla

Veritabanı Nasıl Seçilir: SQL, NoSQL, NewSQL ve Edge Karşılaştırması

SQL, NoSQL, NewSQL ve edge seçenekleri arasında doğru veritabanını seçme rehberi: her kategorinin trade-off'ları, seçim kriterleri ve bir karar çerçevesi.

Ayhan Sipahi Ayhan Sipahi

Bir iş yükü için yanlış veritabanı motoru seçmek, ilerleyen süreçte pahalı migration’lara zemin hazırlar: MongoDB üzerinde 1.000 kayıtla sorunsuz çalışan bir ürün kataloğu, doğru şema ve index tasarımı olmadan 100.000 kayıtta tüm koleksiyon taramalarına dönüşebilir. Sorunun kökeni veri modeli ile erişim örüntüsü arasındaki uyumsuzluktur, veritabanının kendisi değil. SQL, NoSQL, NewSQL ve edge veritabanı kategorilerinin her biri, ilk migration zorunlu hale gelmeden doğru seçime yön veren somut trade-off’lar taşır.

Çoğu proje için varsayılan seçim PostgreSQL’dir; PostgreSQL’in iyi karşılamadığı erişim örüntüleri için de önüne Redis konur. Aşağıdaki diğer kategoriler, ancak somut bir gereksinim bu ikiliyi devre dışı bıraktığında yerini hak eder: bölgeler arası yazma yerelliği, zaman serisi hacmi, offline replikasyon veya alana gerçekten oturan bir document modeli.

Veritabanı Seçiminin Bedeli#

Teknik Borç Patlaması: Proje ortasında veritabanı değiştirmek, veri okuyan ya da yazan her katmana dokunur. Zaman serisi verileri için MySQL kullanmak, aşırı tarih fonksiyonları ve alt sorgularla sorgu karmaşıklığı yaratır. InfluxDB gibi özelleşmiş bir depoya geçmek; sorgu katmanını, retention mantığını ve bunların üzerine kurulmuş her dashboard’u yeniden yazmak demektir.

Takım Verimliliğine Etkisi: Seçiminiz doğrudan geliştirme hızını etkiler. SQL’e hakim bir takım MongoDB document sorgularını devraldığında, modelleme, indeksleme ve okuma hatalarını ayıklama alışkanlıklarını aylarca yeniden öğrenir. NoSQL uzmanlarının katı SQL şemalarıyla zorlanması da ters yönde, basit problemleri aşırı mühendislikle çözmeye iter.

Gizli Operasyonel Maliyetler:

  • Self-hosted PostgreSQL: en düşük fatura, en yüksek mühendis zamanı payı (yamalar, vacuum tuning, failover tatbikatları)
  • Managed PostgreSQL (RDS, Cloud SQL, Neon): daha yüksek fatura, bu operasyonel zamanın çoğu sağlayıcıya devrediliyor
  • DynamoDB: kullanıma dayalı fatura ve neredeyse sıfır yönetim; erişim örüntüleri baştan tasarlandığı sürece

Klasik Veritabanı Kategorileri#

İlişkisel (SQL) Veritabanları#

PostgreSQL#

PostgreSQL çoğu proje için mükemmel bir varsayılan seçimdir. En iyi anlamda sıkıcı: güvenilir, iyi dokümante edilmiş ve edge case’leri zarif şekilde hallediyor. Son major sürümler performansı geliştirmeye ve SQL/JSON standart desteğini genişletmeye devam ediyor; bir zamanlar takımları document store’a iten JSON boşluğu büyük ölçüde kapandı.

PostgreSQL’in Parladığı Durumlar:

  • ACID transaction gerektiren karmaşık iş mantığı
  • Sofistike sorgular gerektiren analytics workload’ları
  • Hem ilişkisel hem document storage (JSONB) ihtiyacı olan uygulamalar
  • SQL’e aşina takımlar

JSONB’nin Kazandırdığı Yer: İlişkisel bir uygulamayı JSON desteği için PostgreSQL’e taşıyan takımlar bunu genellikle ikinci bir veri deposunu silmek için yapar. Esnek metadata’yı ilişkisel sütunların yanında tutmak document store’u mimariden çıkarır.

// PostgreSQL JSONB ile - iki dünyanın da en iyisi
const user = await db.query(`
  SELECT id, email, 
         preferences->>'theme' as theme,
         preferences->'notifications'->>'email' as email_notifications
  FROM users 
  WHERE preferences @> '{"beta_features": true}'
`);

Dikkat Edilmesi Gerekenler:

  • Sık güncellemelerde write amplification (HOT update’leri akıllıca kullanın)
  • Connection management: production’da pgBouncer kullanın
  • Yoğun yazma workload’ları için vacuum tuning gerekli

MySQL#

MySQL web’in en büyük sitelerini besleyerek itibarını kazandı. Hızlı, iyi anlaşılır ve web uygulamaları etrafında inşa edilmiş bir ekosisteme sahip.

MySQL; okuma ağırlıklı web uygulamalarında, master-slave replication’a ya da mevcut MySQL uzmanlığına sahip takımlarda ve community desteğinden yararlanan bütçe bilinçli projelerde iyi çalışır.

Pratikte Okuma Ölçeklemesi: MySQL okumaları replica ekleyerek ve bu replica’ları bir cache katmanı gibi ele alarak ölçekler: denormalize veri, agresif indeksleme ve stratejik partitioning. Throughput tavanı motordan çok satır boyutuna ve indeksin uygunluğuna bağlıdır.

-- Okuma performansı için optimize edilmiş MySQL
CREATE TABLE user_stats (
  user_id INT PRIMARY KEY,
  total_orders INT DEFAULT 0,
  last_order_date DATE,
  lifetime_value DECIMAL(10,2),
  INDEX idx_lifetime_value (lifetime_value DESC),
  INDEX idx_last_order (last_order_date)
) ENGINE=InnoDB;

Trade-off’lar zamanla ortaya çıkıyor: PostgreSQL’den daha az sofistike bir query planner, sonradan eklenmiş hissettiren JSON desteği ve multi-master setup’larda zorlaşabilen replication lag.

SQLite#

SQLite’ı küçümsemeyin. Artık mobil uygulamaların çok ötesinde çalışıyor ve doğru konfigürasyonla şaşırtıcı workload’ları kaldırabiliyor: yerel veri gereksinimleri olan edge uygulamaları, development ve test ortamları, 100GB altı veri ve mütevazı concurrency olan uygulamalar, embedded sistemler ve IoT cihazları.

Performans Gerçeklik Kontrolü: Okumalar page cache’ten servis edilen sıradan dosya okumalarıdır; tek bir node’un beklenenden uzağa gitmesinin sebebi budur. Asıl kısıt eşzamanlı yazmalardır: WAL modunda bile veritabanını aynı anda tek bir yazıcı tutar, okuyucular ise kesintisiz devam eder.

// better-sqlite3: eşzamanlı okuyucular için WAL modu
import Database from 'better-sqlite3';

const db = new Database('app.db');
db.pragma('journal_mode = WAL');
db.pragma('synchronous = NORMAL');  // WAL ile yeterince dayanıklı, çok daha az fsync
db.pragma('cache_size = -64000');   // negatif değer KiB anlamına gelir, yani 64MB
db.pragma('temp_store = MEMORY');

NoSQL Veritabanları#

MongoDB#

MongoDB çok nefret topluyor, genellikle haklı olarak, ama belirli senaryolarda gerçekten mükemmel: gelişen şemalarla hızlı prototipleme, içerik yönetim sistemleri, çeşitli ürün özelliklerine sahip katalog sistemleri ve document yapısının iş mantığıyla eşleştiği uygulamalar. Önemli olan, güçlü yanlarını anlamak ve sınırlarına göre tasarım yapmak.

Önemli Nokta: İndekslerinizi her zaman önce tasarlayın. Doğru indeksler olmadan MongoDB, sahip olduğu özelliklerin çok gerisinde bir performans sunar.

// E-ticaret için MongoDB indeksleme stratejisi
db.products.createIndex({
  "category": 1,
  "price": 1,
  "createdAt": -1
});

// Fasett arama için compound index
db.products.createIndex({
  "category": 1,
  "attributes.brand": 1,
  "attributes.color": 1,
  "price": 1
});

Production Tuzakları:

  • Memory kullanımı working set boyutuyla büyür
  • Aggregation pipeline’ları memory-intensive olabilir
  • Sharding shard key’lerin dikkatli planlanmasını gerektirir

Redis#

Redis, cache konusunda çok iyi olan bir veri yapısı sunucusudur. Diğer veri yapıları ise SQL’in beceriksizce çözdüğü koordinasyon problemlerini çözer.

Cache’in Ötesindeki Redis Kullanım Alanları:

  • Otomatik expire ile session storage
  • Sliding window’larla rate limiting
  • Gerçek zamanlı leaderboard’lar ve counter’lar
  • Gerçek zamanlı özellikler için pub/sub
  • Koordinasyon için distributed lock’lar

Yaygın Pattern: servisler arasında paylaşılan distributed rate limiting:

// Redis'te sliding window rate limiter
async function checkRateLimit(userId: string, limit: number, windowMs: number) {
  const key = `rate_limit:${userId}`;
  const now = Date.now();
  const windowStart = now - windowMs;
  
  const pipeline = redis.pipeline();
  pipeline.zremrangebyscore(key, 0, windowStart);
  pipeline.zadd(key, now, now);
  pipeline.zcard(key);
  pipeline.expire(key, Math.ceil(windowMs / 1000));
  
  const results = await pipeline.exec();
  const currentCount = results[2][1] as number;
  
  return currentCount <= limit;
}

DynamoDB#

DynamoDB’nin performansı, veri modelinin erişim örüntülerinize ne kadar uyduğuna tamamen bağlı; ama cazibesi de gerçek: pay-per-use fiyatlandırmasıyla gerçek serverless, öngörülebilir tek haneli milisaniye gecikme, otomatik scaling ve backup, multi-region uygulamalar için global tablolar.

DynamoDB Zihinsel Modeli: Tabloya şeklini, cevaplaması gereken sorgular verir. Tabloyu oluşturmadan önce tüm access pattern’ları listeleyin; sonradan eklenen bir pattern genellikle yeni bir GSI’ya ya da tam bir backfill’e mal olur.

// DynamoDB single-table tasarım paterni
interface GameRecord {
  PK: string;  // USER#123 veya GAME#456
  SK: string;  // PROFILE veya SCORE#2024-01-15
  Type: string;  // USER veya GAME veya SCORE
  GSI1PK?: string; // İkincil access pattern'lar için
  GSI1SK?: string;
  // ... diğer özellikler
}

// Kullanıcının son skorlarını sorgula (AWS SDK v3)
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, QueryCommand } from '@aws-sdk/lib-dynamodb';

const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));

const { Items } = await docClient.send(new QueryCommand({
  TableName: 'GameData',
  KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
  ExpressionAttributeValues: {
    ':pk': 'USER#123',
    ':sk': 'SCORE#'
  },
  ScanIndexForward: false, // En son önce
  Limit: 10
}));

Tuzaklar da gerçek: hot partition’lar tüm uygulamayı throttle edebilir, sorgu pattern’ları önceden bilinmeli, karmaşık ilişkiler dikkatli GSI tasarımı gerektirir ve FilterExpression’lar yine de read capacity tüketir.

NewSQL: İki Dünyanın En İyisi#

CockroachDB#

CockroachDB, global dağıtımla PostgreSQL uyumluluğu vaat ediyor. Pratikte bu vaatlerin çoğunu yerine getiriyor, ama önemli uyarılarla: wire protokolü PostgreSQL’in olsa da altındaki çalışma modeli dağıtık bir consensus katmanı ve bu hem gecikmede hem de eksik özelliklerde kendini gösteriyor.

CockroachDB’nin Mantıklı Olduğu Durumlar:

  • Strong consistency gerektiren global uygulamalar
  • Bölgeler arasında ACID gerektiren finansal sistemler
  • Single-node PostgreSQL’i aşan uygulamalar
  • Otomatik sharding ile SQL isteyen takımlar

Uygulama Örneği: CockroachDB, birden fazla bölgeye yayılan fintech uygulamaları için iyi çalışır. Otomatik geo-partitioning kullanıcı verilerini compliance için doğru bölgelerde tutarken, finansal transaction’lar için strong consistency sağlar.

-- CockroachDB geo-partitioning.
-- Partition sütunu primary index'in ön eki olmak zorunda,
-- bu yüzden primary key'i region başlatıyor.
CREATE TABLE users (
  id UUID NOT NULL DEFAULT gen_random_uuid(),
  email STRING UNIQUE,
  region STRING NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now(),
  PRIMARY KEY (region, id)
) PARTITION BY LIST (region) (
  PARTITION us_users VALUES IN ('us-east', 'us-west'),
  PARTITION eu_users VALUES IN ('eu-west', 'eu-central')
);

Trade-off’lar da var: consensus nedeniyle single-node veritabanlarından daha yüksek gecikme, geleneksel PostgreSQL’den daha yüksek maliyet ve hâlâ eksik ya da farklı davranan bazı PostgreSQL özellikleri.

Edge Veritabanı Çözümleri#

PouchDB/CouchDB#

Offline çalışması gereken uygulamalar için CouchDB’nin replication modeli hâlâ referans tasarım; PouchDB aynı protokolü browser’a taşıyor: saha hizmet uygulamaları, kötü bağlantı olan bölgelerdeki mobil uygulamalar ve eventual consistency ihtiyaçları olan işbirlikçi uygulamalar.

Uygulama Kalıbı:

// PouchDB offline-first pattern
const localDB = new PouchDB('local-data');
const remoteDB = new PouchDB('https://server.com/data');

// Conflict resolution ile iki yönlü sync
const sync = localDB.sync(remoteDB, {
  live: true,
  retry: true
}).on('change', (info) => {
  console.log('Sync değişikliği:', info);
}).on('error', (err) => {
  console.log('Sync hatası:', err);
});

// App offline çalışır, online olunca sync yapar
await localDB.put({
  _id: 'user-123',
  name: 'John Doe',
  lastModified: new Date().toISOString()
});

InfluxDB#

Metrikler, loglar veya IoT verileriyle uğraşırken InfluxDB gibi özelleşmiş zaman serisi veritabanları; ingest hızı, depolama ayak izi ve aralık sorgularında genel amaçlı motorları geride bırakır.

InfluxDB; otomatik downsampling ve retention policy’leri, built-in zaman tabanlı fonksiyonlar ve aggregation’ları, zaman serisi verileri için verimli depolamayı ve monitoring araçlarıyla native entegrasyonu beraberinde getirir.

-- Sistem metrikleri için InfluxQL sorgusu
SELECT mean("cpu_usage") 
FROM "system_metrics" 
WHERE time >= now() - 24h 
GROUP BY time(1h), "host"

Veritabanı Seçim Matrisi#

Kullanım Alanına Göre#

E-ticaret Platformu:

  • Katalog: PostgreSQL (yapılandırılmış ürün verisi + özellikler için JSONB)
  • Session’lar: Redis (hızlı erişim + otomatik expire)
  • Siparişler: PostgreSQL (finansal veriler için ACID compliance)
  • Analytics: ClickHouse veya BigQuery (analitik workload’lar)

IoT Uygulaması:

  • Cihaz Durumu: Redis (gerçek zamanlı güncellemeler)
  • Zaman Serisi: InfluxDB (sensör verileri)
  • Konfigürasyon: PostgreSQL (cihaz yönetimi)
  • Edge Cache: SQLite (yerel cihaz depolaması)

Sosyal Medya Uygulaması:

  • Kullanıcı Profilleri: PostgreSQL (ilişkisel veri)
  • Gönderiler/Timeline: DynamoDB (yüksek ölçek, basit sorgular)
  • Gerçek Zamanlı: Redis Streams (bildirimler, chat)
  • Arama: Elasticsearch (içerik keşfi)

Ölçek Gereksinimlerine Göre#

Küçük Ölçek (1K-100K kullanıcı): PostgreSQL + Redis kullanım alanlarının %90’ını karşılar. Basit, iyi anlaşılır, maliyet etkin.

Orta Ölçek (100K-10M kullanıcı):

  • PostgreSQL için read replica’lar
  • Yoğun trafik özellikleri için DynamoDB
  • Arama için Elasticsearch
  • Caching için Redis cluster

Büyük Ölçek (10M+ kullanıcı):

  • Sharded PostgreSQL veya CockroachDB
  • Dikkatli partition tasarımıyla DynamoDB
  • Consistent hashing ile Redis Cluster
  • Belirli workload’lar için özelleşmiş veritabanları

Migration Senaryoları#

MongoDB’den PostgreSQL’e#

Problem: MongoDB kullanan içerik yönetim sistemleri karmaşık sorgularda zorlanabiliyor. Aggregation pipeline’ları sürdürülemez hale geliyor ve şema validation eksikliği veri kalitesi sorunlarına yol açıyor.

Çözüm: İçeriğin esnek kısımları için JSONB sütunlarıyla PostgreSQL’e geçmek; document storage’ın avantajlarını korurken SQL’in sorgu gücünü kazandırır.

Geçiş: Dual-write pattern, trafik taşınırken eski depoyu okunabilir tutar; böylece son okuyucu da taşınana kadar geri dönüş mümkün kalır:

// Dual-write migration pattern
class ContentService {
  async createPost(post: Post) {
    // Yeni PostgreSQL veritabanına yaz
    const pgResult = await this.postgresDB.insert(post);
    
    try {
      // Legacy MongoDB'ye yaz (rollback güvenliği için)
      await this.mongoDB.insertOne(post);
    } catch (error) {
      // MongoDB hatası akışı bozmamalı
      console.error('MongoDB yazma hatası:', error);
    }
    
    return pgResult;
  }
}

Sonuçlar:

  • Şema validation ve constraint’ler, veri kalitesi kontrollerini uygulama kodunun dışına taşır
  • Birden fazla varlığa dokunan okumalar, elle yazılmış aggregation adımları yerine join’e dönüşür
  • Yedeklenecek, izlenecek ve tune edilecek iki motor yerine tek motor kalır

Tek Bölgeden Çok Bölgeye#

Zorluk: Büyüyen SaaS uygulamalarının tek bölgeden global’e genişlemesi gerekir; bu da veri residency uyumu ve dünya çapında düşük gecikme demektir.

Çözüm: Tek PostgreSQL’den geo-partitioning ile CockroachDB’ye geçmek, kullanıcı verilerinin kendi bölgelerinde kalmasını sağlarken billing ve analytics için global tutarlılığı korur.

Uygulama:

-- Geo-partitioned kullanıcı verileri
ALTER TABLE users CONFIGURE ZONE USING constraints = '[+region=us-east1]';
ALTER TABLE user_profiles CONFIGURE ZONE USING constraints = '[+region=us-east1]';

-- Global veri (billing, analytics)
ALTER TABLE subscriptions CONFIGURE ZONE USING constraints = '[]';

Trade-off’lar:

  • Okumalar okyanus aşırı gitmek yerine yakındaki bir replica’dan servis edilir; gecikme kazancı buradan gelir
  • Satırları bir bölgeye sabitleyerek veri residency gereksinimleri karşılanır
  • Bölgeler arası her yazma artık bir consensus turu öder ve cluster’ı işletmek daha pahalıya gelir

Performans Karakteristikleri#

Okuma Performansı#

Tekil kayıt sorgularında throughput, motorun adından çok depolama ortamını takip eder. En üstte bellek içi Redis, ardından DynamoDB gibi managed key-value depolar, sonra da indekslenmiş ilişkisel veya document motorları gelir. Çalışma kümesi page cache’e sığdığında aradaki fark hızla kapanır; yayınlanmış sayıların kurulumdan kuruluma taşınamamasının sebebi de budur. Herhangi bir sıralamayı sabit kabul etmeden önce kendi kayıt boyutunuz, indeks yapınız ve eşzamanlılığınızla ölçün.

Karmaşık Sorgular (analitik workload’lar):

  • PostgreSQL: Mükemmel (sofistike query planner)
  • CockroachDB: İyi (distributed ama yine SQL)
  • MongoDB: Kötü (aggregation pipeline’ları)
  • DynamoDB: Uygulanamaz (sınırlı sorgu yetenekleri)

Yük Altında Yazma Performansı#

Eşzamanlı Yazma İşlemleri:

  • DynamoDB: Otomatik scale; bir partition ısınana kadar tutarlı performans
  • Redis: Memory limit’e kadar mükemmel
  • PostgreSQL: Doğru connection pooling ile iyi
  • MongoDB: Document boyutu büyüdükçe performans düşer

Uygulama Kalıpları#

Veritabanı Sharding Stratejileri#

Horizontal Sharding (verileri sunucular arasında bölme):

// Kullanıcı tabanlı sharding
function getShardForUser(userId: string): string {
  const hash = createHash('md5').update(userId).digest('hex');
  const shardIndex = parseInt(hash.substring(0, 8), 16) % NUM_SHARDS;
  return `shard_${shardIndex}`;
}

// Sorguları uygun shard'a yönlendir
class ShardedUserService {
  async getUser(userId: string) {
    const shard = getShardForUser(userId);
    return this.databases[shard].query('SELECT * FROM users WHERE id = ?', [userId]);
  }
}

Vertical Sharding (özelliğe göre ayırma):

// Domain'e göre ayrı veritabanları
class UserService {
  profiles = new DatabaseConnection('user_profiles_db');
  preferences = new DatabaseConnection('user_preferences_db');
  analytics = new DatabaseConnection('user_analytics_db');
  
  async getFullUser(userId: string) {
    const [profile, preferences, analytics] = await Promise.all([
      this.profiles.getUser(userId),
      this.preferences.getUser(userId),
      this.analytics.getUser(userId)
    ]);
    
    return { ...profile, preferences, analytics };
  }
}

Bağlantı Yönetimi#

PostgreSQL Connection Pooling:

// Production PostgreSQL setup'ı
import { Pool } from 'pg';

const pool = new Pool({
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  // Kritik production ayarları
  max: 20,  // Maksimum connection
  idleTimeoutMillis: 30000,  // Boş connection'ları kapat
  connectionTimeoutMillis: 2000, // Connection sorunlarında hızlı fail
  maxUses: 7500,  // Memory leak'leri önlemek için connection'ları rotate et
});

// Veri tutarlılığı için her zaman transaction kullan
async function transferMoney(fromUserId: string, toUserId: string, amount: number) {
  const client = await pool.connect();
  
  try {
    await client.query('BEGIN');
    
    await client.query(
      'UPDATE accounts SET balance = balance - $1 WHERE user_id = $2',
      [amount, fromUserId]
    );
    
    await client.query(
      'UPDATE accounts SET balance = balance + $1 WHERE user_id = $2',
      [amount, toUserId]
    );
    
    await client.query('COMMIT');
  } catch (error) {
    await client.query('ROLLBACK');
    throw error;
  } finally {
    client.release();
  }
}

İzleme ve Sorun Giderme#

Temel Metrikler#

PostgreSQL Temel Metrikleri:

  • Connection kullanımı (pg_stat_activity)
  • Sorgu performansı (pg_stat_statements)
  • Index kullanımı (pg_stat_user_indexes)
  • Replication lag (pg_stat_replication)
-- PostgreSQL sağlık kontrolü sorguları
-- Uzun süren sorgular
SELECT pid, now() - query_start as duration, query 
FROM pg_stat_activity 
WHERE now() - query_start > interval '5 minutes';

-- Index kullanım istatistikleri
SELECT schemaname, tablename, indexname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes 
ORDER BY idx_scan DESC;

-- State'e göre connection sayısı
SELECT state, count(*) 
FROM pg_stat_activity 
GROUP BY state;

DynamoDB CloudWatch Metrikleri:

  • ConsumedReadCapacityUnits / ConsumedWriteCapacityUnits
  • ThrottledRequests (kritik!)
  • SuccessfulRequestLatency
  • SystemErrors

Not: DynamoDB fiyatlandırması Kasım 2024’te ~%50 düşürüldü, on-demand fiyatlandırmasını değişken workload’lar için daha maliyet-etkin hale getirdi.

MongoDB Anahtar Metrikleri:

  • Saniye başına işlemler (opcounters)
  • Working set boyutu vs mevcut memory
  • Lock yüzdesi
  • Replication lag

Yaygın Performans Sorunları#

N+1 Sorgu Problemi:

// KÖTÜ: N+1 sorguları
async function getUsersWithPosts() {
  const users = await db.query('SELECT * FROM users');
  
  for (const user of users) {
    user.posts = await db.query('SELECT * FROM posts WHERE user_id = ?', [user.id]);
  }
  
  return users;
}

// İYİ: JOIN ile tek sorgu
async function getUsersWithPosts() {
  return db.query(`
    SELECT u.*, p.id as post_id, p.title, p.content
    FROM users u
    LEFT JOIN posts p ON u.id = p.user_id
    ORDER BY u.id, p.created_at DESC
  `);
}

Connection Pool Tükenmesi:

// Connection pool sağlığını monitoring
setInterval(() => {
  console.log({
    totalConnections: pool.totalCount,
    idleConnections: pool.idleCount,
    waitingClients: pool.waitingCount
  });
  
  if (pool.waitingCount > 5) {
    console.warn('Connection pool baskı altında!');
  }
}, 30000);

Geleceğe Yönelik Veritabanı Planlaması#

Teknoloji Trendleri#

AI/ML için Vector Veritabanları: PostgreSQL için pgvector, Pinecone, Weaviate

  • Semantic search için embedding storage
  • RAG (Retrieval-Augmented Generation) uygulamaları
  • Görüntü ve doküman benzerlik araması

Multi-Model Veritabanları: FaunaDB, Azure Cosmos DB

  • Birden fazla veri modelini destekleyen tek veritabanı
  • Azaltılmış operasyonel karmaşıklık
  • Birleşik sorgu arayüzleri

Serverless-first mimariler de yükselişte: serverless MySQL için PlanetScale, serverless PostgreSQL için Neon ve serverless transactional işlemler için FaunaDB.

Büyüme Planlaması#

Kapasite Planlama Framework’ü:

// Veritabanı büyüme projeksiyon modeli
interface GrowthProjection {
  currentUsers: number;
  userGrowthRate: number; // aylık oran, kesir olarak; örn. %5 için 0.05
  avgDataPerUser: number; // KB cinsinden
  queryGrowthMultiplier: number; // kullanıcı başına sorgu; kullanıcıdan hızlı büyür
}

function projectDatabaseNeeds(projection: GrowthProjection, months: number) {
  const futureUsers = projection.currentUsers * Math.pow(1 + projection.userGrowthRate, months);
  const futureDataSize = futureUsers * projection.avgDataPerUser;
  const futureQPS = futureUsers * projection.queryGrowthMultiplier;
  
  return {
    estimatedUsers: Math.round(futureUsers),
    estimatedDataSizeGB: Math.round(futureDataSize / 1024 / 1024),
    estimatedQPS: Math.round(futureQPS),
    recommendedShards: Math.ceil(futureQPS / 10000) // Shard başına 10K QPS varsayımı
  };
}

Karar Framework’ü#

Yeni bir proje için veritabanı seçerken, bu soruları sırayla sorun:

1. Tutarlılık Gereksinimleri#

  • ACID transaction’lara ihtiyacınız var mı? → SQL veritabanları
  • Eventual consistency ile çalışabilir misiniz? → NoSQL seçenekleri açılır

2. Sorgu Karmaşıklığı#

  • Karmaşık analitik sorgular? → PostgreSQL, CockroachDB
  • Basit key-value lookup’ları? → Redis, DynamoDB
  • Full-text search gerekli mi? → Elasticsearch + birincil veritabanı

3. Ölçek ve Performans#

  • Mevcut ölçek: <100K kullanıcı → PostgreSQL + Redis
  • Büyüme trajektorisi: >1M kullanıcı → Sharding veya cloud-native seçenekleri düşünün
  • Gecikme gereksinimleri: <10ms → In-memory (Redis) veya optimize NoSQL

4. Takım ve Operasyonel Kısıtlar#

  • Takım uzmanlığı: Başlangıçta mevcut becerilere yakın kalın
  • Operasyonel bütçe: Managed service’ler vs. self-hosted
  • Compliance gereksinimleri: Veri residency, şifreleme, audit trail’leri

5. Gelecek Esnekliği#

  • Veri modelinin değişme ihtimali ne kadar? → Yüksek değişim oranı için document store’lar
  • Multi-region genişleme planlanıyor mu? → Distributed veritabanlarını erken düşünün
  • Entegrasyon gereksinimleri: Hangi diğer sistemlerin bağlanması gerekiyor?

Bu varsayılan ikili, bu cevaplardan biri değişikliği zorunlu kılana kadar geçerliliğini korur: tek bir primary’nin soğuramayacağı yazma hacmi, satırları bir bölgeye sabitleyen bir residency kuralı, diski dolduran bir zaman serisi ingest hızı veya offline çalışmak zorunda olan bir istemci. Bunların her birinin yukarıda özelleşmiş bir karşılığı var ve her biri işletilecek bir motor daha ekliyor. İkinci motoru, gerçekten bir gereksinim onu zorunlu kıldığında ekleyin.

Kaynaklar#

İlgili yazılar