Transactional Outbox Deseni: Diyagram ve Uygulama Rehberi

Transactional outbox deseni çift yazma sorununu çözer: olayı veriyle aynı işlemde yazın, sonra aktarın. Diyagram, SQL, polling ve CDC karşılaştırması.

· Kurucu ve geliştirici

Yayımlandı:

Transactional Outbox Deseni: Diyagram ve Uygulama Rehberi

Olay güdümlü her servis eninde sonunda şöyle bir kod yazar: siparişi kaydet, sonra OrderPlaced yayınla. Testte çalışır. Üretimde süreç iki satırın arasında çöker ve sipariş, olayı olmadan var olur. Ya da yayın başarılı olur, veritabanı işlemi geri alınır ve olay var olmayan bir siparişi duyurur.

Bu çift yazma (dual-write) sorunudur ve transactional outbox deseni bunun standart çözümüdür. Bu yazı sorunu, deseni, aktarıcıyı kurmanın iki yolunu ve desenin gerçekten işe yarayıp yaramayacağını belirleyen ayrıntıları anlatıyor: sıralama, tekrarlar ve temizlik.

Çift yazma sorunu

Bir veritabanı ile bir mesaj aracısı (broker), ortak işlemi olmayan iki sistemdir. İkisine birden yazan her kodda, yazmalardan birinin başarılı olup diğerinin olmadığı bir zaman aralığı vardır:

İşlem sırası Ne ters gidebilir
Önce commit, sonra yayın Commit sonrası çökme: olay sonsuza kadar kaybolur
Önce yayın, sonra commit Commit başarısız olur: tüketiciler hiç var olmamış bir sipariş için harekete geçer
İşlemin içinde yayın Broker çağrısı başarılı olur, işlem yine de geri alınır

Yeniden denemeler bu aralığı kapatmaz, yalnızca yerini değiştirir. Commit sonrası bir hook'tan yayınlamak gibi sıralama hileleri aralığı yalnızca küçültür: commit ile hook arasındaki bir çökme yine olayı kaybettirir. Veritabanı ile broker arasında dağıtık işlemler nadiren mümkündür, mümkün olduğunda da yavaştır.

Desen

Olayı iş verisiyle aynı işlemde veritabanına yazın ve daha sonra oradan yayınlayın:

  1. Servis, sipariş satırını ve bir outbox satırını (olay kimliği, aggregate kimliği, tür, yük) tek bir yerel işlemde ekler. İkisi birlikte commit edilir ya da hiçbiri edilmez.
  2. Ayrı bir aktarıcı (relay) yeni outbox satırlarını okur ve broker'a yayınlar.
  3. Aktarıcı, broker onay verdiğinde satırları yayınlandı olarak işaretler ya da siler.

Artık garanti basittir: sipariş varsa olayı en az bir kez yayınlanacaktır. Chris Richardson'ın desen açıklaması da aynı yapıyı kullanır.

CREATE TABLE outbox (
  id            uuid PRIMARY KEY,
  aggregate_id  bigint      NOT NULL,
  type          text        NOT NULL,
  payload       jsonb       NOT NULL,
  created_at    timestamptz NOT NULL DEFAULT now()
);

Aktarıcıyı kurmanın iki yolu

Polling ile yayınlayıcı. Bir döngü yayınlanmamış satırları gruplar halinde seçer, yayınlar ve siler. Postgres'te SELECT ... FOR UPDATE SKIP LOCKED, birden fazla aktarıcı örneğinin birbirini engellemeden işi paylaşmasını sağlar; kilitleme ifadesi belgelerine bakın. Kurması ve anlaması kolaydır. Bedelleri polling gecikmesi ve yük ile ince bir sıralama ayrıntısıdır: düşük kimlikli satırlar, yüksek kimlikli satırlardan sonra commit edilebilir; bu yüzden kimliğe göre sıralayan bir poller henüz görünür olmayan bir satırı atlayabilir.

Log takibi (change data capture). Bir CDC aracı veritabanının işlem logunu okur ve commit edilmiş outbox eklemelerini mesajlara dönüştürür. Debezium'un outbox event router'ı outbox tabloları için tam olarak bunu yapar ve her satırı sütunlarına göre bir konuya yönlendirir. Olaylar commit sırasıyla, düşük gecikmeyle ve polling sorguları olmadan çıkar; bedeli CDC hattını çalıştırmak ve işletmektir.

Polling CDC
Gecikme Polling aralığı Commit anına yakın
Sıralama Dikkat ister Commit sırası
Hareketli parçalar Bir işçi Bir connector ve log yapılandırması
Uygun olduğu durum Orta hacimler, küçük ekipler Yüksek hacim, çok sayıda outbox

En az bir kez, tekrarlar demektir

Aktarıcı bir satırı yayınlayıp yayınlandı olarak işaretlemeden önce çökebilir. Yeniden başladığında satırı tekrar yayınlar. Bu kaçınılmazdır, bu yüzden tüketiciler idempotent olmalıdır:

  • Her olay benzersiz bir kimlik taşır (outbox satırının kimliği).
  • Tüketiciler işledikleri kimlikleri, tercihen işlemenin etkisiyle aynı işlemde (bir inbox tablosu) kaydeder ve gördükleri kimlikleri atlar.
  • Ya da etkiyi doğal olarak idempotent yapın; örneğin sipariş kimliğine göre bir upsert.

Sıralama

Tüketiciler genellikle aggregate başına sıraya önem verir: aynı sipariş için OrderPlaced, OrderCancelled'dan önce gelmelidir. Bir siparişin tüm olayları aynı bölüme (partition) gitsin diye mesaj anahtarı olarak aggregate kimliğini kullanın ve commit sırasıyla yayınlayın (CDC bunu yapar; bir poller commit anında atanan bir sıra numarasına göre sıralamalı ya da bir aggregate'in olaylarını seri olarak işlemelidir). Tüm siparişler genelinde küresel sıralama nadiren gerekir ve sağlaması pahalıdır.

Temizlik

Hiç boşaltılmayan bir outbox tablosu sınırsız büyür ve poller'ı yavaşlatır. Satırları yayınladıktan sonra silin ya da kısa bir denetim süresi boyunca tutup eski bölümleri düşürün. CDC ile satırlar aynı işlemde, eklendikten hemen sonra silinebilir, çünkü connector eklemeyi tablodan değil logdan okur.

Hataları tek tek gözden geçirmek

Tasarımı her çökme noktasına karşı kontrol edin:

  • Commit öncesi çökme. Ne sipariş ne olay vardır. İstemci bir hata alır ve yeniden deneyebilir.
  • Commit sonrası, aktarıcı çalışmadan önce çökme. İki satır da vardır; aktarıcı başladığında olayı yayınlar.
  • Aktarıcının yayından sonra, satırı silmeden önce çökmesi. Olay yeniden başlatmada tekrar yayınlanır ve tüketiciler tekrarı kimliğe göre ayıklar.
  • Broker çöktü. Satırlar outbox'ta birikir; servis sipariş almaya devam eder ve broker geri geldiğinde aktarıcı yetişir. Yayınlanmamış en eski satırın yaşına alarm kurun.

Her durumda veritabanı doğruluğun kaynağı olarak kalır; desenin var olma sebebi de bu özelliktir.

Mimari diyagram

Bir sipariş servisi tek bir Postgres işleminde orders ve outbox adlı iki tabloya satır ekler. Bir outbox aktarıcısı yeni satırları okur ve order-events konusuna yayınlar. Kargo ve e-posta servisleri olayları tüketir; e-posta servisi tekrarları atlamak için işlenen kimlikleri kaydeder.

10 bileşen · 6 bağlantı

Archboard'da aç
Diyagram içeriği
  • Order service
  • Postgres: one transaction
  • orders
  • outbox
  • Outbox relay
  • order-events
  • Shipping service
  • Email service
  • Processed events
  • both rows
  • new rows
  • publish
  • OrderPlaced
  • seen it?
Transactional outbox deseni. Sipariş servisi sipariş satırını ve bir outbox satırını tek bir Postgres işleminde yazar; bir aktarıcı outbox satırlarını bir Kafka konusuna yayınlar ve teslimat en az bir kez olduğu için tüketiciler olay kimliğine göre tekrarları ayıklar.

Nasıl okunur:

  • Sol: sipariş servisi tek bir Postgres işleminde orders ve outbox tablolarına iki satır yazar. Çerçeve işlem sınırını gösterir.
  • Orta: outbox aktarıcısı, polling ya da CDC ile yeni satırları okur ve order-events konusuna yayınlar.
  • Sağ: kargo ve e-posta servisleri olayları tüketir. E-posta servisi harekete geçmeden önce işlenmiş olay kimlikleri tablosuna bakar; tekrarlar bu şekilde emilir.
  • SQL bloğu işlemi gösterir; böylece kimse burada "atomik" kelimesinin ne anlama geldiğini tahmin etmek zorunda kalmaz.

Diyagrama ne konmalı

  • İşlem sınırı. İki tabloyu da onun içine çizin; desenin tamamı budur.
  • Aktarıcı ve türü. Polling ya da CDC, gecikmeyi ve sıralamayı değiştirir.
  • Teslimat semantiği. Aktarıcının üzerine "en az bir kez", her tüketicinin üzerine "idempotent" yazın.
  • Mesaj anahtarı. Olayların aggregate kimliğiyle anahtarlandığını not edin.

Olay güdümlü mimari yazısı tüketici tarafını daha derinlemesine ele alıyor; saga deseni diyagramı ise çok servisli bir iş akışının her adımında outbox'a nerede ihtiyaç duyulduğunu gösteriyor. Veritabanı replikasyonu yazısı, CDC'nin okuduğu write-ahead log'u açıklıyor.

Archboard'da oluşturun

  1. Google ya da GitHub ile giriş yapın ve bir diyagram oluşturun.
  2. C4 Modeli paketinden sipariş servisi için bir Konteyner, outbox aktarıcısı için bir tane daha ekleyin.
  3. Temel paketinden veritabanı ve işlem adıyla etiketlenmiş bir Çerçeve ekleyin ve içine orders ve outbox tabloları için Varlık-İlişki paketinden iki Varlık düğümü koyun.
  4. Konu için Veri paketinden Kafka, tüketiciler için Konteyner düğümleri ve işlenen olay kimlikleri için bir Veritabanı ekleyin.
  5. İşlemin SQL'ini içeren, Temel paketinden bir Markdown bloğu ekleyin.

Örnek 10 diyagram düğümü kullanıyor ve Free planın sınırları içinde kalıyor.

SSS

Transactional outbox hangi sorunu çözer? Bir servis veritabanına yazmayı ve mesaj yayınlamayı iki ayrı adım olarak yaptığında olayların kaybolmasını ya da var olmayan olayların ortaya çıkmasını. Outbox, olayı aynı veritabanı işleminin parçası yapar.

Outbox deseni tam olarak bir kez (exactly once) teslimat sağlar mı? Hayır. En az bir kez teslimatı garanti eder. Tüketiciler tekrarları ele almalıdır; bu genellikle işlenen olay kimliklerini kaydederek yapılır.

Polling mi CDC mi: hangi aktarıcıyı kullanmalıyım? Polling daha basittir ve orta hacimler için yeterlidir. CDC daha düşük gecikme ve commit sırasıyla teslimat sağlar; yüksek hacimde ya da birçok servis outbox kullandığında buna değer.

Event sourcing kullanıyorsam yine de outbox gerekir mi? Tüketicilerin abone olduğu kaynak olay deposunun kendisiyse gerekmez. Bir servis durumu tablolarda tutuyor ve aynı zamanda olay yayınlamak zorundaysa her zaman gerekir.

Outbox tablosu ne kadar büyür? Siz ne kadar izin verirseniz. Yayınlanan satırları hemen silin ya da tabloyu zamana göre bölümleyip eski bölümleri düşürün.

Ücretsiz başlayın — tek kullanıcı, kredi kartı gerekmez.

İlgili yazılar

← Blog'a dön

Bir dakikada canvas açın

Google veya GitHub ile giriş yapın. Tek koltuk ücretsiz; ekip katılınca planı yükseltin.