İçeriğe geç
wedevit

4 Eylül 2026 · 9 dk okuma · yazılım

İlhan Buğra Aslan

Her rapor başka sayı veriyor: metrik tanımı, kayıt kaynağı ve raporlama katmanı


Aynı soruya üç sistem üç farklı cevap veriyorsa ilk akla gelen açıklama genelde yanlıştır: veri kaybolmuyor. Ağustos ayı için e-ticaret paneli 1.284 sipariş, ERP 1.301, muhasebe 1.259 gösteriyorsa üçünün de kendi içinde doğru olma ihtimali yüksek, çünkü üçü aynı kelimeyle farklı şeyleri sayıyor. Raporlarda tutarlılık dört şeyle kurulur: her metriğin yazılı bir tanımı ve tek bir sahibi olması, her alanın tek bir kayıt kaynağından gelmesi, raporun operasyonel tablolar yerine yeniden hesaplanabilir bir katmanda üretilmesi ve her gün kendiliğinden çalışan bir mutabakat kontrolü. Sıra da tam olarak bu. Tanımı netleştirmeden altyapı değiştirmek farkı yok etmez, sadece gizler.

"Ciro" tek bir sayı değil

Shopify'ın kendi raporlarına bakmak bu karmaşayı görmenin en hızlı yolu. Brüt satış (gross sales), iade ve iskonto düşülmeden, vergi ve kargo hariç birim fiyat çarpı adet olarak hesaplanır. Net satış brüt satıştan iskonto ve iadelerin düşülmesiyle bulunur. Toplam satış (total sales) ise net satışın üzerine vergi, kargo ve ek ücretlerin eklenmesiyle çıkar. Üç sayı, aynı ekranda, üçü de doğru. Bir iade standart raporlarda net satışı düşürür, brüt satışı düşürmez.

Buna bir de "hangi güne yazılır" sorusu ekleniyor. Haziranda verilen bir sipariş temmuzda iade edildiyse, iade haziranın cirosundan mı düşülür yoksa temmuza eksi satır olarak mı yazılır? İki uygulamanın ikisi de savunulabilir, ama biri muhasebeyle, diğeri pazarlama raporuyla uyuşur. Bu tercihi yazmadıysanız ay sonunda iki ekip aynı veriden farklı sonuç çıkarır ve tartışma her ay baştan başlar.

Metrik tanımını panonun içine gömmeyin

Kurumsal ekiplerin bu iş için ayrı bir katmanı var. dbt Semantic Layer'ı besleyen MetricFlow, metrik tanımlarını YAML dosyalarında, sürüm kontrolü altında ve soyağacı takip edilebilir biçimde tutar; panolar formülü kendi içinde saklamaz, tanımı bu katmandan sorar. MetricFlow 2025 sonunda Apache 2.0 lisansıyla açık kaynak oldu. Benzer işi Cube ve Looker'ın LookML'i de yapıyor.

Küçük bir ekipsiniz diye bu fikri atlamanız gerekmiyor, çünkü asıl mesele araç değil, tanımın kaç kopyası olduğu. Tanımı tek bir SQL görünümünde ya da tek bir modelde yaşatın, bütün panolar oradan beslensin. Bir formülü panonun içine yazdığınız anda kopyalanmaya başlar: altı ay sonra aynı ciro formülü altı panoda durur, beşi güncellenir, biri kalır ve o bir tanesi genelde yönetime giden pano olur. Metrik sözlüğü de karmaşık olmak zorunda değil; her satırda ad, iş dilinde tanım, formül, hangi kayıtların hariç tutulduğu, hangi tarih alanının kullanıldığı, saat dilimi, güncellenme sıklığı ve sahibin adı olsun.

Hangi tarihi soruyorsunuz

Bir siparişin tek bir tarihi yok. Oluşturma zamanı, ödeme onayı, fatura tarihi, sevk tarihi ve teslim tarihi ayrı alanlardır ve aynı sipariş bunlara göre beş farklı güne düşebilir. Muhasebe fatura tarihine bakar, pazarlama sipariş tarihine, operasyon sevke, finans banka hesabına geçme gününe. Dördü de kendi işine göre haklı.

Pratik kural: her rapor tek bir alanı "olay tarihi" olarak ilan etsin ve bu, rapor başlığının yanında yazılı olsun. Diğer tarihler kolon olarak dursun, gruplama ölçütü olmasın. İki raporu karşılaştırmadan önce sorulacak ilk soru hangi metriği ölçtüğü değil, hangi tarih alanına göre grupladığıdır. Deneyimde farkın büyük kısmı burada çıkar.

Gününüz saat 03.00'te başlıyor olabilir

Türkiye 7 Eylül 2016 tarihli kararla yıl boyu UTC+3 saat diliminde kaldı, yaz saati uygulaması yok. Bu, sabit bir fark olduğu için hayatı kolaylaştırıyor, ama tek bir tuzağı var: sunucunuz tarihleri UTC'ye göre grupluyorsa raporun "dün" dediği aralık yerel saatle 03.00 ile 03.00 arasıdır. Gece yarısı ile sabah üç arasında gelen siparişler bir önceki güne yazılır. Kampanya bitiş gecelerinde bu fark tek başına raporu tartışmalı hale getirir.

Aynı sorunun tanınmış bir örneği Google Analytics 4'te duruyor. GA4'te event_date alanı property'nin saat diliminde, event_timestamp ise UTC cinsindendir; BigQuery aktarımını dönüştürmeden sorgularsanız arayüzle farklı bir 24 saatlik pencereyi karşılaştırırsınız ve günlük sayılar hiçbir zaman birebir oturmaz. Doğru kurgu şu: veriyi UTC sakla, dönüşümü raporlama katmanında tek bir yerde yap, kullandığınız saat dilimini panonun üstüne yaz. Avrupa'ya satış yapıyorsanız karşı tarafta yaz saati uygulaması olduğunu ve mart ile ekim aylarında günlük toplamların kaydığını da hesaba katın.

Her alanın tek bir kayıt kaynağı olmalı

Stok miktarı nerede doğrudur, ERP'de mi e-ticarette mi? Müşterinin güncel adresi CRM'de mi, sipariş kaydında mı? Bu soruların cevabı alan bazında ve yazılı olmalı. İki sistem aynı alanı bağımsız olarak yazabiliyorsa elinizdeki bir raporlama sorunu değil, veri sahipliği sorunudur ve rapor katmanında çözülemez. Sistemler arası akışın nasıl kurulacağı, sıra ve tekrar garantileriyle birlikte ERP ve e-ticaret veri senkronizasyonu yazısında duruyor.

Rapor sorgusunu canlı veritabanında koşturmayın

Aylık kırılım çıkaran bir sorgu milyonlarca satır tarar, uzun sürer ve aynı veritabanı müşteri isteklerini karşılıyorsa yavaşlama doğrudan siteye yansır. Standart çözüm okuma replikası, ama replika taze veri sözü vermez. PostgreSQL'de gecikmeyi now() - pg_last_xact_replay_timestamp() ile ölçebilirsiniz; bu ölçüm ana sunucuda işlem olmayan sakin dönemlerde olduğundan kötü görünür, o yüzden LSN farkına (pg_wal_lsn_diff) bakmak daha güvenilirdir.

Bunun raporlamaya yansıması net: bir kullanıcı sipariş kaydettikten hemen sonra replikadan beslenen panoyu açarsa siparişini göremeyebilir ve raporun bozuk olduğunu düşünür. Çözüm gecikmeyi sıfırlamaya çalışmak değil, beklentiyi yazmak. Panonun köşesine "veri 09.15 itibarıyla" yazan bir satır, ay boyunca sorulacak soruların önemli kısmını ortadan kaldırır. Sorgunun kendisini hızlandırmak ayrı bir başlık; indeks ve sorgu planı tarafı için veritabanı yavaşlığı yazısına bakabilirsiniz.

Geçen ayın raporu neden değişti

Bu soru neredeyse her zaman aynı sebepten gelir: rapor, bugünün verisiyle geçmişi yeniden anlatıyor. Müşterinin segmenti "bireysel"den "kurumsal"a çevrildiğinde, geçmiş siparişleri de kurumsal görünür ve haziran raporundaki kurumsal ciro geriye dönük büyür. Fiyat listesi güncellendiğinde eski siparişlerin tutarı yeni fiyatla hesaplanıyorsa aynı şey olur.

İki alışkanlık bunu bitirir. Birincisi, işlem satırına o anki değerleri kopyalamak: birim fiyat, uygulanan iskonto, vergi oranı, döviz kuru sipariş satırında yazılı durmalı, cari listeden okunmamalı. İkincisi, değişen özellikleri versiyonlamak. Veri ambarı literatüründeki SCD Type 2 deseni tam bunu yapar: müşteri segmenti değiştiğinde satır güncellenmez, yeni bir satır eklenir; her satırın geçerlilik başlangıcı, bitişi ve "güncel kayıt" işareti olur. Rapor, olayın tarihindeki geçerli satırla eşleşir ve geçmiş sabit kalır.

Geç gelen veri: politikayı siz seçin

İade yirmi gün sonra girer, banka ekstresi iki gün sonra gelir, kargo teslim durumu dört gün sonra netleşir. Yani dünün sayısı bugün, bir hafta sonra ve ay sonunda farklı olacak. Burada iki meşru politika var ve seçmediğiniz sürece herkes kendi politikasını varsayar.

Birincisi kayan yeniden hesaplama: rapor son 30 günü her gece baştan hesaplar, geçmiş sayılar değişebilir, bu davranış panoda yazılıdır. İkincisi dönem kapatma: ay kapandıktan sonra o dönemin sayıları dondurulur, sonradan gelen düzeltmeler içinde bulunulan döneme ayrı satır olarak yazılır. Bu, muhasebenin onlarca yıldır yaptığı şeydir ve yönetim raporlaması için genelde daha sağlıklıdır, çünkü bir kere sunulan sayı bir daha değişmez. Hangisini seçtiğinizi metrik sözlüğüne yazın; asıl hata iki politikayı aynı şirkette yan yana çalıştırmak.

Yükleme işi iki kez çalışırsa satır iki katına çıkmasın

Raporlama tablolarını besleyen gece işleri elle tetiklenir, yarıda kalır, yeniden denenir. Bu yüzden yükleme işinin aynı gün için iki kez çalışması veriyi bozmamalı: hedef tabloda o günün bölümünü silip yeniden yazın ya da doğal anahtar üzerinde upsert kullanın, sonuna satır eklemekle yetinmeyin. Yarım kalan yükleme de yarım gün bırakmamalı; veriyi geçici bir tabloya yazıp iş bittiğinde takas etmek en basit korumadır. Yeniden deneme ve idempotency mekaniğinin tamamı arka plan işleri ve kuyruk yazısında var.

Mutabakatı insanın gözüne bırakmayın

Farkı ay sonunda bir muhasebecinin fark etmesi, en pahalı tespit yöntemidir. Her gün çalışan otomatik bir kontrol koyun ve sırayı bozmayın: önce aynı granülerlikte satır sayısını karşılaştırın, sonra toplamları. Yalnızca toplamı karşılaştırıp eşit görmek yanıltıcıdır, çünkü iki ters yönlü hata birbirini götürebilir. Eşik üstü sapmada uyarı bir kişiye değil bir kanala gitsin.

dbt kullanıyorsanız hazır malzeme fazla: unique, not_null, relationships ve accepted_values generic testleri, dbt-utils'teki equal_rowcount, kaynak tazelik kontrolü. Bir raporu yeniden yazarken ya da eski sistemden taşırken dbt-audit-helper paketindeki compare_queries makrosu iki sorguyu satır satır karşılaştırır ve yalnızca birinci tarafta olan, yalnızca ikinci tarafta olan ve iki tarafta aynı olan satır sayılarını özet olarak döner. Bu paketin varlık sebebi de zaten bu: bir modeli yenilerken çıktının eskisiyle birebir aynı kaldığını kanıtlamak. dbt'niz yoksa aynı işi iki sorgu, bir karşılaştırma ve bir uyarı yapar; önemli olan bunun her gün kendiliğinden koşması.

Erişimi de raporlama tarafında düşünün

Raporlama kopyası, uygulamanın yetki kurallarını yanında getirmez. Uygulamada bir satış temsilcisi sadece kendi müşterilerini görürken, aynı kişi rapor aracında bütün müşteri listesini indirebiliyor olabilir. Bir Excel dışa aktarma, uygulamada aylarca uğraşarak kurduğunuz yetki modelini tek tıkla aşar. Rapor ortamına ayrı roller tanımlayın, mümkün olan yerde satır düzeyinde kısıtlama uygulayın ve dışa aktarmaları kayıt altına alın. Yetkilendirme modelini kurma tarafı için roller, RBAC ve ABAC yazısı işinizi görür.

Veri ambarı ne zaman gerekir

Sırayı atlamayın. Çoğu şirket için ilk doğru adım okuma replikası üzerinde ayrı bir raporlama şeması ve birkaç materialized view'dur. PostgreSQL'de bir noktaya dikkat edin: REFRESH MATERIALIZED VIEW CONCURRENTLY çalışabilmesi için görünümde bütün satırları kapsayan, ifade ya da WHERE koşulu içermeyen bir tekil indeks bulunması gerekir. Bu indeks yoksa yenileme sırasında görünüm kilitlenir ve pano tam o dakikada bekler.

Ambar kararını dört göstergeye bağlayın: kaynak sistem sayısının üçü dördü aşması, geçmişi versiyonlama ihtiyacı, ham olay verisinin operasyonel veritabanında taşınamayacak hacme gelmesi ve analistlerin geliştirici beklemeden SQL yazması gerekliliği. Bunlardan hiçbiri yoksa ambar, çözdüğünden fazla iş çıkarır. Şunu da baştan bilin: ambar tanım sorununu çözmez, sadece taşır. Tanımı yazmadan kurulan ambarın ilk çıktısı, panolara bir de dördüncü farklı sayı eklemek olur.

Bu haftaya sığan ilk adım

En çok tartışılan üç sayıyı seçin. Her biri için tek sayfaya formülü, hangi tarih alanına göre gruplandığını, saat dilimini, hariç tutulan kayıtları (test siparişleri, iptaller, iç hesaplar) ve sahibinin adını yazın. Sonra aynı dönemi iki sistemden aynı granülerlikte çekin, mümkünse gün ve sipariş kimliği bazında, ve farkları satır olarak listeleyin.

İlk yirmi farkı üç kutuya ayırın: tanım farkı, zaman sınırı farkı, gerçek veri kaybı. Pratikte ilk iki kutu farkların büyük bölümünü alır ve bunlar yazılımla değil bir sayfa doküman ve bir sorgu düzeltmesiyle çözülür. Üçüncü kutuda kalan az sayıda satır ise gerçek hatadır ve entegrasyon tarafında aranması gerekir. Bu ayrımı yapmadan başlanan projelerde çoğu bütçe, aslında olmayan bir veri kaybını aramaya gider.


Bu konuda yardıma mı ihtiyacınız var?

iletişime geçtüm yazılar