Outbox Pattern Nedir ve Hangi Sorunu Çözer?

Outbox Pattern, bir veritabanı işlemiyle bir mesaj yayınını atomik hale getirerek dağıtık sistemlerde mesaj kaybını önleyen bir mimari desendir. Doğrudan yanıt: bir servis hem veritabanına yazıp hem mesaj kuyruğuna mesaj göndermek istediğinde, bu iki işlem ayrı sistemlerde olduğu için aynı anda başarılı olacaklarının garantisi yoktur. Veritabanı yazılır ama mesaj gönderilemezse veri tutarsız kalır; mesaj gönderilir ama veritabanı geri alınırsa hayalet mesaj oluşur. Outbox Pattern, mesajı aynı veritabanı transaction’ı içinde bir “outbox” tablosuna yazar; ayrı bir süreç bu tabloyu okuyup mesaj broker’a iletir. Böylece iş verisi ve mesaj ya birlikte var olur ya birlikte yok olur.

Bu sorun “dual write problem” (çifte yazma problemi) olarak bilinir ve mikroservis mimarilerinin en yaygın güvenilirlik tuzaklarından biridir. Dağıtık sistemlerde iki ayrı kaynağa atomik yazma yapmak, dağıtık transaction (2PC) olmadan imkansızdır; 2PC ise ölçeklenebilirlik ve performans açısından genellikle kabul edilemez. Outbox Pattern, tek bir yerel transaction kullanarak bu problemi pratik biçimde çözer. Bu desen sık sık olay güdümlü mimarilerde, özellikle idempotent uç noktalarla birlikte ele alınır.

Outbox Pattern'de iş tablosu ve outbox tablosuna aynı transaction içinde yazımı gösteren atomik akış diyagramı
Outbox Pattern'de iş tablosu ve outbox tablosuna aynı transaction içinde yazımı gösteren atomik akış diyagramı

Çifte Yazma Problemi ve Başarısızlık Senaryoları

Outbox Pattern’in neden gerekli olduğunu anlamak için naif yaklaşımın başarısızlık senaryolarını görmek gerekir. Aşağıdaki tablo iki ayrı işlemi koordine etmeye çalışan kodun karşılaşabileceği sonuçları gösterir.

Senaryo DB Yazma Mesaj Yayını Sonuç Etki
İdeal Başarılı Başarılı Tutarlı Sorun yok
Broker çökmesi Başarılı Başarısız Mesaj kaybı Tüketici bilgilenmez
DB rollback Geri alındı Başarılı Hayalet mesaj Olmayan olay yayılır
Servis çökmesi (arada) Başarılı Hiç denenmedi Mesaj kaybı Sessiz tutarsızlık
Ağ kopması Başarılı Belirsiz Çift mesaj riski Tekrar işleme

Bu senaryolar gösteriyor ki “önce DB’ye yaz sonra mesaj gönder” yaklaşımı, sistemin herhangi bir noktada çökmesi durumunda veri ile mesaj arasında kalıcı tutarsızlık bırakır. Mesaj sıralamasını değiştirmek (önce mesaj sonra DB) sorunu çözmez, yalnızca hata türünü değiştirir. Tek güvenilir çözüm, mesajı iş verisiyle aynı transaction’a dahil etmektir; Outbox Pattern tam olarak bunu yapar.

Bu tutarsızlıkların en tehlikeli yanı sessiz olmalarıdır. Bir mesaj kaybolduğunda sistem hata fırlatmaz; basitçe alt tarafdaki tüketici olaydan haberdar olmaz. Örneğin bir e-ticaret sisteminde sipariş veritabanına yazılır ama “sipariş oluşturuldu” olayı yayınlanamazsa, depo yönetim sistemi siparişi hiç görmez ve ürün sevk edilmez. Müşteri ödemeyi yapmıştır ama siparişi hareketsiz kalır. Bu tür hatalar genellikle aylar sonra, manuel mutabakat sırasında veya müşteri şikayetiyle fark edilir; o noktada kök neden analizi çok zorlaşır çünkü kayıp mesajın izi yoktur. Outbox Pattern bu sessiz kayıpları yapısal olarak imkansız kılar: mesaj iş verisiyle aynı transaction’da kalıcı olduğundan, veri varsa mesaj da kesinlikle vardır ve er ya da geç iletilir.

Çifte yazma probleminde DB başarılı ama broker çöktüğünde oluşan mesaj kaybını gösteren başarısızlık senaryosu
Çifte yazma probleminde DB başarılı ama broker çöktüğünde oluşan mesaj kaybını gösteren başarısızlık senaryosu

Outbox Pattern Nasıl Çalışır: Adım Adım Mimari

Outbox Pattern üç temel bileşenden oluşur: iş tablosu, outbox tablosu ve mesaj iletici (message relay). İş verisi ve giden mesaj aynı yerel transaction içinde yazılır; ayrı bir süreç outbox tablosunu okuyup broker’a iletir ve iletilen mesajları işaretler.

Desenin zarafeti, atomiklik garantisini veritabanının kendisine devretmesindedir. İş verisi ile outbox kaydı aynı yerel transaction içinde yazıldığından, veritabanının ACID garantileri ikisinin birlikte commit edilmesini veya birlikte geri alınmasını sağlar. Burada hiçbir dağıtık koordinasyon yoktur; her şey tek bir veritabanı içinde, kanıtlanmış transaction mekanizmasıyla gerçekleşir. Mesajın broker’a iletilmesi ise asenkron olarak, transaction’dan tamamen ayrı bir süreçte yapılır. Bu ayrışma kritiktir: iş işlemi, broker’ın o anda erişilebilir olup olmadığından bağımsız olarak tamamlanır. Broker geçici olarak çökmüş olsa bile iş verisi ve mesaj outbox’ta güvenle durur; broker geri geldiğinde relay birikmiş mesajları iletir. Böylece sistem, broker kesintilerine karşı dayanıklı hale gelir ve hiçbir olay kaybolmaz.

  1. Uygulama bir transaction başlatır.
  2. İş verisini iş tablosuna yazar (örn. sipariş kaydı).
  3. Aynı transaction içinde outbox tablosuna giden mesajı ekler.
  4. Transaction commit edilir; her iki yazma da atomik biçimde kalıcı olur.
  5. Ayrı bir relay süreci outbox’taki işlenmemiş mesajları okur.
  6. Mesajı broker’a (Kafka, RabbitMQ) yayınlar.
  7. Başarılı yayın sonrası mesajı “işlendi” olarak işaretler veya siler.

Mesaj iletici iki şekilde uygulanabilir. Polling Publisher outbox tablosunu düzenli aralıklarla sorgular; basit ama veritabanına yük bindirir ve gecikme ekler. Transaction Log Tailing (CDC) veritabanının değişiklik günlüğünü (WAL/binlog) izler; daha verimli ve düşük gecikmelidir. Debezium Outbox Event Router CDC tabanlı iletim için endüstri standardı bir araçtır ve PostgreSQL, MySQL gibi veritabanlarının değişiklik akışını Kafka’ya bağlar.

Outbox tablosunun şema tasarımı, desenin güvenilirliği için kritiktir. Aşağıdaki tablo tipik bir outbox kaydının zorunlu alanlarını ve amacını gösterir.

Alan Tip Amaç Zorunlu Not
id UUID Benzersiz mesaj kimliği Evet Tüketici deduplikasyonu
aggregate_type String Olay türü/konu Evet Yönlendirme için
payload JSON Mesaj gövdesi Evet Serileştirilmiş olay
status Enum İşlem durumu Evet bekliyor/iletildi
created_at Timestamp Sıralama + temizlik Evet İndeksli
retry_count Integer Deneme sayacı Hayır DLQ tetiği
Relay sürecinin outbox tablosunu okuyup Kafka'ya mesaj yayınlamasını gösteren message relay mimarisi
Relay sürecinin outbox tablosunu okuyup Kafka'ya mesaj yayınlamasını gösteren message relay mimarisi

İletim Stratejileri: Polling vs CDC

Mesaj iletim mekanizmasının seçimi, sistemin gecikme, karmaşıklık ve veritabanı yükü dengesini belirler. Aşağıdaki tablo iki ana yaklaşımı karşılaştırır.

Kriter Polling Publisher Transaction Log Tailing (CDC)
Gecikme Polling aralığı kadar (sn) Çok düşük (ms)
DB yükü Yüksek (sürekli sorgu) Düşük (log okuma)
Kurulum karmaşıklığı Düşük Orta-yüksek
Ek altyapı Gerekmez Debezium/Connect gerekir
Ölçeklenebilirlik Orta Yüksek
Sıralama garantisi Zayıf Güçlü (log sırası)

Düşük hacimli ve gecikmeye duyarlı olmayan sistemler için Polling Publisher yeterlidir ve ek altyapı gerektirmez. Yüksek hacim, düşük gecikme ve güçlü sıralama garantisi gereken sistemlerde CDC tabanlı yaklaşım tercih edilir. Apache Kafka dokümantasyonu ve microservices.io Transactional Outbox tanımı bu desenin kanonik referanslarıdır. Sistemin uçtan uca güvenilirliğini ölçmek için gözlemlenebilirlik pratikleri burada da değerlidir.

İki iletim stratejisi arasındaki seçim, çoğunlukla ekibin mevcut altyapısına ve gecikme toleransına bağlıdır. Polling yaklaşımının cazibesi sadeliğindedir: bir zamanlayıcı görevi, outbox tablosunda işlenmemiş kayıtları belirli aralıklarla sorgular ve yayınlar. Tek ek bileşen bu zamanlayıcıdır; yeni bir altyapı, yeni bir operasyon yükü yoktur. Dezavantajı, polling aralığı kadar bir gecikme eklemesi ve sürekli sorgularla veritabanına yük bindirmesidir. CDC yaklaşımı ise veritabanının yazma-önce günlüğünü (WAL) okuyarak değişiklikleri neredeyse anlık yakalar; gecikme milisaniyeye iner ve veritabanına ek sorgu yükü binmez. Bedeli, Debezium ve Kafka Connect gibi bileşenlerin kurulması ve işletilmesidir. Pratik bir öneri, sistemin Polling ile başlaması ve hacim arttıkça CDC’ye geçmesidir; bu, erken aşamada gereksiz karmaşıklıktan kaçınır.

İdempotentlik ve En-Az-Bir-Kez Teslimat

Outbox Pattern “en az bir kez” (at-least-once) teslimat garantisi sağlar; yani bir mesaj birden fazla kez iletilebilir. Relay süreci mesajı yayınladıktan sonra ama işaretlemeden önce çökerse, aynı mesaj tekrar yayınlanır. Bu nedenle tüketici tarafının idempotent olması zorunludur: aynı mesajı iki kez işlemek tek kez işlemekle aynı sonucu vermelidir.

Bu noktada dağıtık sistemlerin temel bir gerçeğiyle yüzleşmek gerekir: ağ üzerinden tam-bir-kez (exactly-once) teslimat, genel durumda matematiksel olarak imkansızdır. Bir mesaj gönderildikten sonra alındığına dair onayın kaybolması, göndericiyi tekrar göndermeye zorlar; gönderici ise mesajın gerçekten ulaşıp ulaşmadığını asla kesin bilemez. Pratik mühendislik çözümü, en-az-bir-kez teslimatı tüketici idempotentliğiyle birleştirerek tam-bir-kez işleme (exactly-once processing) etkisini elde etmektir. Yani mesaj birden çok kez gelse de etki bir kez gerçekleşir. Bu, dağıtık sistemlerde güvenilirliğin temel reçetesidir ve Outbox Pattern bu reçetenin gönderim ayağını sağlam biçimde garanti eder.

İdempotentlik genellikle her mesaja benzersiz bir kimlik (message ID veya idempotency key) eklenerek sağlanır. Tüketici işlediği mesaj kimliklerini saklar ve daha önce gördüğü bir kimliği tekrar işlemez. Bu desen, idempotent API tasarımıyla doğrudan ilişkilidir ve birlikte uygulandığında mesaj kaybı ve mükerrer işleme risklerinin her ikisini de ortadan kaldırır. Outbox tablosuna yazılan her mesaja eklenen benzersiz kimlik, hem tüketici idempotentliğini hem de denetlenebilirliği sağlar.

Tüketici tarafında deduplikasyonun pratik uygulaması bir tasarım kararı gerektirir: işlenmiş kimliklerin nerede ve ne kadar süre saklanacağı. En basit yaklaşım, işlenen her mesaj kimliğini bir veritabanı tablosuna birincil anahtar olarak yazmaktır; aynı kimlik ikinci kez geldiğinde benzersizlik ihlali tetiklenir ve mesaj atlanır. Bu, idempotentliği veritabanı kısıtına devrettiği için son derece güvenilirdir. Ancak bu tablo da sınırsız büyüyemez; işlenmiş kimlikler için bir saklama penceresi (örneğin son 7 gün) tanımlanır ve eski kayıtlar temizlenir. Alternatif olarak Redis gibi bir TTL’li anahtar deposu kullanılabilir; bu daha hızlıdır ama kalıcılık garantisi veritabanı kadar güçlü değildir. Yüksek hacimli sistemlerde bu deduplikasyon deposu kendisi bir darboğaz haline gelebileceği için, mesaj kimliğinin doğal bir iş anahtarıyla (örneğin sipariş numarası) ilişkilendirilmesi ve idempotentliğin iş mantığına gömülmesi daha ölçeklenebilir bir çözüm sunar.

Hedef mesaj broker’ının seçimi, Outbox Pattern’in iletim katmanını ve teslimat garantilerini etkiler. Aşağıdaki tablo yaygın broker seçeneklerini karşılaştırır.

Broker Teslimat Garantisi Sıralama Veri Saklama Tipik Hacim
Apache Kafka En az bir kez / tam bir kez Partition içi güçlü Uzun (log) Çok yüksek
RabbitMQ En az bir kez Kuyruk içi Tüketince siler Yüksek
AWS SQS En az bir kez (FIFO: bir kez) FIFO opsiyonel 14 güne kadar Yüksek
Google Pub/Sub En az bir kez Sıralı opsiyonel 7 güne kadar Çok yüksek
NATS JetStream Yapılandırılabilir Stream içi Yapılandırılabilir Yüksek

Dikkat edilmesi gereken nokta, broker “tam bir kez” (exactly-once) garantisi sunsa bile Outbox relay’inin en-az-bir-kez yayın yapabilmesidir; bu nedenle uçtan uca tam-bir-kez davranışı yalnızca tüketici idempotentliğiyle elde edilir.

  • Benzersiz mesaj kimliği: Her outbox kaydına UUID eklenir; tüketici bunu deduplikasyon için kullanır.
  • İşlenmiş kimlik deposu: Tüketici gördüğü kimlikleri saklayarak tekrarı önler.
  • Outbox temizliği: İletilen mesajlar periyodik olarak arşivlenir veya silinir; tablo şişmesi engellenir.
En-az-bir-kez teslimatta benzersiz mesaj kimliğiyle tüketici tarafında deduplikasyon yapılması
En-az-bir-kez teslimatta benzersiz mesaj kimliğiyle tüketici tarafında deduplikasyon yapılması

Tipik Sorunlar ve Çözümleri

Outbox Pattern uygularken ekipler bir dizi tekrar eden sorunla karşılaşır. Aşağıdaki maddeler en yaygın tuzakları ve doğrudan çözümlerini özetler.

  • Outbox tablosu şişmesi: İletilen mesajlar silinmiyor; periyodik temizlik (cleanup) işi veya partition rotasyonu uygulanır.
  • Mükerrer mesaj işleme: En-az-bir-kez teslimat doğası gereği tekrar üretir; tüketici idempotent tasarlanır ve mesaj kimliği ile deduplikasyon yapılır.
  • Polling yükü: Sık polling veritabanını yorar; indeksli durum sütunu ve uygun aralık ayarlanır veya CDC’ye geçilir.
  • Mesaj sıralaması bozulması: Paralel relay sırayı bozabilir; partition anahtarı veya sıralı tüketim ile korunur.
  • Relay tek hata noktası: Tek relay çökerse iletim durur; lider seçimli (leader election) yedekli relay kurulur.
  • Zehirli mesaj (poison message): Sürekli başarısız mesaj kuyruğu tıkar; deneme limiti ve ölü mektup kuyruğu (DLQ) eklenir.

Üretimde İzleme, Sıralama ve Performans

Outbox Pattern üretime alındığında doğru çalıştığını kanıtlamak ve sapmaları erken yakalamak için belirli metriklerin izlenmesi şarttır. En kritik gösterge, outbox tablosundaki işlenmemiş kayıtların yaşıdır (lag): bekleyen en eski mesajın ne kadar süredir iletilmediği. Bu değer sürekli artıyorsa, relay süreci ya çökmüştür ya da broker yetişememektedir; her iki durum da bir alarm tetiklemelidir. İkinci kritik metrik, tabloda biriken işlenmemiş kayıt sayısıdır; ani bir artış, alt sistemlerden birinde tıkanma olduğunu gösterir. Üçüncüsü, retry_count alanı yüksek olan zehirli mesajların (poison message) sayısıdır; bu mesajlar belirli bir denemeden sonra ölü mektup kuyruğuna (DLQ) taşınmalı ve manuel inceleme için işaretlenmelidir.

Sıralama garantisi, Outbox Pattern’in en çok yanlış anlaşılan yönüdür. Tek bir relay süreci outbox’u created_at sırasıyla okuyup yayınlarsa, mesajlar yayınlanma sırasını korur. Ancak relay’i paralelleştirmek (throughput için) sıralamayı bozabilir; çünkü iki paralel işçi mesajları farklı hızlarda işleyebilir. Çözüm, aynı varlığa (aggregate) ait mesajların aynı partition’a yönlendirilmesidir: bir partition anahtarı (örneğin sipariş kimliği) belirlenir ve o varlığın tüm olayları sıralı bir partition’da işlenir. Böylece global sıralama feda edilse bile, iş açısından kritik olan varlık-içi sıralama korunur. Kafka gibi partition tabanlı broker’lar bu deseni doğal destekler.

Performans tarafında en sık karşılaşılan tuzak, polling sorgusunun verimsizliğidir. İşlenmemiş kayıtları getiren sorgu, status sütununda bir indeks kullanmazsa tablo büyüdükçe yavaşlar. Doğru tasarım, yalnızca işlenmemiş kayıtları kapsayan kısmi bir indeks (partial index) tanımlar; böylece sorgu, tablonun tamamını değil yalnızca küçük bekleyen kümeyi tarar. Ayrıca relay’in kayıtları toplu (batch) işlemesi, tek tek işlemeye kıyasla veritabanı round-trip sayısını dramatik düşürür. İletilen kayıtların ayrı bir arşiv tablosuna taşınması veya kısa aralıklı temizlik işiyle silinmesi, ana outbox tablosunu küçük ve hızlı tutarak hem polling hem CDC performansını korur.

Sonuç

Outbox Pattern, dağıtık sistemlerdeki çifte yazma problemini dağıtık transaction’ın karmaşıklığı olmadan çözen kanıtlanmış bir mimari desendir. İş verisini ve giden mesajı aynı yerel transaction’a dahil ederek “ya ikisi de var ya hiçbiri” garantisini sağlar ve mesaj kaybını ortadan kaldırır. İletim için düşük hacimde Polling Publisher, yüksek hacimde Debezium gibi araçlarla CDC tabanlı log tailing tercih edilir. Desen en-az-bir-kez teslimat sunduğundan tüketici tarafının idempotent olması zorunludur; bu nedenle Outbox Pattern, idempotent API tasarımıyla birlikte uygulandığında en güçlü güvenilirlik garantisini verir. Mikroservis mimarilerinde veri tutarlılığını ciddiye alan her ekip için Outbox Pattern, isteğe bağlı bir iyileştirme değil temel bir gerekliliktir.

Sıkça Sorulan Sorular

Outbox Pattern tam olarak hangi problemi çözer?

Çifte yazma problemini (dual write problem) çözer: bir servis hem veritabanına yazıp hem mesaj kuyruğuna mesaj göndermek istediğinde, bu iki ayrı sistemin aynı anda başarılı olacağı garanti edilemez. Outbox Pattern, mesajı iş verisiyle aynı yerel transaction’a dahil ederek ikisinin atomik biçimde birlikte kalıcı olmasını sağlar.

Polling Publisher ile CDC arasında hangisini seçmeliyim?

Düşük hacimli ve gecikmeye duyarlı olmayan sistemler için ek altyapı gerektirmeyen Polling Publisher yeterlidir. Yüksek hacim, düşük gecikme ve güçlü sıralama garantisi gereken sistemlerde Debezium gibi araçlarla CDC (transaction log tailing) tercih edilir; daha verimli ama kurulumu daha karmaşıktır.

Outbox Pattern mesajların tam-bir-kez iletilmesini garanti eder mi?

Hayır, en-az-bir-kez (at-least-once) teslimat garantisi sağlar; bir mesaj birden fazla kez iletilebilir. Bu nedenle tüketici tarafının idempotent olması zorunludur. Her mesaja benzersiz kimlik eklenip tüketicide deduplikasyon yapılarak mükerrer işleme etkisi nötralize edilir.

Outbox tablosu zamanla şişer mi?

Evet, iletilen mesajlar silinmezse outbox tablosu büyür ve performansı düşürür. Periyodik bir temizlik işi iletilmiş kayıtları arşivler veya siler; alternatif olarak tablo partition’lara bölünüp eski partition’lar rotasyonla düşürülür. Bu, tablonun küçük ve hızlı kalmasını sağlar.

Outbox Pattern yerine dağıtık transaction (2PC) kullansam olmaz mı?

İki fazlı commit (2PC) teorik olarak atomiklik sağlar ancak yüksek gecikme, kilitlenme riski ve zayıf ölçeklenebilirlik getirir; mikroservis ortamlarında genellikle kabul edilemez. Outbox Pattern tek bir yerel transaction kullanarak aynı tutarlılığı çok daha düşük maliyet ve karmaşıklıkla sağlar.

Ömer ÖNAL

Yazılım Mimarı | Yapay Zeka LLC. Ölçeklenebilir SaaS, .NET Core altyapıları ve Otonom AI süreçleri inşa ediyorum. Kod değil, sistem tasarlarım.

Yorum (1)

  1. Ömer ÖNAL
    Haziran 6, 2026

    Mikroservis projelerinde en sinsi hata, önce DB’ye yazıp sonra mesaj gönderen koddur; mutlu yolda çalışır ama prod’da servis arada çökünce sessizce veri kaybeder. Bu tutarsızlıkları aylar sonra fark eden ekiplerle çalıştım. Outbox Pattern bunu tek yerel transaction’la çözer. Tek hatırlatma: en-az-bir-kez teslimat verir, yani tüketiciyi idempotent yapmazsanız bu kez de mükerrer işleme problemini satın alırsınız.

Yorum Yap

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir