Makale

Laravel ve Kafka'da Kayıp Olayları Önlemek: PostgreSQL Outbox ve Idempotent Consumer

Laravel ve Kafka'da Kayıp Olayları Önlemek: PostgreSQL Outbox ve Idempotent Consumer

Veritabanı commit'i ve Kafka yayını tek normal transaction değildir. Outbox ile kaybı, consumer tarafındaki deduplikasyonla çift yan etkiyi önleyin.

11 dk okumaDil: TR TürkçeÜcretsiz0 alkış0 yorum
Okuma seçenekleri

Şu kod ilk bakışta yeterli görünür:

$order = Order::create($data);
$kafka->publish('orders.placed.v1', $order->toArray());

Veritabanı kaydı başarılı, Kafka yayını başarısız olursa stok sistemi siparişten habersiz kalır. Sırayı ters çevirmek de çözmez: olay yayımlanıp veritabanı transaction'ı geri alınabilir. DB::transaction() içine ağ çağrısı koymak PostgreSQL ve Kafka'yı tek transaction yapmaz.

Transactional outbox, iş verisiyle olay kaydını aynı veritabanı transaction'ına yazar. Ayrı publisher bu kaydı Kafka'ya taşır. Böylece yayın beklerken süreç ölse de olay kaybolmaz. Fakat tekrar gönderim mümkün olduğundan consumer da idempotent olmalıdır.

Önce olayın anlamı

{
  "eventId": "evt_01JQ8X8F",
  "eventType": "OrderPlaced",
  "schemaVersion": 1,
  "occurredAt": "2026-09-29T10:30:00Z",
  "orderId": "ord_782",
  "customerId": "cus_17",
  "totalMinor": 12900,
  "currency": "USD"
}

OrderPlaced siparişin kalıcı olarak kabul edildiğini ifade eder. eventId yeniden yayınlarda sabit kalır; consumer çift işleme kontrolünde kullanır. Kafka kayıt anahtarı orderId olur ve kararlı bölümleme altında sipariş olaylarını aynı partition'da tutar. Eloquent modelinin tamamını yayımlamayın: tablo kolonları ve ilişkiler event sözleşmesi değildir, hassas veri içerebilir.

Topic sahibi, saklama süresi, anahtar, şema sürümü ve beklenen tüketiciler belgelenmelidir. orders.placed.v1 adı tek başına şema yönetimi değildir.

Outbox tablosu ve tek transaction

PostgreSQL için başlangıç yapısı:

CREATE TABLE outbox_events (
  id uuid PRIMARY KEY,
  aggregate_id text NOT NULL,
  topic text NOT NULL,
  event_key text NOT NULL,
  payload jsonb NOT NULL,
  occurred_at timestamptz NOT NULL,
  published_at timestamptz NULL,
  attempts integer NOT NULL DEFAULT 0
);
CREATE INDEX outbox_pending_idx ON outbox_events (occurred_at, id)
  WHERE published_at IS NULL;

Laravel tarafında siparişle outbox satırını aynı DB::transaction() içinde oluşturun. Transaction geri alınırsa ikisi de yok olur; commit olursa web süreci ölse bile olay bekler. Ağ yanıtı beklerken veritabanı kilidi tutmayın. Kod taslağı:

DB::transaction(function () use ($data) {
    $order = Order::create($data);
    $eventId = (string) Str::uuid();
    DB::table('outbox_events')->insert([
        'id' => $eventId,
        'aggregate_id' => (string) $order->id,
        'topic' => 'orders.placed.v1',
        'event_key' => (string) $order->id,
        'payload' => json_encode([
            'eventId' => $eventId,
            'eventType' => 'OrderPlaced',
            'schemaVersion' => 1,
            'orderId' => (string) $order->id,
        ], JSON_THROW_ON_ERROR),
        'occurred_at' => now(),
    ]);
});

Gerçek uygulamada doğrulama, veri türleri, kişisel veri ve sipariş alanlarını açıkça tasarlayın. Bu örnek bir Kafka paketine ait hazır API değildir.

Publisher neyi garanti eder?

Worker yayımlanmamış küçük bir parti seçer, çoklu worker arasında güvenle sahiplenir, kayıtları saklı key ile Kafka'ya yollar, uygun producer onayını bekler ve published_at yazar. Kısa claim transaction'ı veya süreli lease kullanılabilir. Yavaş broker yanıtı boyunca satır kilidi tutmayın; çöken worker'ın lease'i geri alınabilsin.

Kafka kaydı kabul ettikten sonra worker published_at yazamadan ölebilir. Sonraki deneme aynı eventId ile tekrar yayınlar. Bu nedenle outbox en az bir kez yayın sağlar; her sisteme yalnızca bir kez teslimi garanti etmez. Idempotent producer, Kafka'ya gönderim tekrarlarını azaltır ama PostgreSQL güncellemesiyle Kafka onayını atomik hale getirmez.

Publisher bir Laravel command, worker veya CDC hattı olabilir. Laravel'in yerleşik queue seçenekleri Redis, SQS ve veritabanı gibi sistemleri kapsar; yalnızca QUEUE_CONNECTION değerini değiştirerek Kafka yerleşik driver'a dönüşmez. Seçtiğiniz istemci paketinin sürüm, güvenlik ve operasyon özelliklerini ayrıca değerlendirin.

Idempotent inventory consumer

Stok consumer'ı rezervasyon yapıp offset commit etmeden çökerse aynı olayı yeniden okur. Şu benzersiz kayıt ikinci rezervasyonu önleyebilir:

CREATE TABLE processed_events (
  consumer_name text NOT NULL,
  event_id text NOT NULL,
  processed_at timestamptz NOT NULL DEFAULT now(),
  PRIMARY KEY (consumer_name, event_id)
);

Stok veritabanı transaction'ı içinde (inventory-reservation, eventId) ekleyin ve rezervasyonu uygulayın. Aynı anahtar tekrar gelirse yan etkiyi atlayın. Transaction commit edildikten sonra Kafka offset'ini ilerletin. Veritabanı commit'i başarılı olup offset kaybolursa tekrar okuma güvenlidir; transaction geri alınırsa offset başarılı işlem gibi ilerlememelidir.

E-posta ve ödeme gibi dış sistemlerde bu yerel transaction yeterli olmayabilir. Sağlayıcı idempotency key'i, başka bir outbox veya telafi akışı tasarlayın. Her yan etkinin garantisini ayrı anlatın; “tam olarak bir kez” ifadesini tüm zincire yaymayın.

Hatalar, replay ve şema evrimi

Geçici veritabanı kesintisinde gecikmeli retry gerekir. Sürekli bozuk olayda düzeltme, karantina veya belgelenmiş atlama kararı gerekir. Dead-letter topic kullanılabilir, fakat asıl offset commit'iyle karantinaya yayın arasında yeni bir hata penceresi vardır. Alarm, erişim, saklama ve yeniden işleme prosedürünü belirleyin.

eventId, topic, partition, offset, grup ve güvenli hata kategorisini loglayın; hassas payload'ı bütünüyle loglamayın. Deduplikasyon kayıtlarını vaat edilen retention ve replay süresi boyunca tutun. Yeni alan eklerken eski olay örnekleriyle uyumluluk testi yapın; mevcut alanın anlamını sessizce değiştirmeyin.

Kritik testler ve metrikler

Sipariş transaction'ının rollback'ini, commit sonrası publisher durmasını, Kafka kabul edip published_at öncesi çökmesini, aynı olayın iki kez teslimini ve rezervasyon sonrası offset öncesi çöküşü test edin. İki publisher ve eşzamanlı consumer çalıştırın. En eski bekleyen outbox satırının yaşını, pending sayısını, yayın hatalarını, consumer lag'i ve karantina sayısını izleyin.

Tek bir arka plan e-postası için sıradan Laravel kuyruğu yeterli olabilir. Sipariş olayı bağımsız sistemlere güvenilir biçimde ulaşmalı ve yeniden okunabilmeliyse outbox, sabit event kimliği, idempotent consumer ve görünür hata yönetimi bir bütün olarak değer üretir.

Kaynaklar

Öne Çıkan Makaleler

Üretimde Kafka: Bölümleme, Consumer Lag, Güvenilirlik ve Olay Müdahalesi
EditoryalTR
11 dkÜcretsiz

Üretimde Kafka: Bölümleme, Consumer Lag, Güvenilirlik ve Olay Müdahalesi

Kafka kümesini canlıya taşımadan önce sıralama, kapasite, gecikme ve yeniden oynatma kararlarını netleştirin. Somut metrikler ve olay müdahalesi adımlarıyla bir rehber.

Mühendislik MakaleleriPlatform Rehberleri
0 alkış
Oku
Apache Kafka'yı Anlamak: Topic, Partition, Consumer Group ve İlk Olay Akışınız
EditoryalTR
10 dkÜcretsiz

Apache Kafka'yı Anlamak: Topic, Partition, Consumer Group ve İlk Olay Akışınız

Bir sipariş olayını üreticiden tüketicilere izleyin; ardından Kafka'yı yerelde çalıştırıp sıralama, gruplar ve tekrar okuma davranışını deneyin.

Mühendislik MakaleleriPlatform Rehberleri
0 alkış
Oku
İstemcilerin Güvenebileceği API'ler Nasıl Tasarlanır? Sözleşmeden Başlayan Pratik Rehber
EditoryalTR
9 dkÜcretsiz

İstemcilerin Güvenebileceği API'ler Nasıl Tasarlanır? Sözleşmeden Başlayan Pratik Rehber

İyi bir API, istemcinin sonraki adımını öngörülebilir kılar. Bir kurs kaydı örneğiyle güvenli tekrarları, yararlı hataları ve değişim planını tasarlayın.

Mühendislik MakaleleriPlatform Rehberleri
0 alkış
Oku

Yorumlar

0 yorum

Henüz onaylı yorum yok. Yeni yanıtlar moderasyon bekleyebilir.