ODI Mapping’lerinde Sessiz Performans Tuzakları: Row-by-Row vs Set-Based
SQL tabanlı bir ELT (Extract- Load- Transform) tool’u olan ODI (Oracle Data Integrator) kullanarak birer mapping ve paket akışı…
ODI Mapping’lerinde Sessiz Performans Tuzakları: Row-by-Row vs Set-Based
SQL tabanlı bir ELT (Extract- Load- Transform) tool’u olan ODI (Oracle Data Integrator) kullanarak birer mapping ve paket akışı oluştururken nasıl daha performanslı SQL çıktıları yaratabileceğini biliyor musun?
Tabii ki set-based çalışma mantığını benimsemiş SQL’in suyuna giderek!
Merak ediyorsan içeriği okumaya devam et!
Konuda derinleşmeden evvel Row by Row ve Bulk (set-based) çalışma prensiplerine göz atalım.
Row by Row (RBAR — “Row By Agonizing Row”)
Her satırın tek tek işlenmesi demektir. 1 milyon satırlık bir tablo üzerinde işlem yaptığını varsayalım, bu 1 milyon ayrı işlem anlamına gelir. Database için tam bir işkence…
Nasıl çalışır?
Bu yaklaşımda işlem mantığı satır odaklı kurulur. Yani veri kümesi bir bütün olarak ele alınmaz; her kayıt ayrı ayrı okunur, değerlendirilir ve işlenir.
Teknik olarak bu genellikle FOR, WHILE ya da cursor gibi döngü yapılarıyla gerçekleştirilir.
Database önce bir satırı alır, ilgili iş kuralını o satır için çalıştırır, gerekiyorsa INSERT, UPDATE veya DELETE yapar, sonra bir sonraki satıra geçer.
Çoğu zaman her satır için ayrı bir işlem çağrısı ve hatta ayrı bir commit söz konusu olur. Bu da veri miktarı arttıkça işlem süresinin katlanarak büyümesine neden olur.
Artıları
Bu yaklaşımın tercih edilmesinin nedeni, özellikle ilk öğrenme aşamasında zihinsel olarak daha kolay olmasıdır.
Geliştirici “şu satır geldi, buna ne yapayım?” diye düşünür; bu da imperative (adım adım) düşünceye alışkın kişiler için daha sezgiseldir.
Ayrıca karmaşık iş kurallarını satır bazında uygulamak görece daha basittir. Yani kural ne kadar detaylı ve dallanmalıysa, bunu tek bir satır üzerinde tarif etmek ilk bakışta daha rahat hissettirebilir. Ancak bu kolaylık, büyük veri ve ölçeklenebilirlik söz konusu olduğunda ciddi performans ve bakım maliyetleriyle geri döner.
Eksileri
Row-by-row yaklaşımın en kritik dezavantajı, büyük veri hacimlerinde ciddi performans sorunlarına yol açmasıdır. Satır sayısı arttıkça maliyet doğrusal değil, pratikte katlanarak artar. Binlerce satırda fark edilmezken, yüz binlerce veya milyonlarca satırda sistem belirgin şekilde yavaşlar ve job süreleri kabul edilemez seviyelere çıkar.
Bunun temel nedeni, row-by-row işlemlerin database’in set-based (küme tabanlı) çalışma gücünü kullanamamasıdır. Modern ilişkisel veritabanları; optimizer, execution plan, join reordering, indeks kullanımı gibi mekanizmalarla büyük veri kümelerini tek seferde ve en verimli şekilde işlemek üzere tasarlanmıştır. Satır bazlı döngülerde ise bu mekanizmaların büyük kısmı devre dışı kalır ya da çok sınırlı çalışır. Database, “ne yapılacağını” optimize etmek yerine, geliştiricinin yazdığı adım adım talimatları birebir uygulamak zorunda kalır.
Bir diğer önemli problem de I/O ve context switch maliyetlerinin yükselmesidir. Her satır için yapılan SQL çağrıları, database engine ile procedural katman (örneğin PL/SQL veya ODI engine) arasında sürekli geçişlere neden olur. Bu geçişler CPU açısından pahalıdır ve disk I/O yükünü artırır. Sonuç olarak sistem kaynakları verimsiz kullanılır, aynı işi yapan set-based bir çözüme kıyasla çok daha fazla CPU zamanı ve bellek tüketilir. Bu yüzden row-by-row yaklaşım, özellikle DWH, BI ve ETL gibi yüksek hacimli veri işlenen ortamlarda teknik borç olarak görülür ve mümkün olduğunca kaçınılması gereken bir yöntemdir.
Bulk / Set-Based Processing
Verinin tamamını veya büyük bir kısmını tek seferde işlemek demektir.
Artıları
Set-based (bulk) yaklaşımda veriye tek tek satırlar üzerinden değil, bir bütün olarak kümeler üzerinden işlem uygulanır. Bu yaklaşımda geliştirici “şu satır geldiğinde ne yapmalıyım?” diye düşünmez; bunun yerine “bu koşulu sağlayan tüm kayıtlar için hangi dönüşüm uygulanmalı?” sorusuna odaklanır. Teknik olarak bu, INSERT INTO … SELECT, UPDATE … WHERE, MERGE gibi SQL ifadeleriyle veya PL/SQL tarafında BULK COLLECT ve FORALL gibi yapılarla gerçekleştirilir. Buradaki temel fikir, veriyi önce uygun şekilde filtrelemek, gruplayarak veya join’leyerek gerekli ara setleri üretmek ve ardından hedef tabloya tek seferde yazmaktır. Yani işlem akışı adım adım satır gezmek yerine, tanımlanan koşullara uyan tüm kayıtları kapsayan bir SQL ifadesi etrafında şekillenir.
Bu yaklaşımın en büyük avantajı performanstır. Çünkü database motoru set-based SQL’lerde tüm gücünü devreye sokar: optimizer execution plan üretir, join sırasını belirler, indeksleri etkin şekilde kullanır ve I/O maliyetini minimize eder. Sonuç olarak binlerce hatta milyonlarca satır, tek bir statement içinde çok daha kısa sürede işlenir. Ayrıca context switch (SQL ↔ PL/SQL geçişi), satır başına commit gibi maliyetler ortadan kalktığı için sistem kaynakları çok daha verimli kullanılır.
Bu yüzden DWH, ETL ve ODI gibi büyük hacimli veri işlenen ortamlarda set-based yaklaşım sadece bir tercih değil, fiilen bir standarttır.
Eksileri
Ancak set-based düşünmenin bir bedeli vardır: zihinsel olarak ilk etapta daha zorlayıcıdır.
Çünkü burada düşünme biçimi imperative (adım adım) değil, deklaratif bir yapıya kayar. Geliştirici artık “önce bunu yap, sonra şunu yap” demek yerine, “şu koşulu sağlayan kayıtlar şuna dönüşür” şeklinde düşünmek zorundadır.
Özellikle karmaşık iş kurallarında bu, kuralı parçalara ayırmayı, ara dataset’ler tasarlamayı ve bazen mapping’i birden fazla akışa bölmeyi gerektirir. Yani zorluk kod yazmaktan çok, doğru veri kümelerini tasarlamak noktasındadır. Bu zihniyet oturduğunda ise, hem performanslı hem de ölçeklenebilir çözümler üretmek çok daha doğal hale gelir.
Aşağıdaki tabloda Row by Row işleme ve Bulk (Set-Based) İşleme farkını tek bir bakışta görebilirsin.

RowByRow vs. Bulk
ODI’de Row by Row Tuzakları
1- Mapping Expression’da “satır mantığıyla” yazmak
Örnek olarak aşağıdaki kod bloğunda Case condition’ı içerisinde bir subquery yazılmış ancak bu subquery, condition ile eş zamanlı olarak tüm satırlar için ele alınacaktır. Yani her satıra tek tek gidip ilgili satırın amount bilgisini, orders tablosunda cust_id ile eşleşen tüm satırların amount tutarlarının ortalamasından büyük/küçük olmasına göre sınayacak. Bu yaklaşım Row by Row işleme için iyi bir örnektir.

row-by-row
Yukarıdaki kod bloğu için direkt olarak yanlış diyemeyiz ancak bu yapı bazen inline view olarak kalır. Optimizer her zaman istendiği gibi “flatten” edemeyebilir. ve mapping karmaşıklaştıkça okunabilirlik ve tuning zorlaşır
Aşağıdaki kod bloğunda ise aynı işlemin bulk (set-based) yaklaşıma uygun olarak düzenlenmiş halini görebilirsin.

bulk-setbased
Burada yapılan ise, aggregate işlemi önce ayrı bir yerde yapılır ardından join componentine bağlanarak veri akışına dahil edilir. Böylece orders tablosundaki amount ortalaması çoktan alınmış olup cust_id eşleşmesi olan kayıtlarda bir değerlendirme kıstası olarak ele alınır.
ODI’de Join/Expression component’i içinde subquery yazmak yerine:
- Subquery’yi ayrı bir component / flow olarak üretip
- Onu join ile bağlayarak optimizer’a daha temiz, daha optimize edilebilir bir SQL verebilirsin.
Buna benzer yaklaşımlar tuzaktır çünkü optimizer çoğu zaman bunu merge edemez, büyük tablolarda performans gömülür.
Altın kural: Expression içinde SELECT görüyorsan alarm çalsın.
2- Filter’da Function Kullanmak:
Bu örnekte order_date kolonu için TRUNC fonksiyonu uygulanıp TRUNC(SYSDATE) ‘e — yani sabit bir değer olan SYSDATE eşlenmek isteniyor.
Buradaki tuzak şudur:
TRUNC()kolonun üzerine uygulanıyor,- Database,
order_date’in ham değerini kullanamıyor, - Eğer
order_dateüzerinde index varsa Index kullanılamaz hale geliyor. ❗Çünkü index,TRUNC(order_date)değil,order_datetutar.
Sonuç:
- Database tüm tabloyu taramak zorunda kalır yani row by row tarayarak Full Table Scan yapar.

rowbyrow işleme
Aşağıdaki örnekte ise,
Kolona hiç dokunulmuyor. Function sadece sabit değere (SYSDATE) uygulanıyor veorder_date olduğu gibi bırakılıyor.
Bu ne sağlar?
- Database
order_dateindex’ini kullanabilir. - Index Range Scan yapar yani sadece ilgili tarih aralığını okur.
Sonuç:
- Çok daha az I/O
- Çok daha hızlı sorgu
- Set-based düşünceye tam uyum sağlanır.

bulk yaklaşım
Somutlaştırmak gerekirse:
Doğru olan, kitaplıktaki her kitabı açıp “bugün basılmış mı?” diye bakmak değil, “Bugün basılan kitaplar şu raf aralığında” deyip sadece oraya bakmaktır. 📖
3. Knowledge Module’ü “fark etmeden” row-by-row’a sokmak
ODI’de mapping’i ne kadar doğru ve set-based tasarlarsan tasarla, Knowledge Module (özellikle IKM) yanlış kurgulanmışsa tüm yükleme süreci gizlice row-by-row’a dönebilir.
Bu genellikle geliştiricinin niyeti olmadan olur; mapping çalışır, veri doğru gelir ama performans beklendiği gibi değildir.
Bunun en yaygın nedeni, IKM içinde insert sonrası (post-insert) satır bazlı güncellemeler yapılmasıdır.
Nasıl?
Örneğin target tabloya veri insert edildikten sonra, her satır için ayrı ayrı UPDATE çalıştıran bir IKM düşünelim.
Burada problem şudur: database artık bir küme üzerinde değil, her bir kayıt için ayrı execution path çalıştırmak zorunda kalır. Satır sayısı büyüdükçe bu yapı doğrusal değil, pratikte katlanarak yavaşlar.
Eğer IKM içinde cursor, loop veya “for each row” mantığı varsa, mapping’in set-based avantajı tamamen ortadan kalkar. Loop içinde commit yapılması ise durumu daha da ağırlaştırır; çünkü transaction yönetimi her satırda yeniden devreye girer.
Bu noktada kırmızı bayraklar genelde şunlardır:
- IKM içinde açıkça cursor veya loop görülmesi,
- Insert’ten sonra satır bazlı
UPDATEblokları, - Döngü içinde commit kullanımı
Doğru yaklaşım, işi tek SQL ifadesiyle çözen bir Knowledge Module kullanmaktır.
Özellikle MERGE tabanlı IKM’ler bu yüzden profesyonel ortamlarda standart kabul edilir. MERGE sayesinde insert ve update mantığı satır satır değil, tek bir execution plan üzerinden set-based şekilde çalışır. Database optimizer devreye girer, join sırasını ve erişim yollarını kendi belirler. Sonuç olarak hem performans artar hem de veri hacmi büyüdükçe job süresi stabil kalır. Buradaki fark iş kuralında değil, execution modelindedir.
4. ODI’de “lookup = row-by-row” sanmak
ODI’de çok yaygın ama yanlış bir inanış vardır: “Lookup kullanıyorsan row-by-row çalışıyorsundur.” Bu doğru değildir.
Lookup’ın row-by-row ya da bulk olması, nasıl kurgulandığına bağlıdır. Yanlış kurgulanan lookup’lar mapping’i sessizce satır bazlı çalışmaya zorlar; doğru kurgulananlar ise tamamen set-based’tir.
Yanlış kullanım genellikle lookup logic’inin expression içinde yazılmasıyla başlar. Bu durumda ODI, her satır için lookup sorgusu üretmek zorunda kalır. Eğer lookup cache kapalıysa veya eşleşilen kolonlar indeksli değilse, database her satırda aynı tabloyu tekrar tekrar tarar. Özellikle çok kolonlu eşleşmelerde ve büyük referans tablolarında bu durum ciddi performans sorunlarına yol açar. Mapping sade görünür ama arka planda binlerce küçük sorgu çalışır.
Bu senaryoda sık görülen problemler şunlardır:
- Lookup’ın expression içinde çağrılması,
- Cached lookup’ın kapalı olması,
- Lookup yapılan kolonlarda indeks olmaması
Doğru kullanımda ise lookup, satır kontrolü gibi değil, iki veri kümesinin join edilmesi gibi ele alınır. Yani lookup için ayrı bir flow oluşturulur, bu flow mapping seviyesinde join component’i ile ana akışa bağlanır. Gerekirse temp veya staging tablolar kullanılır. Bu yaklaşımda lookup artık “her satıra sorulan bir soru” değil, önceden üretilmiş bir dataset haline gelir. Cache açık ve indeksler doğruysa, database bu join’i tek seferde, set-based şekilde çözer. Performans açısından klasik bir join’den farkı kalmaz.
Özetle burada fark lookup kullanıp kullanmamakta değil; lookup’ı satır mantığıyla mı yoksa küme mantığıyla mı düşündüğündedir. ODI’de iyi tasarlanmış bir lookup, row-by-row değil, tamamen bulk çalışır ve mapping’in ölçeklenebilirliğini korur.
Nasıl Bulk (Set — Based ) Kurgularsın?
1 * Kuralı Parçala
Aşağıdaki örneği ele alalım,
“Bir müşteri son 30 günde 3’ten fazla sipariş verdiyse ve toplam tutar ortalamanın üzerindeyse VIP”
2* Kümeleri Yarat
Common Table Expression (CTE) ile yazılmış bu sorguda işi satırda değil direkt olarak database üzerinde çözüyoruz:

3 * Target’a Bas
İş kuralını set-based şekilde çözdükten sonra, ortaya çıkan sonucu target tabloya da yine set-based bir yöntemle yazmak gerekir.
Insert, “Bu koşulları sağlayan tüm kayıtları al ve target tabloya tek seferde ekle.” mantığıyla satır satır insert yok.

Merge ise INSERT + UPDATE ihtiyacını aynı anda ve set-based şekilde çözer.
“Source set ile target tabloyu karşılaştır; eşleşenleri güncelle, eşleşmeyenleri ekle.” şeklinde bir kurgu yaratmaya çalışırken aşağıdaki şekilde bir yapı ile,

Tek SQL ve tek execution plan ile ne yapacağına database kendi karar verir: hangisi insert, hangisi update…
ODI’de:
- Incremental load
- Slowly Changing Dimension (SCD)
- Upsert senaryoları neredeyse standart çözüm MERGE’dür.
Bu Yapı ODI’de Nasıl Yaratılır?
- Orders → Filter (son 30 gün)
- Aggregate → count, sum
- Set-based avg flow
- Join
- Expression → CASE
- Target’a bas.
Özetlemek gerekirse, ODI’de performansın anahtarı, satır satır (row-by-row) değil set-based / bulk düşünmektir. Lookup, subquery veya expression içinde yapılan satır bazlı işlemler veri büyüdükçe ciddi performans sorunlarına yol açar. Doğru yaklaşım; iş kurallarını join, filter, aggregate ve CASE gibi SQL set mantığıyla çözmek ve ortaya çıkan sonucu target tabloya INSERT SELECT, UPDATE JOIN veya MERGE gibi tek seferlik işlemlerle yazmaktır. Cache gibi mekanizmalar tasarımı kurtarmak için değil, doğru tasarlanmış bir akışı optimize etmek için kullanılmalıdır.
İçeriği beğendiysen alkış bırakmayı, sorularını ya da görüşlerini yorumlarda belirtmeyi unutma.
Bana ulaşmak için: 🌸
메타데이터
- post_id
- 12040a5688d0
- slug
- odi-mappinglerinde-sessiz-performans-tuzakları-row-by-row-vs-set-based-12040a5688d0
- url
- https://medium.com/@asmahazalnur/odi-mappinglerinde-sessiz-performans-tuzaklar%C4%B1-row-by-row-vs-set-based-12040a5688d0
- canonical_url
- https://medium.com/@asmahazalnur/odi-mappinglerinde-sessiz-performans-tuzaklar%C4%B1-row-by-row-vs-set-based-12040a5688d0
- author_url
- https://medium.com/@asmahazalnur
- status
- ok
- fetched_at
- 2026-06-12 18:14:10