Normalizasyon ve Denormalizasyon: Şema Tasarımı

İlişkisel veritabanlarında şema tasarımı, projenin ilk günlerinde alınan ve sonradan değiştirilmesi pahalı olan kararların başında gelir. Normalizasyo...

Normalizasyon ve Denormalizasyon: Şema Tasarımı

İlişkisel veritabanlarında şema tasarımı, projenin ilk günlerinde alınan ve sonradan değiştirilmesi pahalı olan kararların başında gelir. Normalizasyon ile denormalizasyon arasındaki tercih, çoğu zaman "doğru olan" ile "hızlı olan" arasında bir seçim gibi sunulur. Gerçekte durum bundan daha nüanslıdır: ikisi de belirli problemleri çözmek için tasarlanmış araçlardır ve hangisinin uygun olduğu, verinin nasıl yazıldığına ve nasıl okunduğuna bağlıdır.

Bu rehber, orta seviye geliştiricilerin bu kararı bilinçli verebilmesi için normalizasyonun ne sağladığını, denormalizasyonun hangi maliyetleri getirdiğini ve ikisinin nasıl birlikte kullanılabileceğini ele alır.

Önkoşullar

  • İlişkisel veritabanı temelleri (tablolar, birincil ve yabancı anahtarlar, temel SQL).
  • En az bir normal formun (1NF, 2NF, 3NF) ne anlama geldiğine dair genel fikir.
  • Uygulama tarafında veri erişim katmanının (ORM veya ham SQL) nasıl çalıştığına dair deneyim.

Sürüm kapsamı: Buradaki kavramlar belirli bir veritabanı motoruna veya sürüme bağlı değildir. Örnekler standart SQL'e yakın tutulmuştur; üretim ortamına uyarlarken kullandığınız motorun dokümantasyonunu esas alın.

Normalizasyon Ne Çözer

Normalizasyon, veriyi tekrarı en aza indirecek ve güncelleme tutarsızlıklarını önleyecek şekilde parçalara ayırmaktır. Amaç estetik değil, bütünlüktür.

Klasik bir örnekle düşünün: siparişleri ve müşteri bilgilerini tek tabloda tutmak.

-- Denormalize edilmiş tek tablo (tasarım kokusu)
CREATE TABLE orders_flat (
    order_id      BIGINT PRIMARY KEY,
    customer_name VARCHAR(200),
    customer_city VARCHAR(100),
    product_name  VARCHAR(200),
    quantity      INT
);

Bu tasarımda aynı müşteri birden fazla sipariş verdiğinde customer_name ve customer_city her satırda tekrarlanır. Müşteri şehir değiştirdiğinde tüm satırları güncellemek gerekir; bir satır atlanırsa aynı müşteri için iki farklı şehir bilgisi oluşur. Bu, güncelleme anomalisi olarak bilinen ve normalizasyonun doğrudan hedef aldığı problemdir.

Normalize edilmiş hali veriyi sorumluluklarına ayırır:

CREATE TABLE customers (
    customer_id BIGINT PRIMARY KEY,
    name        VARCHAR(200) NOT NULL,
    city        VARCHAR(100)
);

CREATE TABLE products (
    product_id BIGINT PRIMARY KEY,
    name       VARCHAR(200) NOT NULL
);

CREATE TABLE orders (
    order_id    BIGINT PRIMARY KEY,
    customer_id BIGINT NOT NULL REFERENCES customers(customer_id)
);

CREATE TABLE order_items (
    order_id   BIGINT NOT NULL REFERENCES orders(order_id),
    product_id BIGINT NOT NULL REFERENCES products(product_id),
    quantity   INT NOT NULL,
    PRIMARY KEY (order_id, product_id)
);

Burada müşteri şehri tek bir yerde tutulur; güncelleme tek satırı etkiler. Yazma işlemleri tutarlı ve öngörülebilir hale gelir.

Denormalizasyon Ne Zaman Gündeme Gelir

Normalize şema, veriyi yazmak için idealdir; ancak okuma sırasında sık sık birden fazla tabloyu birleştirmeyi (JOIN) gerektirir. Okuma yükü arttığında veya sorgular çok sayıda tabloyu birleştirdiğinde, bu maliyet ölçülebilir hale gelebilir.

Denormalizasyon, bilinçli olarak veri tekrarı eklemek veya hazır hesaplanmış değerleri saklamak anlamına gelir. Amaç, okuma sırasında yapılacak işi yazma sırasına taşımaktır.

Tipik denormalizasyon biçimleri:

  • Türetilmiş değerleri saklamak: Örneğin order_items satırlarının toplamını orders.total_amount alanında tutmak. Okuma sırasında SUM hesaplamak yerine tek alan okunur.
  • Sık kullanılan alanları kopyalamak: Sipariş listesinde müşteri adını göstermek için her sorguda customers tablosuna JOIN yapmak yerine, sipariş satırında customer_name_snapshot tutmak.
  • Önceden birleştirilmiş görünümler veya özet tablolar: Raporlama için ayrı bir tablo veya materialized view.

Örnek olarak toplam tutarı sipariş tablosunda tutalım:

ALTER TABLE orders ADD COLUMN total_amount NUMERIC(12, 2) NOT NULL DEFAULT 0;

Bu alanı güncel tutmak artık uygulamanın sorumluluğundadır. Bir order_items satırı eklendiğinde, güncellendiğinde veya silindiğinde total_amount yeniden hesaplanmalıdır. Bunu tek bir transaction içinde yapmak, tutarsızlığı önlemenin temel yoludur:

BEGIN;

INSERT INTO order_items (order_id, product_id, quantity)
VALUES (42, 7, 3);

UPDATE orders o
SET total_amount = (
    SELECT COALESCE(SUM(oi.quantity * p.unit_price), 0)
    FROM order_items oi
    JOIN products p ON p.product_id = oi.product_id
    WHERE oi.order_id = o.order_id
)
WHERE o.order_id = 42;

COMMIT;

Buradaki kritik nokta, iki ifadenin aynı transaction içinde olmasıdır. Aksi halde araya giren bir hata, total_amount ile gerçek satır toplamının ayrışmasına yol açar.

Karar Verirken Sorulacak Sorular

Normalizasyon ve denormalizasyon arasında seçim yaparken şu sorular yol gösterir:

  1. Yazma mı okuma mı baskın? Yazma yoğun sistemlerde tekrar eden veriyi senkron tutmak maliyetlidir; okuma yoğun sistemlerde JOIN maliyeti öne çıkar.
  2. Veri ne sıklıkla değişiyor? Nadiren değişen referans verisi (şehir, kategori adı) kopyalanmaya daha uygundur. Sürekli değişen bir değeri kopyalamak, senkronizasyon yükünü artırır.
  3. Tutarsızlık tolere edilebilir mi? Finansal toplamlar için tolerans yoktur; bir ürünün görüntülenme sayısı için küçük sapmalar kabul edilebilir.
  4. Ölçüm yapıldı mı? Denormalizasyon, ölçülmüş bir darboğaza cevap olmalıdır, öngörüye değil.

Yaygın Hatalar

  • Erken denormalizasyon: Henüz bir performans problemi ölçülmeden veri tekrarı eklemek, ileride bakımı zor bir şema bırakır. Önce normalize tasarımla başlayın, darboğazı ölçün, sonra hedefli denormalize edin.
  • Senkronizasyonu uygulamaya bırakmak: Türetilmiş alanı güncelleyen tek bir kod yolu olmalı. Farklı yerlerden total_amount güncellenirse tutarsızlık kaçınılmazdır. Mümkünse tetikleyici (trigger) veya tek bir servis metodu üzerinden yönetin.
  • Transaction sınırlarını göz ardı etmek: Kaynak veri ile kopyası arasındaki güncelleme atomik değilse, kısmi hata durumunda veri ayrışır.
  • Kopyalanan veriyi "gerçek" sanmak: Kopya alanlar türetilmiş veridir; tek doğruluk kaynağı asıl tablodur. Bu ayrımı dokümante edin.

Güvenlik ve Bütünlük Etkileri

Denormalizasyon, veri bütünlüğü risklerini artırır. Kopyalanan alanlar, asıl kaynaktaki bir silme veya güncelleme işleminden haberdar olmayabilir. Örneğin bir müşteri silindiğinde, sipariş satırlarındaki customer_name_snapshot hâlâ eski ismi taşır. Bu bazen istenen davranıştır (tarihsel kayıt), bazen de gizlilik açısından problemdir: silinmesi gereken kişisel veri, kopya olarak başka tablolarda kalmış olabilir.

Bu nedenle veri saklama ve silme politikalarınızı denormalize alanları da kapsayacak şekilde tasarlayın. "Silme" işleminin hangi tablolardaki kopyaları etkilemesi gerektiğini baştan belirleyin.

Ayrıca normalizasyon, yabancı anahtar kısıtları sayesinde geçersiz referansları veritabanı seviyesinde engeller. Denormalize tasarımlarda bu tür kısıtlar zayıflar; tutarlılığı uygulama katmanına taşımış olursunuz ve bu katmanın doğruluğundan siz sorumlu olursunuz.

İkisini Birlikte Kullanmak

Bu bir ya/ya da tercihi değildir. Yaygın ve sağlam yaklaşım, çekirdek veriyi normalize tutmak, okuma performansı kritik olan belirli sorgular için hedefli denormalizasyon uygulamaktır.

Örneğin:

  • İşlemsel tablolar (customers, orders, order_items) normalize kalır.
  • Sık çalışan bir rapor sorgusu için ayrı bir özet tablo veya materialized view tutulur ve periyodik olarak yenilenir.
  • Toplam tutar gibi sık okunan türetilmiş değerler, transaction içinde güncellenen alanlar olarak saklanır.

Bu yaklaşımda normalizasyon doğruluk kaynağı, denormalizasyon ise performans katmanı olarak konumlanır.

Sonuç

Normalizasyon, veri bütünlüğünü ve yazma tutarlılığını önceliklendirir; denormalizasyon, okuma performansını önceliklendirir ve karşılığında senkronizasyon yükü ile bütünlük riski getirir. Doğru başlangıç noktası genellikle normalize bir tasarımdır. Denormalizasyon, ölçülmüş bir ihtiyaca cevap olarak, sınırları net çizilmiş ve senkronizasyonu tek bir yerde yönetilen biçimde eklenmelidir. Hangi yolu seçerseniz seçin, türetilmiş verinin tek doğruluk kaynağını ve silme politikalarını açıkça belirleyin.

İLGİLİ İÇERİKLER

Bunları da Okumak İsteyebilirsiniz