DEV Community

Cover image for Bir Siparişten Satış Raporuna: OLTP, OLAP ve Data Warehouse
TAHA TÜZEL
TAHA TÜZEL

Posted on Fully Autonomous

Bir Siparişten Satış Raporuna: OLTP, OLAP ve Data Warehouse

Backend tarafında bir kullanıcı kaydı oluştururken veya bir siparişi kaydederken database kullanıyorum. Hangi alanları tutacağım, tabloları nasıl ilişkilendireceğim ve sorguyu nasıl yazacağım genellikle uygulamanın o anki ihtiyacına göre şekilleniyor.

Aynı veriden bir yıllık satış raporu çıkarmak istediğimde ise ihtiyaç değişiyor. Belirli bir müşterinin siparişini bulmakla bütün satışları aylara göre gruplamak aynı veriyi kullansa da database'den farklı işler istiyor.

Bu konuları çalışırken sık karşılaştığım açıklama şuydu:

OLTP günlük işlemler, OLAP analiz için kullanılır.

Bu ayrım başlangıç için anlamlı. Ama uygulamanın database'inde zaten rapor sorgusu yazabiliyorsam neden ayrıca data warehouse kullandığımızı tek başına açıklamıyor. Veriyi başka bir yere taşımak ne kazandırıyor, tablo yapısı neden değişiyor, satır ve sütun odaklı depolama bu resimde nereye oturuyor?

Bu yazıda önce bu kavramların neyi tarif ettiğini açıklayıp ardından aynı e-ticaret örneği üzerinde ilerleyeceğim. Ahmet'in 300 TL'lik muz siparişini, uygulamada oluştuğu andan aylık satış raporuna girdiği noktaya kadar takip edeceğiz. Tablolar ve sorgular anlatım için hazırladığım basitleştirilmiş örnekler.


Büyük Resim: Veriyi Kaydetmek ve Veriden Sonuç Çıkarmak

Bir veri sistemi, uygulamanın günlük işlemlerini yürütmek için çalışabilir. Kullanıcının hesabına erişmesi, bir kaydın durumunu görmesi veya yeni bir kayıt oluşturması bu kullanımın parçasıdır. Burada işlemlerin kısa sürede tamamlanması ve eşzamanlı kullanıcıların yaptığı değişikliklerin doğru yönetilmesi önemlidir.

Aynı veriyi analiz için kullandığımızda ise çok sayıda kaydı birlikte inceleriz. Toplamları hesaplar, dönemleri karşılaştırır veya veriyi farklı kırılımlarda gruplarız. Tek bir kaydın güncel durumundan, kayıtların birlikte ne anlattığına geçeriz.

OLTP (Online Transaction Processing) ilk kullanım biçimine, OLAP (Online Analytical Processing) ise analitik kullanıma odaklanır. Bunlar veriyi hangi iş için işlediğimizi anlatır. Data warehouse, analiz ihtiyacını desteklemek için kurduğumuz veri deposudur; farklı kaynaklardan gelen veriyi ortak bir düzen içinde saklamamızı sağlar.

E-ticaret örneğinde genel yol şöyle olabilir:

Müşteri sipariş verir
        ↓
Operasyonel Database
Siparişi kaydet, stok bilgisini güncelle → OLTP
        ↓
Veriyi analiz için hazırla ve aktar
        ↓
Data Warehouse
        ↓
Aylara ve kategorilere göre satışları hesapla → OLAP
        ↓
Satış raporu / Dashboard
Enter fullscreen mode Exit fullscreen mode

Ahmet sipariş verdiğinde uygulama ona işlemin sonucunu gösteriyor. Ay sonunda hazırlanan raporda ise aynı sipariş, meyve kategorisinin satış toplamına katkı sağlıyor.

Her uygulama için bu aşamaları ayrı sistemlerle kurmamız gerekmiyor. Ancak bu yolu takip etmek, iki kullanımın nerede farklılaştığını anlamayı kolaylaştırıyor.


Adım 1: Database ve OLTP ile Günlük İşlemleri Yürütmek

Database, veriyi belirli bir düzen içinde sakladığımız ve sorguladığımız yapıdır. DBMS (Database Management System) ise bu verinin saklanmasını, okunmasını ve değiştirilmesini yöneten yazılımı ifade eder. PostgreSQL ve MySQL birer DBMS örneğidir.

İlişkisel bir database'de veriyi tablolarda tutarız. Satırlar kayıtları, sütunlar ise bu kayıtların alanlarını temsil eder. Primary key, bir kaydı benzersiz biçimde tanımlar; foreign key de tablolar arasındaki ilişkinin bütünlüğünü korumaya yardımcı olur.

Bir kaydı bulmak için kullanılan index, sorgunun ilgili verilere daha az kayıt okuyarak ulaşmasını sağlayan ek bir yapıdır. Bunun da maliyeti vardır: index alanı kaplar ve veri değiştiğinde database index'i de güncellemek zorunda kalabilir. Bu nedenle her sütuna index eklemek yerine uygulamanın sorgularını dikkate almak gerekir.

OLTP sistemleri bu araçları, çok sayıda kullanıcının kısa süren işlemlerini karşılamak için kullanır. İşlem çoğunlukla belirli bir müşteri, sipariş veya ürün gibi sınırlı bir kayıt kümesiyle ilgilidir. Hem okuma hem yazma vardır; yanıt süresinin kısa olması uygulamanın kullanımını doğrudan etkiler.

Örneğin Ahmet siparişinin durumunu görmek istediğinde bütün satış geçmişini incelemiyoruz. Belirli bir sipariş kaydına gidiyoruz:

SELECT order_id, customer_id, status, created_at
FROM orders
WHERE order_id = 1042;
Enter fullscreen mode Exit fullscreen mode

order_id primary key ise bu alanı destekleyen index, database'in ilgili siparişi bulmasına yardımcı olur. Sonuç, sipariş ekranında göstereceğim durum bilgisi olabilir.

Aynı sırada başka bir kullanıcı yeni sipariş oluşturabilir, bir çalışan da başka bir siparişin durumunu güncelleyebilir. OLTP tarafındaki ihtiyaç bu işlemleri birlikte yürütürken veriyi doğru durumda tutmak. Sipariş kaydetmek kadar sipariş okumak da bu iş yüküne ait.

Normalization: Veriyi Neden Ayrı Tablolarda Tutuyoruz?

Normalization, veriyi ilişkilerine göre düzenleyerek gereksiz tekrarları ve bu tekrarlardan doğan değişiklik sorunlarını azaltma yaklaşımıdır. Aynı bilginin birçok kayıtta tutulması, bir değişiklik sırasında kopyaların bir bölümünün güncel, diğer bölümünün eski kalmasına yol açabilir.

Bu nedenle bir varlığa ait bilgiyi uygun tabloda saklar, diğer kayıtları ona anahtar üzerinden bağlarız. Bir ilişkiyi okumak için join yapmak gerekebilir; bunun karşılığında ortak bilgiyi her kopyasında ayrı ayrı güncellemekten kurtuluruz.

E-ticaret uygulamasında şöyle bir model kurabiliriz:

customers
  customer_id, name, email

orders
  order_id, customer_id, status, created_at

order_items
  order_item_id, order_id, product_id, quantity, unit_price

products
  product_id, name, category_id, current_price, stock_quantity

categories
  category_id, name
Enter fullscreen mode Exit fullscreen mode

Ahmet'in adı ve e-posta adresi customers tablosunda duruyor. Sipariş kaydı, aynı alanları tekrar taşımak yerine customer_id ile Ahmet'e bağlanıyor. E-posta adresi değiştiğinde bütün eski siparişleri dolaşmam gerekmiyor.

orders, siparişin genel durumunu; order_items ise hangi üründen kaç adet alındığını saklıyor. Bir siparişte farklı ürünler bulunabileceği için ürün kalemlerini ayrı tutuyoruz.

Burada unit_price alanını özellikle sipariş kaleminde saklıyorum. Bu örnekte alan, indirim varsa onu da yansıtan satış anındaki birim fiyatı temsil etsin. Ahmet iki paket muzu paket başına 150 TL'den aldıysa sipariş tutarı 300 TL olur. Ürünün current_price değeri yarın değiştiğinde geçmiş siparişin fiyatını değiştirmek istemiyorum.

Bu iki fiyat alanı gereksiz bir kopya değil; biri güncel fiyatı, diğeri geçmiş satışın fiyatını anlatıyor. Veri modelini kurarken alanların yalnızca isimlerine değil, hangi bilgiyi temsil ettiklerine de bakmam gerekiyor.

Transaction: Birbiriyle İlgili Değişiklikleri Birlikte Tamamlamak

Transaction, database üzerindeki bir veya daha fazla işlemi bir bütün olarak ele aldığımız çalışma birimidir. Değişiklikleri COMMIT ile tamamlar, işlemden vazgeçtiğimizde veya hata durumunda ROLLBACK ile geri alırız.

Transaction kelimesi burada ödeme anlamına gelmiyor. Bir adres değişikliği veya kayıt silme de transaction içinde gerçekleşebilir. Önemli olan, database işlemlerinin hangi sınır içinde birlikte değerlendirildiğidir.

Transaction davranışını açıklamak için kullanılan ACID özellikleri şunlardır:

  • Atomicity: Transaction'ın değişiklikleri birlikte gerçekleşir; işlem tamamlanmazsa yarım değişiklikler kalıcı hale gelmez.
  • Consistency: Doğru tasarlanmış işlemler database'in tanımlı bütünlük kurallarını korur. Foreign key gibi kurallar bu sınırın parçasıdır.
  • Isolation: Database, eşzamanlı transaction'ların birbirlerinin değişikliklerini nasıl göreceğini yönetir. Davranış seçilen isolation level'a bağlıdır.
  • Durability: Database başarıyla commit ettiği değişiklikleri kalıcı tutma garantisi verir.

Ahmet'in siparişinde sipariş kaydı oluşturmak, ürün kalemini eklemek ve stoktan iki paket düşmek birlikte ele alınabilir:

BEGIN
  ↓
Sipariş kaydını oluştur
  ↓
Sipariş kalemini ekle
  ↓
Stok miktarını güncelle
  ↓
COMMIT
Enter fullscreen mode Exit fullscreen mode

Bu değişiklikler aynı transaction içindeyse hata durumunda geri alabilirim. Böylece ürün kalemi oluşmadığı halde tamamlanmış görünen bir sipariş bırakmam.

Eşzamanlı stok kontrolünü de doğru tasarlamak gerekir. İki müşteri son ürünü aynı anda almaya çalışıyorsa kontrol ve güncellemeyi uygun koşullar veya kilitlemeyle yönetmeliyim. Sadece transaction başlatmak, uygulamadaki bütün iş kurallarını kendiliğinden doğru hale getirmiyor.

Sipariş kaydolduktan sonra uygulama günlük işlerini yapmaya devam edebilir. Şimdi aynı kayıtlara başka bir amaçla bakacağız.


Adım 2: OLAP ile Veriden Analiz Çıkarmak

OLAP, büyük veri kümeleri üzerinde analiz yapmaya odaklanan işlem yaklaşımıdır. Çok sayıda kaydı filtrelemek, gruplamak ve farklı dönemleri karşılaştırmak bu iş yükünde sık görülür.

Aggregation, birçok kayıttan toplam, ortalama veya adet gibi bir sonuç hesaplamaktır. SQL'deki SUM, AVG ve COUNT bu hesaplamalara örnek. Analizde kullandığımız satış tutarı veya ürün adedi gibi sayısal değerlere de measure diyebiliriz.

Bir measure'ı hangi açıdan inceleyeceğimizi ise dimension belirler. Tarih, ürün kategorisi veya mağaza gibi bilgiler, sonucu gruplamak ve filtrelemek için kullanabileceğimiz kırılımlardır. Böylece tek bir toplam yerine, o toplamın farklı bölümlerini inceleyebiliriz.

E-ticaret uygulamasında artık şu soruyu soralım:

2025 yılında aylara göre hangi ürün kategorisinden ne kadar satış yaptık?

Raporun kullanacağı kayıtların küçük bir bölümü şöyle olsun:

Tarih Ürün Kategori Adet Satış tutarı
12 Ocak 2025 Muz Meyve 2 300 TL
13 Ocak 2025 Kahve İçecek 1 120 TL
20 Ocak 2025 Muz Meyve 1 150 TL
3 Şubat 2025 Muz Meyve 1 150 TL
4 Şubat 2025 Kahve İçecek 2 200 TL

Ahmet'in siparişi ilk satırda duruyor. Ocak ayının meyve satışlarını hesapladığımda onun 300 TL'lik siparişini, aynı ayın diğer 150 TL'lik muz satışıyla topluyorum. Bu örnekte sonuç 450 TL.

Satış tutarı measure, ay ve kategori ise analizin kırılımları. Başta tek bir siparişin durumuna bakıyorduk; şimdi birden fazla sipariş kaleminden sonuç çıkarıyoruz.

Analizin Ayrıntı Düzeyini Değiştirmek

OLAP analizinde aynı measure'ı farklı ayrıntı seviyelerinde inceleyebiliriz. Drill-down, yıllık toplamdan aylara veya aylardan günlere inmek gibi daha ayrıntılı bir seviyeye geçmeyi ifade eder. Roll-up ise ayrıntılı kayıtları daha üst bir seviyede toplar; günlük satışlardan aylık toplam çıkarmak buna örnek.

Bu geçişlerin mümkün olması, elimizdeki verinin ayrıntı düzeyine bağlıdır. Yalnızca aylık toplamı sakladıysak, günlük sonuçları o toplamdan geri çıkaramayız.

Örnekte Ocak ayındaki 450 TL'yi gördükten sonra günlere inebilirim. 12 Ocak'ta 300 TL, 20 Ocak'ta 150 TL meyve satışı görürüm. İki kaydı koruduğum için hem aylık rapor çıkarabiliyorum hem de o toplamın nasıl oluştuğunu inceleyebiliyorum.

OLAP İçin Mutlaka Ayrı Bir Sistem Gerekir mi?

OLAP bir kullanım biçimini tarif eder. İlişkisel bir database de JOIN ve GROUP BY gibi işlemlerle analitik sorgular çalıştırabilir. Küçük bir veri kümesinde mevcut database üzerinde rapor üretmek yeterli olabilir.

Ayrı bir analiz sistemi ihtiyacı, veri büyüklüğü ve sorguların yaptığı işle birlikte belirginleşir. Tek bir kaydı index üzerinden bulmakla geniş bir tarih aralığındaki tutarları okumak aynı maliyete sahip değildir. Analitik sorgu, uygulamanın günlük işlemleriyle aynı CPU, bellek ve disk kaynaklarını paylaşabilir.

Örnekte yıllık raporu PostgreSQL üzerinde çıkarabilirim. Ama rapor sorguları büyüdüğünde yeni siparişlerin yanıt süresini etkileyip etkilemediğine de bakmam gerekir. Fiziksel mağazaların satışlarını rapora eklemek istersem farklı kaynakları birleştirme ihtiyacı da doğar.

Bu noktada farkın bir bölümü sorgunun kapsamından, bir bölümü de verinin nasıl saklandığından geliyor.


Adım 3: Row-Based ve Column-Based Depolama

Row-based ve column-based storage, verinin fiziksel depolama düzenini anlatır. Tablo ekranda yine satır ve sütunlardan oluşabilir; değişen şey, database'in bu alanları depolama katmanında nasıl yerleştirdiğidir.

Bu ayrım, bir sorgunun ihtiyaç duyduğu verilere ulaşırken ne kadar veri okuduğunu etkileyebilir. Sorgu birkaç kaydın birçok alanını mı istiyor, yoksa çok sayıda kaydın yalnızca birkaç sütununu mu kullanıyor?

Row-Based Storage: Aynı Kaydın Alanları Birlikte

Satır odaklı depolamada aynı kayda ait alanlar birlikte yer alır. Belirli bir kaydın farklı alanlarını okumak veya o kayıt üzerinde değişiklik yapmak için uygun bir erişim düzenidir. Geleneksel OLTP sistemlerinde bu yaklaşım yaygındır.

Satış kayıtlarının ürün, adet ve tutar alanlarını kavramsal olarak şöyle gösterebiliriz:

[Muz,   2, 300]
[Kahve, 1, 120]
[Muz,   1, 150]
[Muz,   1, 150]
[Kahve, 2, 200]
Enter fullscreen mode Exit fullscreen mode

Ahmet'in sipariş kalemini okuduğumda ürün, adet ve fiyat gibi alanları birlikte istiyorum. Satır odaklı yerleşim bu ihtiyaca uyuyor.

Ancak sadece bütün satış tutarlarını toplamak istersem satırları içeren depolama bloklarından, sorguda kullanmadığım alanları da okumam gerekebilir. Tabloda daha fazla sütun bulundukça bu fark büyüyebilir; gerçek maliyet sorgu planına ve depolama düzenine bağlıdır.

Column-Based Storage: Aynı Alanın Değerleri Birlikte

Sütun odaklı depolamada aynı sütuna ait değerler birlikte saklanır. Çok sayıda satırın az sayıda alanını kullanan sorgular, ihtiyaç duyduğu sütunların bloklarını okuyabilir. Benzer değerlerin bir arada bulunması sıkıştırmayı da kolaylaştırabilir.

Aynı örneğin sütun odaklı gösterimi şöyle olur:

Ürün:         [Muz, Kahve, Muz, Muz, Kahve]
Adet:         [2, 1, 1, 1, 2]
Satış tutarı: [300, 120, 150, 150, 200]
Enter fullscreen mode Exit fullscreen mode

SUM(sales_amount) hesaplamak istediğimde yalnızca satış tutarı alanına ihtiyaç duyuyorum. Kategori bazında analiz yapıyorsam ilgili kategori bilgilerini de okurum; müşterinin adresi gibi raporda kullanmadığım alanları taşımam gerekmeyebilir.

Gerçek sistemlerde sütunlar bloklara ve farklı makinelere dağılabilir. Filtreler veya bölümleme sayesinde sütunun tamamı yerine ilgili bölümünü okumak yeterli olabilir. Çizim, bütün değerlerin tek ve kesintisiz bir yerde durduğunu söylemiyor.

Sütun odaklı sistemlere yeni veri yazmak da mümkündür. Bu sistemler toplu yüklemeler için optimizasyonlar kullanabilir; sık ve tekil satır güncellemelerinin maliyeti ise farklı olabilir. Aynı şekilde satır odaklı bir sisteme kayıt eklemek, index'ler ve bütünlük kontrolleri nedeniyle yalnızca yeni satırı diske koymaktan ibaret değildir.

Dolayısıyla “OLTP her zaman satır, OLAP her zaman sütun” kesin bir kural değil. Bu yerleşimler belirli erişim biçimlerini destekleyen yaygın tasarım tercihleridir. Verinin tablolar arasında nasıl modellendiği ise bundan ayrı bir karar; ona warehouse içinde döneceğiz.


Adım 4: Data Warehouse Nedir?

Data warehouse, analiz ve raporlama için tasarladığımız veri deposudur. Farklı kaynakların verilerini bir araya getirir, ortak tanımlarla düzenler ve tarihsel karşılaştırmalar için saklar. Kullanıcılar bu hazırlanmış veri üzerinden sorgular çalıştırabilir.

Buradaki düzen sadece dosyaların veya tabloların aynı yerde durmasıyla oluşmaz. Kaynakların alanlarını ortak anlamlara taşımak, ürün kodlarını eşleştirmek ve rapordaki hesapları tanımlamak gerekir. Bir kaynakta “satış” anlamına gelen kayıt, diğerinde farklı bir aşamayı temsil ediyorsa bunları doğrudan toplamak yanlış sonuç verebilir.

Warehouse genellikle satış veya finans gibi iş konuları etrafında düzenlenir. Veriyi farklı dönemlerde karşılaştırmak için tarihler ve gerektiğinde değişen bilgilerin geçmiş sürümleri de modelin parçası olur. Günlük uygulamada güncel duruma odaklanırken burada geçmişin hangi haliyle analiz edileceğini ayrıca düşünürüz.

Bu, warehouse'daki verinin hiçbir zaman değişmediği anlamına gelmez. Yeni kayıtlar yüklenebilir, hatalı veriler düzeltilebilir veya geç gelen bilgiler işlenebilir. Tarihsel analiz için önemli olan, bu değişiklikleri tanımlı kurallarla yönetmektir.

Database ile Data Warehouse Arasındaki İlişki

Database, veriyi saklama ve sorgulama için kullandığımız genel kavramdır. Warehouse da veritabanı teknolojileri kullanabilir. “Database mi, warehouse mı?” karşılaştırmasında çoğunlukla uygulamanın operasyonel database'i ile analitik veri deposunu karşılaştırıyoruz.

Operasyonel database, uygulamanın çalışma akışını destekler. Warehouse'ın modeli ve kaynakları ise raporlama ihtiyacına göre şekillenir. OLTP/OLAP işlem yaklaşımını, operasyonel database/warehouse ayrımı da bu yaklaşımı destekleyen veri düzenini anlatır.

Karşılaştırma Operasyonel Database Data Warehouse
Ana ihtiyaç Günlük uygulama işlemlerini yürütmek Analiz ve raporlama yapmak
Tipik erişim Belirli kayıtlar üzerinde kısa okumalar ve değişiklikler Geniş kayıt kümeleri üzerinde gruplama ve hesaplama
Veri düzeni Uygulamanın iş kuralları ve ilişkileri Ortak iş tanımları ve analitik model
Geçmiş Uygulamanın ihtiyacına göre saklanır Dönem karşılaştırmaları ve geçmişin yorumu için planlanır

Ahmet'in sipariş durumunu değiştirirken operasyonel database'e gidiyorum. Aylık satış raporunda ise warehouse'daki hazırlanmış satış verisini kullanabilirim. Uygulamanın database'i yıllarca sipariş saklayabilir; warehouse da birkaç dakika önce oluşan satışları içerebilir. Ayrımı yalnızca verinin yaşı belirlemiyor.

Satış Analizinde Warehouse Ne Kazandırır?

Warehouse kurarken veri kaynaklarını birleştirmenin yanında analiz yükünü ayrı yönetebiliriz. Bu, günlük uygulama işlemleri ile raporların aynı çalışma kaynakları için yarışmasını azaltmaya yardımcı olur. Karşılığında bir aktarım süreci kurmak, veri güncelliğini izlemek ve iki sistemin maliyetini yönetmek gerekir.

E-ticaret örneğinde internet siparişleriyle mağaza satışlarını birleştirelim. İnternet sisteminde muzun kodu 10, mağaza dosyasında MUZ-01 olabilir. Ortak ürün eşleştirmesi olmadan rapor bunları iki farklı ürün gibi sayabilir.

Benzer şekilde satış tutarının anlamını da belirlemem gerekiyor. Bu yazıdaki rapor tamamlanmış sipariş kalemlerinin indirim sonrası ürün tutarını hesaplasın; vergi, kargo ve iadeleri basit örneğin dışında tutalım. Gerçek kullanımda bu alanların her biri için iş kuralı gerekir.

Bu kararları hazırlık sırasında verdiğimde her rapor aynı satış tanımından hareket edebilir. Şimdi bu ortak veriyi warehouse içinde nasıl düzenleyeceğimize bakalım.


Adım 5: Fact ve Dimension ile Analitik Model Kurmak

Dimensional modeling, analiz için veriyi olaylar ve bu olayları açıklayan bilgiler etrafında düzenleme yaklaşımıdır. Amaç, hangi değerleri ölçtüğümüzü ve bunları hangi kırılımlarda inceleyebileceğimizi modelde görünür hale getirmek.

Fact table, satış gibi gerçekleşen olayları ve bu olaylara ait ölçümleri saklar. Dimension table ise olayları tarih, ürün veya müşteri gibi açılardan açıklayan bilgileri tutar. Fact içindeki anahtarlar, ilgili dimension kayıtlarına bağlanır.

Bu ayrım, raporun her defasında operasyonel sistemdeki bütün ilişkileri çözmesi yerine analize uygun bağlantılarla çalışmasını sağlar. Modelin doğru kurulması performans kadar hesapların anlamını da etkiler.

Satış örneğinde adet ve tutarı fact_sales içinde tutabiliriz. Ürünün adı ve kategorisi dim_product, tarih bilgileri ise dim_date içinde yer alabilir. Böylece satış miktarını ürün veya zaman kırılımıyla birlikte sorgulayabiliriz.

Grain: Bir Satır Tam Olarak Neyi Temsil Ediyor?

Grain, fact table'daki bir satırın temsil ettiği ayrıntı düzeyidir. Satır bir siparişin tamamını, tek bir sipariş kalemini veya günlük kategori toplamını temsil edebilir. Bu kararı baştan vermek, hangi measure'ları hangi düzeyde tutabileceğimizi belirler.

Farklı ayrıntı seviyelerini aynı hesapta karıştırırsak veriyi yanlış sayabiliriz. Bir siparişin toplamını her ürün kalemine tekrar yazmak, kalemleri topladığımızda aynı sipariş tutarını birkaç kez hesaba katmamıza neden olur.

Bizim fact_sales tablomuzda her satır tek bir sipariş kalemini temsil etsin. Ahmet'in iki paket muz aldığı kalem için adet 2, tutar 300 olur. Aynı siparişte kahve de olsaydı kahve ayrı bir satırda dururdu; iki satırın tutarlarını topladığımda siparişin ürün toplamına ulaşabilirdim.

Warehouse'da yalnızca günlük veya aylık toplam tutmamız gerekmez. Bu ayrıntılı kayıtları koruyarak farklı seviyelerde rapor çıkarabiliriz. Sık kullanılan sonuçlar için ayrıca özet tablolar hazırlamak mümkün; hangi ayrıntıyı saklayacağımızla hangi hesabı önceden yapacağımız ayrı kararlar.

Star Schema: Fact ve Dimension Tablolarını Bağlamak

Star schema, dimension tablolarının merkezdeki fact table'a doğrudan bağlandığı modeldir. Fact, olayın ölçümlerini ve dimension anahtarlarını tutar; dimension tablolarındaki alanlar filtreleme ve gruplama için kullanılır.

Dimension anahtarı, kaynak uygulamadaki kimlikle aynı olmak zorunda değildir. Warehouse içinde ayrı bir anahtar üretebiliriz. Bu, farklı kaynakların kayıtlarını eşleştirmeyi ve gerektiğinde bir varlığın geçmiş sürümlerini ayrı tutmayı kolaylaştırır.

Örnekteki model şöyle görünebilir:

dim_date                  fact_sales                  dim_product
  date_key          ←       date_key
  full_date                 product_key         →       product_key
  year                      order_item_id               product_id
  month                     quantity                    product_name
                            sales_amount                category_name
Enter fullscreen mode Exit fullscreen mode

date_key, satışın ait olduğu tarih kaydına; product_key ise ürün dimension'ındaki kayda bağlanıyor. Bu örnekte satış tarihi için siparişin oluşturulma tarihini kullanıyoruz. Başka bir rapor ödeme veya teslim tarihini esas alabilir; tarih seçimi de raporun tanımına ait.

dim_date içinde yıl ve ayın yanında hafta veya hafta sonu bilgisi de saklayabilirim. Böylece aynı satış verisini farklı zaman kırılımlarında inceleyebilirim. dim_product ise ürün adıyla birlikte kategoriyi veriyor.

Denormalization: Analizde Gereken Bilgileri Bir Araya Getirmek

Denormalization, belirli sorguların ihtiyaç duyduğu bilgileri daha az ilişki üzerinden okuyabilmek için bazı verileri bir arada tutma veya kontrollü biçimde tekrar etme yaklaşımıdır. Normalization ile farklı bir kullanım ihtiyacına cevap verir.

Analitik modelde dimension'ların açıklayıcı alanlarını bir arada tutmak yaygındır. Bunun karşılığında veri hazırlama sırasında tekrarların doğru güncellenmesini yönetmemiz gerekir. Join sayısının azalması bazı sorguları kolaylaştırabilir, ancak daha az normalization her koşulda daha yüksek hız garantisi vermez.

Snowflake schema, bazı dimension bilgilerini ayrı ilişkili tablolara bölerek daha normalize biçimde düzenleyen modeldir. Veri tekrarını azaltabilir ama sorguda ek bağlantılar gerektirir. Star schema ile arasındaki fark, dimension bilgilerinin ne kadar ayrıştırıldığıyla ilgilidir.

Operasyonel modelde ürün ile kategoriyi ayrı tablolarda tutmuştuk. Warehouse'da category_name alanını dim_product içine koyabiliriz. Böylece rapor, ürün dimension'ına bağlandığında kategori bilgisine de ulaşır. Kategoriyi ayrı bir dimension olarak tutup ürün dimension'ını ona bağlarsam snowflake yaklaşımına geçmiş olurum.

Bizim basit satış raporumuzda star schema ile ilerleyebiliriz. Fact ile dimension tablolarını yine join edeceğiz. OLAP sistemlerinin join yapmadığı gibi bir kural yok; modelde analizin ihtiyaç duyduğu ilişkileri kullanıyoruz.

Değişen Dimension Bilgilerinin Geçmişini Korumak

Slowly Changing Dimensions (SCD), dimension alanları değiştiğinde geçmişi nasıl yöneteceğimizi ele alan yöntemlerdir. Yeni değerin eski kaydın üzerine yazılmasıyla ayrı bir sürüm olarak saklanması, raporun geçmişi yorumlama biçimini değiştirir.

Type 1 yaklaşımı eski değeri günceller ve o alanın geçmiş sürümünü korumaz. Type 2 ise yeni bir dimension satırı açarak sürümleri saklar; geçerlilik tarihleriyle hangi sürümün hangi döneme ait olduğunu belirleyebiliriz.

Kahveyi bugün “İçecek” kategorisinden “Kahve ve Çay” kategorisine taşıdığımızı düşünelim. Type 1 kullandığımda geçmiş satışlar da güncel kategori altında görünebilir. Type 2 kullandığımda ise yeni kategori için ayrı bir ürün sürümü oluşturur, eski satışları eski sürüme bağlı tutarım.

Hangi davranışın doğru olduğu raporun sorusuna bağlı. Geçmişi o dönemdeki sınıflandırmayla mı görmek istiyorum, yoksa bütün yılları bugünkü kategorilere göre mi karşılaştıracağım? Warehouse kurarken bunu da belirlemem gerekiyor.

Artık satışın ne anlama geldiğini ve onu hangi modelde saklayacağımızı biliyoruz. Sıradaki iş, kaynak veriyi bu modele ulaştırmak.


Adım 6: ETL ve ELT ile Veriyi Taşımak ve Dönüştürmek

Veriyi kaynak sistemlerden alıp başka bir veri deposuna hazırlayan akışlara data pipeline diyebiliriz. Bu akış veri çekme, kontrol etme ve hedefe yazma gibi işleri birbirine bağlar. Kaynak bir database olabileceği gibi bir API veya dosya da olabilir.

Extract, veriyi kaynaktan alma adımıdır. Transform, veriyi hedef kullanım için dönüştürmeyi; Load ise hedefe yüklemeyi ifade eder. Tarih formatlarını eşitlemek, kaynak ürün kodlarını eşleştirmek veya satış tutarını hesaplamak dönüşüme örnek.

ETL ve ELT, bu adımların hangi sırayla ve nerede gerçekleştiğini ayırır. İkisinde de analize uygun veri üretmek istiyoruz; dönüşümün hedefe yüklemeden önce veya sonra yapılması çalışma biçimini değiştiriyor.

ETL: Önce Dönüştürmek, Sonra Yüklemek

ETL (Extract, Transform, Load) akışında veriyi alır, hedefe yüklemeden önce bir işlem ortamında dönüştürür ve hazırladığımız sonucu hedefe yazarız. Dönüşüm, hedef warehouse'ın dışında çalışan bir işlem tarafından yürütülebilir.

Bu düzen, hedefe hangi verinin hangi biçimde gireceğini yükleme öncesinde kontrol etmeyi sağlar. Dönüşüm ortamının işlem kapasitesi ve hata durumları da akışın parçasıdır; veriyi almakla başarıyla yüklemek arasında izlememiz gereken işler vardır.

Sipariş örneğinde orders ile order_items kayıtlarını alıp ilgili alanları birleştirebilirim. Tamamlanmış siparişleri seçer, quantity × unit_price ile ürün tutarını hesaplar ve uygun dimension anahtarlarını bulurum. Ardından hazırladığım satış satırlarını fact_sales tablosuna yazarım.

Operasyonel Database
        ↓ Extract
Siparişleri ve sipariş kalemlerini al
        ↓ Transform
Kontrol et, birleştir, tutarı hesapla, anahtarları eşleştir
        ↓ Load
Hazırlanan kayıtları fact_sales tablosuna yükle
Enter fullscreen mode Exit fullscreen mode

Ahmet'in iki paketlik sipariş kalemi bu akışın sonunda quantity = 2 ve sales_amount = 300 değerleriyle warehouse'a ulaşır.

ELT: Önce Yüklemek, Sonra Hedefte Dönüştürmek

ELT (Extract, Load, Transform) akışında veriyi önce hedef sistemin ham veri alanına yükler, dönüşümleri daha sonra o sistemin işlem gücüyle yaparız. Warehouse içinde çalışan SQL sorguları, ham tablolardan analitik tablolar üretebilir.

Ham veriyi almakla analize hazır veri sunmak farklı aşamalardır. Kaynak tabloların hedefe yüklenmesi, raporun bunları doğrudan kullanması gerektiği anlamına gelmez. Kontroller ve iş kuralları, dönüşüm sırasında yine uygulanır.

Örnekte sipariş ve sipariş kalemi tablolarını warehouse'ın ham alanına alabilirim. Ardından orada çalışan sorgularla tamamlanmış siparişleri seçer, fiyat hesaplarını yapar ve fact_sales tablosunu oluştururum:

Operasyonel Database
        ↓ Extract
Kaynak kayıtları al
        ↓ Load
Warehouse'ın ham veri alanına yükle
        ↓ Transform
Hedefteki sorgularla dimension ve fact tablolarını hazırla
Enter fullscreen mode Exit fullscreen mode

ETL ile aynı satış sonucunu elde edebilirim. Fark, dönüşümün warehouse'a girmeden önce mi yoksa girdikten sonra mı çalıştığı.

Bu yöntemler sadece OLTP'den OLAP'e geçişte kullanılmaz. Mağazanın gönderdiği bir CSV dosyasını temizleyip yüklemek veya bir API'den gelen kayıtları hedef tabloya hazırlamak da ETL ya da ELT akışı olabilir.

Staging ve Data Lake Hangi İhtiyacı Karşılıyor?

Staging, gelen veriyi karşılamak ve sonraki işlem için hazırlamak amacıyla kullandığımız ara alanı ifade eder. Kayıtları kontrol etmek veya bir yükleme grubunu diğerinden ayırmak için kullanılabilir. ETL ve ELT akışlarının ikisinde de staging bulunabilir.

Data lake ise farklı türlerdeki veriyi, çoğu zaman kaynakta gelen ham biçimini de koruyarak saklayabildiğimiz alandır. Tablo verilerinin yanında JSON event'leri, görseller veya ses dosyaları da burada yer alabilir. Veriyi ham saklamak, ne içerdiğini belgelemek ve erişimini yönetmek ihtiyacını ortadan kaldırmaz.

Satış örneğinde mağazanın gönderdiği CSV dosyasını önce staging alanına alıp satırlarını kontrol edebilirim. Dosyanın orijinal halini ileride tekrar işlemek için data lake içinde saklamayı da tercih edebilirim. Böylece dönüşüm kuralını değiştirdiğimde kaynak dosyaya dönebilirim.

Her aktarımda ayrı bir data lake kurmak zorunda değilim. Veriyi doğrudan warehouse'a yükleyebilirim; bu bileşenlerin yerini, saklama ve yeniden işleme ihtiyacı belirler.


Adım 7: Verinin Güncelliğini ve Aktarımın Doğruluğunu Yönetmek

Bir pipeline'ın dönüşüm sırası kadar ne zaman çalıştığı da önemlidir. Raporun veri güncelliği, kaynaktaki değişikliklerin analitik sisteme ne kadar gecikmeyle ulaştığına bağlıdır.

Batch processing, belirli bir veri grubunu toplu işlemektir. İş saatlik veya günlük çalışabilir; bir dosyanın gelmesi de işlemi başlatabilir. Stream processing ise sürekli gelen veri akışını işleme yaklaşımıdır. Her kaydın sıfır gecikmeyle sonuçlanacağını garanti etmez; hedef daha kısa gecikmelerle akışı işlemektir.

Günlük satış raporunda siparişleri gece toplu aktarmak yeterli olabilir. Ahmet'in bugün verdiği sipariş, aktarım tamamlandıktan sonra yarınki raporda görünür. Satış ekranının gün içinde güncellenmesi gerekiyorsa daha sık yükleme veya değişiklikleri sürekli işleyen bir akış seçebilirim.

ETL/ELT ile batch/stream farklı kararları anlatır. İlki dönüşümün yüklemeye göre sırasıyla, ikincisi veriyi işleme biçimiyle ilgilidir.

Aynı Veriyi Tekrar Aktardığımızda Ne Olur?

Artımlı yükleme, her seferinde bütün kaynağı almak yerine yeni veya değişen kayıtları aktarma yaklaşımıdır. Bunun için hangi değişikliklerin gerçekleştiğini takip etmemiz gerekir. Güncellemeler kadar silmelerin hedefe nasıl yansıtılacağını da belirlemeliyiz.

CDC (Change Data Capture), kaynaktaki ekleme, güncelleme ve silmeleri yakalamaya yarayan yaklaşımı ifade eder. Bu değişiklikleri veri aktarımına girdi olarak kullanabiliriz. Hangi yöntemi seçeceğimiz, kaynak sistemin sunduğu imkanlara ve gerekli güncelliğe bağlıdır.

Örnekte her gece bütün sipariş geçmişini kopyalamak yerine yeni ve değişen siparişleri alabilirim. Ancak aktarım yarıda kalırsa aynı kayıtları yeniden işlemek gerekebilir. Ahmet'in siparişini ikinci kez eklersem 300 TL'lik satış raporda 600 TL görünür.

Bu yüzden kaydı hangi anahtarla tanıyacağımı ve tekrar geldiğinde nasıl davranacağımı belirlemem gerekiyor. Tek kaynaklı örnekte order_item_id satış kalemini tanıyabilir; farklı kaynakları birleştirdiğimde kaynak kimliğini de dikkate almam gerekir. Bir mağazanın 1042 numaralı kaydı, internet sistemindeki aynı numarayla aynı olay olmayabilir.

Pipeline'ın başarılı çalışması da yalnızca hata vermeden bitmesi değildir. Beklenen veriyi alıp almadığını kontrol etmeli, eksik veya başarısız aktarımda raporu güncelmiş gibi göstermemeliyim. Verinin doğruluğunu korumak, rapor sorgusundan önce başlayan bir iş.


Adım 8: BI ve Data Mart ile Hazırlanan Veriyi Kullanmak

BI (Business Intelligence), veriyi raporlar ve analizlerle iş kararlarında kullanılabilir hale getiren çalışmaların ve araçların genel adıdır. Bir BI aracı veri kaynağına bağlanabilir, ölçümleri hesaplayabilir ve sonuçları grafik veya dashboard olarak sunabilir.

Warehouse bu kullanım için düzenlenmiş veriyi sağlar. Rapor katmanı da bu veri üzerinde hangi measure'ların nasıl gösterileceğini belirler. Veri doğru gelse bile raporda farklı filtre veya hesap kullanmak sonucu değiştirebilir; modelin anlamını raporda da korumak gerekir.

Örnekte artık başta sorduğumuz soruya dönebiliriz:

2025 yılında aylara göre hangi ürün kategorisinden ne kadar satış yaptık?

Hazırladığımız modelde sorgu şöyle olabilir:

SELECT
    d.year,
    d.month,
    p.category_name,
    SUM(f.sales_amount) AS total_sales
FROM fact_sales AS f
JOIN dim_date AS d ON d.date_key = f.date_key
JOIN dim_product AS p ON p.product_key = f.product_key
WHERE d.year = 2025
GROUP BY d.year, d.month, p.category_name
ORDER BY d.year, d.month, total_sales DESC;
Enter fullscreen mode Exit fullscreen mode

fact_sales, sipariş kalemlerinin tutarını veriyor. dim_date ile yıl ve ayı, dim_product ile kategoriyi öğreniyoruz. WHERE yalnızca 2025'i seçiyor; GROUP BY ise aynı ay ve kategoriye ait satışları birlikte hesaplamamızı sağlıyor.

İkinci bölümdeki beş örnek kayıtla beklenen sonuç şu olur:

Yıl Ay Kategori Toplam satış
2025 1 Meyve 450 TL
2025 1 İçecek 120 TL
2025 2 İçecek 200 TL
2025 2 Meyve 150 TL

Ocak ayındaki 450 TL'nin 300 TL'si Ahmet'in siparişinden, kalan 150 TL'si aynı ayın diğer muz satışından geliyor. Ahmet'in kaydı warehouse'da ayrı bir satış kalemi olarak duruyor; raporda ise başka kayıtlarla aynı grupta yer alıyor.

Bir BI aracı bu sonucu aylık kategori grafiği olarak gösterebilir. Günlük ayrıntıya indiğimde yıl ve ayın yanında gün bilgisini de kullanırım. Bunun mümkün olması, satışları sipariş kalemi düzeyinde saklamamıza bağlı.

Data Mart: Belirli Bir Konuya Odaklanmak

Data mart, satış veya finans gibi belirli bir konuya odaklanan analitik veri kümesidir. Bir warehouse'ın ilgili bölümünden oluşturulabilir; ayrı bir depolama yapısı veya mantıksal veri modeli olarak sunulabilir. Her mart'ın mutlaka merkezi bir warehouse'dan gelmesi gerekmez.

Amaç, belirli kullanıcıların ilgilendiği verilere ve tanımlara daha kolay ulaşmasını sağlamaktır. Konuya odaklanmak karmaşıklığı azaltabilir; erişim güvenliğini ise ayrıca yetkilendirme kurallarıyla uygulamamız gerekir.

Bizim örnekte warehouse ileride satış dışındaki konuları da içerebilir. Satış ekibine fact_sales ve ilgili dimension'ları içeren bir satış mart'ı sunarak raporlarında kullanacakları modeli daraltabilirim. İlgisiz tabloları incelemek zorunda kalmazlar.

Diğer verilere erişmelerini engellemek istiyorsam gerekli izinleri de tanımlarım. Bir veri kümesine data mart adını vermek, tek başına güvenlik sınırı oluşturmuyor.

Ahmet'in siparişini uygulamada kaydetmekle başlayan yol, böylece satış raporuna ulaşıyor. İşlem sırasında gereken ayrıntıları OLTP tarafında yönettik; analize uygun modeli hazırlayıp OLAP sorgusuyla sonucu çıkardık.


Kendime Notlar

Bu konuları çalışırken OLTP, OLAP ve warehouse başta ayrı araç isimleri gibi görünüyordu. Aynı siparişi takip ettiğimde, bunların hangi ihtiyaca cevap verdiği daha anlaşılır hale geldi.

OLTP tarafında kısa süren okumalar ve değişiklikler var. OLAP tarafında ise birçok kaydı birlikte hesaplayıp sonucu farklı kırılımlarda inceliyoruz. Bu yüzden yalnızca “write için biri, read için diğeri” demek, iki taraftaki işlemleri açıklamaya yetmiyor.

Row-based ve column-based depolama, hangi alanlara nasıl eriştiğimi anlamama yardımcı oluyor. Normalization ve denormalization ise veriyi hangi ilişkilerle düzenlediğimle ilgili. Fiziksel depolamayla veri modelini ayrı düşündüğümde, tercihlerinin nedenlerini daha rahat takip edebiliyorum.

Warehouse kurarken veriyi taşımak kadar raporun anlamını belirlemek de gerekiyor. Ahmet'in satış anındaki fiyatını saklamasaydım yanlış tutar hesaplayabilirdim; aynı kalemi aktarımda iki kez ekleseydim toplamı şişirirdim. Raporun doğru olması, sorgudan önceki adımların da doğru olmasına bağlı.

Küçük bir uygulamada mevcut database üzerinde rapor çıkarmak yeterli olabilir. Analiz yükü, veri kaynakları ve tarihsel karşılaştırma ihtiyacı büyüdükçe ayrı bir warehouse'ın sağladığı düzen daha anlamlı hale geliyor.

Bir veri sistemiyle çalışırken önce hangi işlemi tamamlamak veya hangi soruyu cevaplamak istediğime bakmak istiyorum. Depolama biçimi, model ve aktarım kararları da bu sorunun ardından geliyor.

Top comments (0)