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.

Ç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.

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.
- Uygulama bir transaction başlatır.
- İş verisini iş tablosuna yazar (örn. sipariş kaydı).
- Aynı transaction içinde outbox tablosuna giden mesajı ekler.
- Transaction commit edilir; her iki yazma da atomik biçimde kalıcı olur.
- Ayrı bir relay süreci outbox’taki işlenmemiş mesajları okur.
- Mesajı broker’a (Kafka, RabbitMQ) yayınlar.
- 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 |

İ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.

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
Haziran 6, 2026Mikroservis 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.