İçeriğe Atla
Mustafa Erbay
Yaşam erp-altyapi-mimarisi · 15 dk okuma · görüntülenme Read in English

Ürün Ağacı Denormalizasyonu ve Teknik Borcun Anatomisi

Bir üretim ERP'sinde karşılaştığım ürün ağacı sorunlarını, denormalizasyonun nedenlerini ve teknik borcun nasıl biriktiğini kendi deneyimimle anlatıyorum.

100%

Geçenlerde bir üretim ERP’sinde, karmaşık ürün ağaçlarıyla ilgili bir raporun saatlerce tamamlanamadığını fark ettim. Raporun gecikmesi, sevkiyat planlamayı doğrudan etkiliyor, bu da şirketin günlük operasyonlarını ciddi şekilde aksatıyordu. Sorunun kökenine indiğimde, veritabanı tasarımında yıllar önce yapılmış “performans” odaklı bir denormalizasyon kararının, zamanla nasıl devasa bir teknik borca dönüştüğünü gördüm.

Bu durum, hayatımda defalarca karşılaştığım bir senaryonun özeti aslında. Bir sorunu hızlıca çözmek için atılan adımlar, uzun vadede daha büyük sorunlara yol açabiliyor. Üretim süreçlerinde ürün ağaçları (Bill of Materials – BOM) kritik bir rol oynar. Bir ürünün hangi parçalardan oluştuğunu, bu parçaların miktarlarını ve üretim sırasını detaylandıran bu yapılar, doğru yönetilmediğinde operasyonel felaketlere yol açabiliyor. Benim buradaki tecrübem, bazen bir geliştirici olarak attığımız adımların, şirket yaşamının her alanını nasıl etkilediğini bir kez daha gösterdi.

Ürün Ağacı Karmaşası ve Geciken Raporlar

Üretim firmalarında ürün ağacı, yani BOM, her şeyin kalbidir. Bir ürünün ana malzemelerden başlayıp nihai ürüne dönüşene kadar geçtiği tüm ara basamakları, kullanılan her bir bileşeni ve miktarını gösterir. Bu yapılar genellikle çok katmanlı, yani bir parçanın kendisi de başka parçalardan oluşabilir. Bu durum, veritabanında recursive bir ilişkiyi zorunlu kılar.

Bu karmaşık yapıları yönetmek, özellikle raporlama ve maliyet hesaplama gibi işlemlerde ciddi performans sorunlarına yol açabilir. Benim karşılaştığım vakada, haftalık sevkiyat raporu, ürün ağacının derinliklerine inerek her bir nihai ürün için gerekli tüm alt bileşenleri ve bunların stok durumlarını hesaplıyordu. Başlangıçta görece az sayıda ürün tipi varken, şirket büyüdükçe bu sayı katlandı ve rapor süresi de katlanarak arttı. İlk başta birkaç dakikada biten rapor, zamanla saatler sürer hale geldi.

Bu raporun gecikmesi basit bir yazılımsal bug değildi. Haftalık üretim ve sevkiyat planlarının aksamasına neden oluyor, bu da müşterilere verilen sözlerin tutulamaması riskini ortaya çıkarıyordu. Sabah yapılması gereken sevkiyat planlama toplantısı, raporun ancak öğlene doğru bitmesi yüzünden öğleden sonraya kalıyordu. Bu durum, sadece IT ekibinin değil, tüm üretim ve lojistik departmanlarının stresini artırıyordu.

Veritabanı Tasarımında Denormalizasyonun Cazibesi ve Tuzakları

Denormalizasyon, veritabanı tasarımında genellikle performans sorunlarını aşmak için başvurulan bir yöntemdir. Normalizasyon, veri tekrarlarını azaltarak veri bütünlüğünü sağlamayı hedeflerken, denormalizasyon, sorgu performansını artırmak için veri tekrarlarına izin verir. Özellikle çoklu JOIN işlemlerinden kaçınmak veya sıkça kullanılan hesaplanmış değerleri önceden saklamak amacıyla denormalizasyona gidebiliriz. Ben de kariyerimin ilk yıllarında bu cazibeye kapılmıştım. Birçok projede, özellikle raporlama ihtiyaçları için, bu tür çözümlerin ne kadar “hızlı” olduğunu görmüştüm.

Ürün ağacı bağlamında, denormalizasyon genellikle bir ürünün tüm alt bileşenlerinin bir listede (örneğin, JSONB bir kolonda) saklanması ya da her bir bileşenin toplam maliyetinin ana ürün tablosuna yazılması şeklinde karşımıza çıkar. Örneğin, bir üretim ERP’sinde, ana ürün tablosundaki bir material_cost kolonu, tüm alt bileşenlerin maliyetlerinin toplamını tutuyordu. Bu sayede, “Toplam Maliyet” raporu için derinlemesine JOIN’ler yapmaya gerek kalmıyordu. İlk başta bu yaklaşım, raporların saniyeler içinde gelmesini sağlıyordu, bu da kulağa harika geliyordu.

-- Normalizasyon Ornegi: Her parca ve iliskisi ayri tabloda
CREATE TABLE products (
    product_id SERIAL PRIMARY KEY,
    product_name VARCHAR(255)
);

CREATE TABLE bill_of_materials (
    bom_id SERIAL PRIMARY KEY,
    parent_product_id INT REFERENCES products(product_id),
    component_product_id INT REFERENCES products(product_id),
    quantity NUMERIC(10, 4),
    UNIQUE (parent_product_id, component_product_id)
);

-- Denormalizasyon Ornegi: Ana urun tablosunda toplam maliyeti tutma
-- Ancak bu maliyetin nasil guncellenecegi kritik
ALTER TABLE products
ADD COLUMN total_material_cost NUMERIC(18, 4);

Ancak bu basit gibi görünen çözümün ciddi tuzakları var. Bir bileşenin maliyeti değiştiğinde, bu bileşeni içeren tüm ana ürünlerin total_material_cost kolonunun da güncellenmesi gerekir. Eğer bu güncelleme manuel veya basit bir TRIGGER ile yapılmazsa, veri tutarsızlığı kaçınılmaz olur. İşte benim karşılaştığım problem de buydu: Sistem büyüdükçe, bu güncellemelerin domino etkisi, performans darboğazına neden oluyordu. Maliyet değişiklikleri, özellikle ay sonlarında, sistemin neredeyse kilitlenmesine yol açıyordu. Bu, sadece bir maliyet kolonunun denormalize edilmesinin bile ne kadar büyük bir zincirleme reaksiyona yol açabileceğini gösteren acı bir deneyimdi.

Ürün Ağacı Yapılarında Teknik Borç Nasıl Birikir?

Teknik borç, yazılım geliştirme dünyasında kaçınılmazdır. Tıpkı finansal borç gibi, kısa vadeli kazançlar için alınan kararların uzun vadede faiziyle ödenmesi gereken maliyetidir. Ürün ağacı gibi karmaşık ve dinamik yapılarda bu borç, genellikle farkında olmadan birikir. Benim deneyimimde, bu borcun birikmesinin birkaç ana nedeni vardı:

  1. Versiyonlama ve Geçerlilik Tarihleri: Ürün ağaçları zamanla değişir. Yeni parçalar eklenir, mevcut parçaların miktarları değişir veya tamamen kaldırılır. Bu değişiklikleri izlemek için versiyonlama ve geçerlilik tarihleri (validity dates) kullanılır. Ancak denormalize edilmiş bir yapıda, her versiyon değişikliğinde tüm denormalize edilmiş verinin de doğru şekilde güncellenmesi gerekir. Eski bir versiyonun maliyeti mi güncellendi, yoksa yeni bir versiyonun mu? Bu sorular, karmaşıklığı artırır.

  2. Hesaplanmış Değerlerin Önceden Saklanması: Bir önceki bölümde bahsettiğim total_material_cost gibi. Başlangıçta mantıklı görünen bu yaklaşım, iş kuralları değiştikçe veya yeni hesaplama metrikleri eklendikçe bir kabusa dönüşür. Örneğin, maliyete “işçilik maliyeti” veya “üretim firesi” gibi yeni kalemler eklendiğinde, mevcut total_material_cost kolonunun tanımı değişir ve tüm geçmiş verinin yeniden hesaplanması gerekir. Bu, milyonlarca kayıt için günler süren batch işlemler anlamına gelebilir.

  3. Anlık Değişimlerin Domino Etkisi: Bir alt bileşenin maliyetinin değişmesi, onu kullanan tüm ara mamulleri ve nihayetinde nihai ürünleri etkiler. Normal bir veritabanında bu, bir dizi SELECT ve UPDATE ile çözülebilirken, denormalize edilmiş bir yapıda bu güncellemeleri manuel olarak veya karmaşık TRIGGER’larla yönetmek gerekir. Bu TRIGGER’lar, özellikle PostgreSQL’de, her INSERT, UPDATE veya DELETE işleminde tetiklenir ve ek yük bindirir.

-- Denormalize edilmis maliyeti guncellemek icin basit bir TRIGGER ornegi
-- Ancak bu, recursive agac yapilarinda cok daha karmasik hale gelir.
CREATE OR REPLACE FUNCTION update_product_total_cost()
RETURNS TRIGGER AS $$
BEGIN
    -- Bu ornek basittir, gercek bir BOM agacinda cok daha derin bir hesaplama gerekir
    UPDATE products
    SET total_material_cost = (
        SELECT SUM(p.price * bom.quantity)
        FROM bill_of_materials bom
        JOIN products p ON bom.component_product_id = p.product_id
        WHERE bom.parent_product_id = NEW.product_id
    )
    WHERE product_id = NEW.product_id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_update_product_cost
AFTER INSERT OR UPDATE ON bill_of_materials
FOR EACH ROW
EXECUTE FUNCTION update_product_total_cost();

Bu tür TRIGGER’lar, başlangıçta küçük bir performans sorunu gibi görünse de, sistemdeki veri hacmi arttıkça ciddi bir darboğaz haline gelebilir. Benim yaşadığım vakada, bill_of_materials tablosundaki tek bir UPDATE işlemi, çok sayıda products tablosu kaydının yeniden hesaplanmasına yol açıyor, bu da fark edilir kilitlenmelere neden oluyordu. Özellikle yoğun yazma işlemlerinin olduğu zamanlarda, bu durum VACUUM işlemlerini de olumsuz etkiliyor ve WAL bloat gibi sorunları tetikleyebiliyordu.

Ürün Ağacını Modellemenin Üç Yolu: Yan Yana

Çözüme geçmeden önce, masaya hangi seçeneklerin geldiğini netleştirmek lazım. Bir BOM hiyerarşisini ilişkisel veritabanında denormalize etmenin pratikte üç ana yolu var ve bunları yıllar içinde sahada birbiriyle yarıştırdım. Hiçbiri “doğru” cevap değil; her biri farklı bir okuma/yazma dengesi satıyor.

1. Arama Yolları (Path Enumeration). Her düğüm için kök üründen kendine kadar olan yolu bir string olarak saklarsın: /A/B/C. “Bir ürünün tüm atalarını veya torunlarını bul” sorgusu, özyinelemeli CTE yerine basit bir LIKE '/A/%' eşleşmesine iner ve indeks sayesinde milisaniyeler içinde döner. Bedeli: ağaç her değiştiğinde etkilenen tüm yolların güncellenmesi gerekir, string uzadıkça tablo şişer.

-- Path Enumeration: her dugum icin tam yol
ALTER TABLE products ADD COLUMN product_path TEXT;
CREATE INDEX idx_product_path ON products (product_path text_pattern_ops);
-- "/A/" ile baslayan tum alt bilesenler:
-- SELECT * FROM products WHERE product_path LIKE '/A/%';

2. Seviye + parent_id (Adjacency List). Her ürün için sadece level ve doğrudan parent_id tutulur. “Bir üst öğenin doğrudan çocukları” (WHERE parent_id = X) veya “belirli seviyedeki tüm öğeler” (WHERE level = N) sorguları çok hızlıdır. Veri tekrarı Path Enumeration’a göre çok daha az, güncelleme çok daha ucuzdur; sadece etkilenen alt ağacın seviyeleri yenilenir. Karşılığında, tüm ağacı dolaşmak için hâlâ özyinelemeli sorguya ihtiyaç duyabilirsin — ama level/parent_id ile çok sadeleşmiş haliyle.

-- Adjacency list + denormalize seviye
ALTER TABLE products ADD COLUMN parent_id INT REFERENCES products(product_id);
ALTER TABLE products ADD COLUMN level INT DEFAULT 0;
CREATE INDEX idx_products_parent ON products (parent_id);
CREATE INDEX idx_products_level ON products (level);

3. Çıkarılmış BOM (Exploded BOM). En agresif yöntem: her ana ürün için tüm alt bileşenlerini önceden düzleştirip ayrı bir tabloya yazarsın. “Bir ürünün bütün bileşenlerini listele” sorgusu tek bir SELECT’e iner — ne özyineleme, ne join. Bedeli en yüksek olanı: bir alt bileşen onlarca ana üründe geçiyorsa, o kadar satır tekrarlanır; tablo en çok bu modelde şişer ve güncelleme en pahalı burada olur.

-- Exploded BOM: her ana urun x her alt bilesen
CREATE TABLE exploded_bom (
    main_product_id  INT NOT NULL,
    component_id     INT NOT NULL,
    quantity         NUMERIC(18, 4) NOT NULL,
    level            INT NOT NULL
);
CREATE INDEX idx_exploded_main ON exploded_bom (main_product_id);
CREATE INDEX idx_exploded_component ON exploded_bom (component_id);

Hangi Yöntem Ne Zaman?

Doğru seçim, ağacın ne sıklıkla değiştiğine ve hangi sorgunun baskın olduğuna bağlı. Sahadaki kararım kabaca şu matrise oturdu:

Yöntem Ne zaman Avantaj Bedel
Path Enumeration Ağaç görece statik, “tüm ata/torun” sorguları yoğun Sorgu çok hızlı, indeks dostu Güncelleme maliyetli, string şişer
Seviye + parent_id Orta derecede değişim, “doğrudan çocuk” / “seviye” sorguları ağır Güncelleme yönetilebilir, tekrar az Derin dolaşım için yine özyineleme
Exploded BOM Ağaç çok nadir değişir (yılda birkaç kez), “tüm bileşenler” kritik Sorgu inanılmaz hızlı Tekrar ve boyut en yüksek, yazma en pahalı

Denormalizasyonun “Production” Ortamında Bedeli: Gerçek Senaryolar

“Production” ortamında, kağıt üzerinde mantıklı görünen kararların bedelini çok acı ödeyebiliriz. Benim bir üretim firmasının ERP’sinde yaşadığım deneyim, bunun canlı bir örneğiydi. Şirket, ayda yüksek hacimde nihai ürün sevk ediyordu ve bu ürünler çok katmanlı, derin ürün ağaçlarına sahipti. bill_of_materials tablosundaki kayıt sayısı milyonlarla ifade ediliyordu.

Sorun, her ayın ilk iş günü sabah erkenden başlayan ve son günün üretim verilerini toplayan “Günlük Üretim Özeti” raporuyla patlak verdi. Raporun normalde birkaç dakikada bitmesi beklenirken, bir anda onlarca dakikaya, ardından saatlere çıkmaya başladı. Sonunda, raporun çalışması sistemin CPU’sunu neredeyse tümüyle doyuruyor ve diğer kritik operasyonları yavaşlatıyordu. Sistem yöneticisi olarak journald loglarına baktığımda, raporun çalıştığı anlarda PostgreSQL process’lerinin CPU limit’lerine takıldığını ve hatta bazı query timeout’ların oluştuğunu gördüm.

pg_stat_activity çıktılarında, raporu çalıştıran sorgunun devasa boyutlarda geçici disk alanı kullandığını gördüm. EXPLAIN ANALYZE sonuçları, sorgunun bill_of_materials tablosunda derinlemesine recursive CTE (Common Table Expression) kullandığını ve her bir alt bileşen için products tablosundan maliyet çekip topladığını gösteriyordu. En kötüsü de, bu maliyetlerin zaten products tablosunda denormalize edilmiş bir şekilde durmasıydı, ama rapor, güncel olmayabilecek bu denormalize edilmiş veriye güvenmek yerine her seferinde yeniden hesaplıyordu. Bu, tam bir paradoks ve çifte maliyet idi.

-- Raporun icinde yer alan ve performansi dusuren ornek bir CTE yapisi
WITH RECURSIVE bom_tree AS (
    SELECT
        b.parent_product_id,
        b.component_product_id,
        b.quantity,
        p.price AS component_price,
        1 AS level
    FROM bill_of_materials b
    JOIN products p ON b.component_product_id = p.product_id
    WHERE b.parent_product_id = <Nihai_Urun_ID>

    UNION ALL

    SELECT
        b.parent_product_id,
        b.component_product_id,
        b.quantity,
        p.price AS component_price,
        bt.level + 1
    FROM bill_of_materials b
    JOIN bom_tree bt ON b.parent_product_id = bt.component_product_id
    JOIN products p ON b.component_product_id = p.product_id
)
SELECT
    parent_product_id,
    SUM(component_price * quantity) AS total_calculated_cost
FROM bom_tree
GROUP BY parent_product_id;

Bu karmaşa, sadece raporlama tarafını değil, aynı zamanda veri girişini de etkiliyordu. Yeni bir ürün ağacı tanımlandığında veya mevcut bir bileşenin miktarı değiştiğinde, tetikleyiciler yüzünden işlem süreleri uzuyordu. Operatörler, ekranın donduğunu zannedip tekrar tekrar kaydet tuşuna basıyor, bu da daha fazla kilitlenmeye ve tutarsız veri girişlerine yol açıyordu. Bir öğleden sonra üretim saatlerce durma noktasına gelmişti, çünkü ERP sistemine yeni üretim siparişleri girilemiyordu. Bu durum, ciddi bir üretim kaybına yol açtı. Kurumsal yazılım mimarisinde performans darboğazları üzerine daha önce yaşadığım bir tecrübeye benziyordu, ancak bu sefer sorun denormalizasyonun kendisinden kaynaklanıyordu.

Çözüm Yolları ve Teknik Borcu Yönetme Stratejileri

Bu türden bir teknik borcu tek seferde ve kökten çözmek genellikle mümkün değildir. Mevcut sistemin canlı operasyonlarını aksatmadan, adım adım ilerlemek gerekir. Benim uyguladığım strateji, birkaç aşamadan oluştu:

  1. Mevcut Denormalize Alanların Tespiti ve Temizliği: İlk olarak, products tablosundaki total_material_cost gibi denormalize edilmiş kolonları belirledim. Bu kolonların güncel olup olmadığını kontrol eden bir batch job yazdım. Bu iş, genellikle geceleri sistemin daha az yoğun olduğu saatlerde çalışıyordu. Eğer bir tutarsızlık tespit edilirse, doğru maliyetler yeniden hesaplanıp güncelleniyordu. Bu, geçici bir çözüm olsa da, raporların en azından doğru veriyle çalışmasını sağlıyordu.

  2. Materialized Views Kullanımı: Recursive ürün ağacı sorguları, her çalıştığında aynı hesaplamaları tekrarlıyordu. Bunu engellemek için, belirli ana ürünler için önceden hesaplanmış ürün ağacı maliyetlerini MATERIALIZED VIEW’lara kaydetmeye karar verdim. Bu view’lar, REFRESH MATERIALIZED VIEW komutuyla belirli aralıklarla (örneğin, her saat başı veya üretim maliyetleri değiştiğinde) güncelleniyordu. Bu, raporların anlık sorgulardan ziyade önceden hesaplanmış, güncel verilere erişmesini sağladı.

-- Ürün ağacının maliyetini tutan bir Materialized View
CREATE MATERIALIZED VIEW product_bom_total_costs AS
WITH RECURSIVE bom_calc AS (
    SELECT
        b.parent_product_id,
        b.component_product_id,
        b.quantity,
        p.price AS component_price
    FROM bill_of_materials b
    JOIN products p ON b.component_product_id = p.product_id

    UNION ALL

    SELECT
        bc.parent_product_id,
        b.component_product_id,
        b.quantity * bc.quantity, -- Miktari carparak dogru hesaplama
        p.price AS component_price
    FROM bill_of_materials b
    JOIN bom_calc bc ON b.parent_product_id = bc.component_product_id
    JOIN products p ON b.component_product_id = p.product_id
)
SELECT
    parent_product_id,
    SUM(component_price * quantity) AS total_calculated_cost,
    NOW() AS last_calculated_at
FROM bom_calc
GROUP BY parent_product_id;

-- Materialized View'i guncellemek
REFRESH MATERIALIZED VIEW product_bom_total_costs;

Bu MATERIALIZED VIEW’lar, raporlama performansını belirgin şekilde artırdı. Saatler süren raporlar, dakikalar mertebesine indi. Ancak bu, verilerin “eventually consistent” olacağı anlamına geliyordu; yani, bir maliyet değişikliği yapıldığında raporun anında güncellenmiş veriyi göstermesi garanti değildi. Bu durumu iş birimleriyle net bir şekilde iletişim kurdum. Onlar için kısa bir gecikmeyle güncel veri almak, hiç alamamaktan çok daha iyiydi.

  1. CQRS (Command Query Responsibility Segregation) ve Event Sourcing Yaklaşımı: Uzun vadede, bu tür karmaşık iş alanları için CQRS ve Event Sourcing mimarilerinin daha uygun olduğunu düşündüm. Yazma işlemleri (Command) ve okuma işlemleri (Query) ayrı modellerde ele alınır. Bir maliyet değişikliği gibi her önemli olay (Event), bir event stream’e kaydedilir. Okuma modelleri (örneğin, ürün maliyet raporu için denormalize edilmiş bir tablo), bu olay akışından beslenerek güncellenir. Bu, veri bütünlüğünü sağlarken, okuma performansını da maksimize eder. Bu mimariyi kendi yan ürünümün finansal hesaplayıcılarında denemiştim ve çok başarılı sonuçlar almıştım.

Neye Geçtik: LTREE Tabanlı Hibrit Model

MATERIALIZED VIEW’lar yangını söndürdü ama yapının kendisi hâlâ kırılgandı. Kalıcı çözüm için modeli değiştirmem gerekti. Önce kök nedeni kesin olarak görmek istedim: üretim emri oluşturma neden bu kadar yavaştı? EXPLAIN planı her şeyi anlattı — sistem, artık kullanılmadığını sandığımız eski bir denormalizasyon yolundan, her bileşen için tabloyu kendi üzerine join’liyordu.

-- cikti ortama gore degisir; onemli olan yapi:
                                      QUERY PLAN
--------------------------------------------------------------------------------------
 Seq Scan on product_components pc1
   Filter: (main_product_id = ...)
   ->  Join
         Join Filter: (pc2.main_product_id = pc1.component_product_id)
         ->  Seq Scan on product_components pc2
               Filter: (main_product_id = ...)
               ->  Join ... (boyle devam ediyor...)

Basit bir ürün için bile bu sorgu defalarca kendi üzerine join yapıyordu; derin hiyerarşilerde maliyet katlanarak büyüyordu. Karmaşık ürünlerde sürenin neden dakikalara çıktığını bu plan tek başına açıklıyordu — her Seq Scan tam tablo taramasıydı, indeks devre dışıydı.

Çözüm olarak product_components tablosunu tamamen atmak yerine, onunla bir hibrit kurduk. Ana ürün ve doğrudan bağlı bileşenler için düz tabloyu koruduk; daha derin dalları, her ürünün kökten kendine kadar olan yolunu tutan bir materialized_path alanı üzerinden çözdük. PostgreSQL bunun için biçilmiş kaftan: LTREE veri tipi, materialized path’i hem saklamak hem de sorgulamak için yerleşik operatörlerle gelir.

-- Hedef sema: adjacency + LTREE materialized path hibridi
CREATE TABLE products (
    id                SERIAL PRIMARY KEY,
    name              VARCHAR(255) NOT NULL,
    parent_id         INT NULL REFERENCES products(id),  -- adjacency list
    materialized_path LTREE                               -- PostgreSQL'e ozel
);

-- LTREE sorgularini hizlandiran GiST indeksi sart
CREATE INDEX idx_products_path_gist ON products USING GIST (materialized_path);

-- Belirli bir ana urunun tum alt dali, tek sorguda, ozyineleme yok:
SELECT * FROM products WHERE materialized_path <@ '1.15'::ltree;

<@ operatörü “şu yolun alt ağacındaki her şey” demektir; subtree() ve nlevel() gibi fonksiyonlarla belirli derinlikleri de kesip alabilirsin. GiST indeksiyle birlikte, bir dalı çekmek artık tam tablo taraması değil, indeks aramasıydı. Önce o hatalı self-join sorgusunu tamamen kaldırdık, ardından üretim emri akışını bu LTREE sorgularına geçirdik. Aynı emrin oluşturulma süresi dakikalar mertebesinden saniyeler mertebesine indi. İşin güzel tarafı, tam denormalizasyonun aksine bu model hiyerarşi değiştiğinde tüm düz tabloyu yeniden yazmayı gerektirmiyordu — sadece etkilenen dalın path’i güncelleniyordu.

”Eventually Consistent” Yaklaşım ve İş Süreçlerine Entegrasyon

Eventually consistent olmak, anlık tutarlılık gerektirmeyen durumlarda harika bir çözümdür. Ürün ağacı maliyetleri gibi genellikle anlık olarak değişmeyen veya anlık değişimin kritik olmadığı durumlarda bu yaklaşım işe yarar. Önemli olan, bu durumu iş birimleriyle net bir şekilde konuşmaktır. “Raporunuz şu anki en güncel veriyi değil, son 1 saat önceki veriyi gösteriyor, bu sizin için kabul edilebilir mi?” Bu soruya aldığım “evet” yanıtı, işin teknik kısmını çok daha kolaylaştırdı.

Bu tür bir geçişi yönetirken feature flag ve dark launch tekniklerini yoğun olarak kullandım. Yeni MATERIALIZED VIEW’ları arka planda çalıştırırken, mevcut raporlama sistemi hala eski yavaş sorgularla devam ediyordu. Yeni raporlama altyapısını bir feature flag arkasına gizledim. Önce küçük bir grup kullanıcıyla (örneğin, finans departmanından birkaç kişi) yeni raporları test ettik. Onay alındıktan sonra, feature flag’i tüm kullanıcılar için açarak yeni sisteme geçiş yaptık. Bu, riskleri minimize etmeme ve geçişi sorunsuz hale getirmeme yardımcı oldu.

Sistemde vacuum monitoring ve replication lag takibi gibi observability araçlarını kullanarak veri bütünlüğünü ve performansını sürekli izledim. PostgreSQL’deki pg_stat_statements ile en yavaş sorguları tespit edip, bunların MATERIALIZED VIEW’ları doğru kullanıp kullanmadığını kontrol ettim. Ayrıca, denormalize edilmiş alanların güncelliğini garantilemek için bir CDC (Change Data Capture) mekanizması kurmayı da düşündüm, ancak mevcut sistemin karmaşıklığı nedeniyle bu daha uzun vadeli bir hedef olarak kaldı.

Öğrenilen Dersler ve Gelecek İçin Tavsiyelerim

Bu Ürün Ağacı denormalizasyonu macerası, bana teknik borcun sadece kodda değil, aynı zamanda veritabanı tasarımında da nasıl derin kökler saldığını bir kez daha gösterdi. İlk başta “hızlı bir çözüm” gibi görünen denormalizasyon, zamanla katlanarak artan bir maliyet ve operasyonel sıkıntıya dönüştü. Burada çıkardığım birkaç temel ders var:

  1. İş Alanını Derinlemesine Anlayın: Herhangi bir veritabanı tasarımı yapmadan önce, iş alanının gereksinimlerini, veri akışını ve gelecekteki olası değişimleri çok iyi anlamak gerekir. Ürün ağacı gibi karmaşık yapılar, yüzeysel yaklaşımları affetmez. Hangi verinin anlık, hangisinin “eventually consistent” olabileceği, iş birimleriyle birlikte belirlenmelidir.

  2. Denormalizasyon Kararını İki Kere Düşünün: Denormalizasyon, performans için güçlü bir araç olabilir, ancak bir trade-off ile gelir: veri tutarlılığı ve bakım maliyeti. Bu kararı verirken, sadece anlık sorgu performansını değil, veri güncelliği, güncelleme maliyeti ve gelecekteki değişikliklerin etkisini de değerlendirmek şarttır. Genellikle, iyi indekslenmiş ve optimize edilmiş normalleştirilmiş bir yapı, çoğu zaman yeterli performansı sağlayabilir.

  3. Teknik Borç Kaçınılmazdır, Ama Yönetilebilir: Teknik borç, tamamen ortadan kaldırılamaz. Her projede, zaman baskısı veya bilgi eksikliği nedeniyle bazı kısa yollar seçilebilir. Önemli olan, bu borcun farkında olmak, onu düzenli olarak izlemek ve stratejik olarak azaltma planları yapmaktır. Tıpkı finansal borç gibi, teknik borcu da ödemeye devam etmezseniz, faiziyle birlikte sizi batırabilir.

Bu deneyim, bir teknoloji insanı olarak kariyerimde bana çok şey öğretti. Teknik kararlarımızın sadece kod seviyesinde değil, bir şirketin tüm operasyonlarını, çalışanlarının stres seviyesini ve hatta müşteri ilişkilerini nasıl etkilediğini bir kez daha gördüm. Bu yüzden, bir sonraki veritabanı tasarımınızda veya performans optimizasyonunda, denormalizasyonun cazibesine kapılmadan önce, uzun vadeli etkilerini göz önünde bulundurmanızı tavsiye ederim. Her zaman pragmatik olmak iyidir, ama “olur o kadar” derken, gelecekteki “o kadar”ın ne kadar büyük olabileceğini de hesaba katmak lazım.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

Sıkça Sorulanlar

Bu makale ile ilgili okurların sorduğu yaygın sorular.

Ürün ağacı denormalizasyonu neden performans sorunlarına yol açar?
Benim deneyimim, ürün ağacı denormalizasyonunun, veritabanı tasarımında yapılan performans odaklı kararların, zamanla devasa bir teknik borca dönüştüğünü gösterdi. Bu, özellikle raporlama ve maliyet hesaplama gibi işlemlerde ciddi performans sorunlarına yol açabilir. Veritabanının recursive ilişkilerini yönetmek, deep bir ürün ağacını hesaplamak ve stok durumlarını güncellemek gibi işlemler, ciddi performans sorunlarına neden olabilir.
Ürün ağacı yönetimi için hangi araçları kullanmalıyım?
Ben, ürün ağacı yönetimi için çeşitli araçları denedim. Bunlardan biri, veritabanı tasarımı araçlarıdır. Bu araçlar, veritabanının performansını optimize etmek ve recursive ilişkileri yönetmek için çok faydalı olabilir. Ayrıca, raporlama ve maliyet hesaplama araçları da ürün ağacı yönetiminde önemli bir rol oynar. Benim deneyimim, bu araçların doğru şekilde seçilmesi ve kullanılmasıyla, ürün ağacı yönetimi daha efektif hale gelebilir.
Denormalizasyon yerine ne kullanmalıyım?
Benim deneyimim, denormalizasyon yerine normalize edilmiş bir veritabanı tasarımı kullanmanın daha efektif olduğunu gösterdi. Normalize edilmiş bir veritabanı tasarımı, verilerin tutarlılığını sağlar ve performans sorunlarını azaltabilir. Ancak, bu approach, daha fazla veri işleme gerektirebilir. Ben, denormalizasyon yerine normalize edilmiş bir veritabanı tasarımı kullanmayı tercih ediyorum, çünkü bu, uzun vadede daha az teknik borca yol açar.
Ürün ağacı yönetimi için kaç sefer denemek gerekir?
Benim deneyimim, ürün ağacı yönetimi için birden fazla deneme yapmanın gerekli olduğunu gösterdi. Her bir deneme, ürün ağacı yönetimindeki performans sorunlarını azaltmak ve teknik borcu azaltmak için önemlidir. Ben, birkaç farklı yaklaşımı denediğimde, ürün ağacı yönetiminde önemli bir ilerleme kaydettiğimi gördüm. Her bir deneme, ürün ağacı yönetimindeki sorunları daha iyi anlamak ve daha efektif bir çözüm bulmak için önemlidir.
ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Yeni yazılardan haberdar olun

Yeni içerikler ve teknik notlar e-postanıza gelsin.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar