Dağıtık sistemlerin kalbi olan Mesajlaşma Semantiği (Messaging Semantics), bir mesajın göndericiden alıcıya ulaşma garantisini ve bu sürecin kurallarını tanımlar. Basitçe söylemek gerekirse; "Bu mesaj karşıya kesin gider mi, giderse kaç kere gider?" sorusunun cevabıdır.

Sisteminizin hızı ile güvenilirliği arasındaki o hassas dengeyi bu üç kategoriden biriyle kurarsınız:


1. En Fazla Bir Kez (At-Most-Once)

"Gönder ve unut" (Fire and forget) mantığıyla çalışır. Mesaj bir kez yola çıkar; hedefe ulaşıp ulaşmadığı kontrol edilmez.

  • Davranış: Mesaj kaybolabilir ama asla tekrar etmez.

  • Kullanım Alanı: Veri kaybının dünyanın sonu olmadığı, hızın her şeyden önemli olduğu durumlar.

  • Örnek: Canlı video akışı (bir kare kaçarsa yenisi gelir), sıcaklık sensörü verileri veya log toplama.

  • Artısı: Çok hızlıdır, sistem yükü minimumdur.

2. En Az Bir Kez (At-Least-Once)

Sistemin en popüler tercihidir. Mesajın ulaştığından emin olmak için alıcıdan bir onay (ACK) beklenir. Onay gelmezse mesaj tekrar gönderilir.

  • Davranış: Mesaj asla kaybolmaz ama birden fazla kez iletilebilir.

  • Kullanım Alanı: Verinin kaybolmaması gereken kritik iş süreçleri.

  • Örnek: Sipariş onayı, fatura oluşturma.

  • Önemli Not: Bu semantiği kullanırken alıcının idempotent (eş etkili) olması gerekir. Yani aynı mesaj 3 kere gelse bile sistem hata vermemeli veya mükerrer işlem yapmamalıdır.

3. Tam Olarak Bir Kez (Exactly-Once)

Mühendislik açısından "kutsal kâse" olarak kabul edilir. Mesajın ne kaybolmasına ne de yinelenmesine izin verilir.

  • Davranış: Kayıp yok, kopya yok.
  • Nasıl Sağlanır: Genellikle işlem kimlikleri (Transaction IDs) ve mesajın işlenip işlenmediğini takip eden gelişmiş mekanizmalarla (Kafka'nın Transactional API'si gibi) yapılır.
  • Kullanım Alanı: Hata payının sıfır olması gereken finansal sistemler.
  • Eksisi: Karmaşıktır ve ağ üzerinde ciddi bir gecikme (latency) yaratabilir.

Özet Karşılaştırma Tablosu

Semantik Veri Kaybı? Kopya Mesaj? Performans
At-Most-Once Mümkün Hayır En Yüksek
At-Least-Once Hayır Mümkün Orta
Exactly-Once Hayır Hayır En Düşük

Küçük Bir İpucu: Gerçek dünyada çoğu sistem "At-Least-Once" kullanır ve mükerrer kayıtları önlemek için uygulama katmanında (veritabanı seviyesinde) önlem alır. Çünkü "Exactly-Once" sağlamak hem pahalı hem de sistem performansını yoran bir süreçtir.


1. Apache Kafka

Kafka, bu konuda en esnek olanıdır çünkü "Exactly-Once" desteğini yerleşik (native) olarak sunan nadir sistemlerden biridir.

  • At-Most-Once: Producer acks=0 ayarıyla çalışır. Mesajı gönderir, broker'dan onay beklemez. Mesaj yolda düşerse kaybolur.
  • At-Least-Once (Varsayılan): Producer acks=all (veya 1) kullanır. Broker mesajı aldığına dair onay vermezse, Producer mesajı tekrar gönderir. Alıcı (Consumer) tarafında mesaj işlendikten sonra "offset" commit edilir.
  • Exactly-Once (EOS): 2017'den beri mümkündür.
  • İdempotent Producer: Her mesaja bir dizi numarası ekler, böylece broker aynı mesajı iki kez alırsa kopyayı eler.
  • Transactions: processing.guarantee=exactly_once_v2 ayarıyla, okuma-işleme-yazma döngüsü atomik hale getirilir.

2. RabbitMQ

Klasik bir mesaj kuyruğu (message queue) olan RabbitMQ, güvenilirliğe odaklanır ancak mimarisi gereği "Exactly-Once" sağlamak daha zordur.

  • At-Most-Once: Onay mekanizması (Publisher Confirms) kapatılırsa mesaj gönderilir ve unutulur.
  • At-Least-Once: En yaygın kullanım şeklidir. Mesaj tüketildikten sonra Consumer manuel bir ack gönderir. Eğer ack gelmeden bağlantı koparsa, RabbitMQ mesajı tekrar kuyruğa koyar (unacked -> ready).
  • Exactly-Once: RabbitMQ bunu doğrudan desteklemez. Bunu sağlamak için uygulama katmanında Idempotency (örneğin; mesaj ID'sini bir DB'de kontrol etmek) veya "Deduplication" eklentileri kullanılması şarttır.

3. AWS SQS (Simple Queue Service)

Bulut tabanlı bu serviste kuyruk tipine göre semantik değişir:

  • Standart Kuyruk (Standard Queues): At-Least-Once garanti eder. Ölçeklenebilirlik adına mesajların birden fazla kopyasını tutar, bu yüzden bazen aynı mesaj iki kez gelebilir.
  • FIFO Kuyruklar (First-In-First-Out): Exactly-Once ve Sıralı İletim garanti eder. AWS, 5 dakikalık bir pencere içinde aynı MessageDeduplicationId ile gelen mesajları eler. Ancak bu tip kuyrukların saniye başına işlem kapasitesi (TPS) standart kuyruklara göre daha düşüktür.

Hangisini Seçmeli?

Senaryo Önerilen Araç Semantik
Büyük Veri / Log Akışı Kafka At-Most-Once
Finansal İşlemler / Ödeme Kafka (EOS) veya SQS FIFO Exactly-Once
Mikroservis İletişimi RabbitMQ veya SQS Standard At-Least-Once + Idempotency

Kritik Bir Detay: Idempotency (Eş Etkililik)

Gördüğünüz gibi, çoğu sistem "At-Least-Once" seviyesinde kalır. Bunun sebebi, ağ üzerinde "tam olarak bir kez" ulaştığından emin olmanın maliyetinin çok yüksek olmasıdır.

Bunun yerine mühendisler genellikle At-Least-Once kullanır ve alıcı tarafta şu basit mantığı kurar:

"Eğer bu ID'ye sahip mesajı daha önce işlediysem, tekrar işleme; sadece başarılı olduğunu söyle (ACK)."

Python ve Kafka (confluent-kafka kütüphanesi) kullanarak, At-Least-Once teslimat garantisi altında çalışan, ancak uygulama katmanında Exactly-Once etkisi yaratan idempotent bir consumer yapısını inceleyelim.

Bu yaklaşım, mesaj iki kez gelse bile veritabanında mükerrer işlem yapılmasını engeller.


Idempotent Consumer Mantığı

Mesajların içinde mutlaka benzersiz bir message_id (UUID gibi) bulunmalıdır. Alıcı (Consumer), bir mesajı işlemeden önce bu ID'nin daha önce işlenip işlenmediğini kontrol eder.

Örnek Kod (Python)

import json
from confluent_kafka import Consumer, KafkaError

# Kafka Ayarları
conf = {
    'bootstrap.servers': 'localhost:9092',
    'group.id': 'odeme-servisi-grubu',
    'auto.offset.reset': 'earliest',
    'enable.auto.commit': False  # Manuel commit: İşlem bittikten sonra onay veriyoruz
}

consumer = Consumer(conf)
consumer.subscribe(['odeme-islemleri'])

# Daha önce işlenen mesajları tutan bir yapı (Gerçek dünyada Redis veya DB olmalı)
processed_message_ids = set() 

def process_message(msg_data):
    msg_id = msg_data.get("id")

    # 1. Kontrol Et: Bu mesaj daha önce işlendi mi? (Idempotency Check)
    if msg_id in processed_message_ids:
        print(f"⚠️ Mesaj {msg_id} zaten işlenmiş. Atlanıyor...")
        return True

    # 2. İşlemi Yap: (Örn: Veritabanına yazma, bakiye güncelleme)
    try:
        print(f"✅ İşleniyor: {msg_data['payload']}")
        # Veritabanı işlemleri burada yapılır...

        # 3. Kaydet: İşlenen ID'yi "işlendi" olarak işaretle
        processed_message_ids.add(msg_id)
        return True
    except Exception as e:
        print(f"❌ İşlem hatası: {e}")
        return False

try:
    while True:
        msg = consumer.poll(1.0)
        if msg is None: continue
        if msg.error():
            print(f"Hata: {msg.error()}")
            continue

        # Mesajı decode et
        data = json.loads(msg.value().decode('utf-8'))

        # İşleme başla
        success = process_message(data)

        if success:
            # İşlem başarılıysa Kafka'ya "Mesajı aldım, sıradakine geç" (ACK) diyoruz
            consumer.commit(msg)

except KeyboardInterrupt:
    pass
finally:
    consumer.close()

Bu Yapıda Neler Oluyor?

  1. enable.auto.commit: False: Kafka'ya "Ben söylemeden offset'i ilerletme" diyoruz. Eğer kodumuz mesajı işlerken çökerse, Kafka mesajın işlenmediğini anlar ve tekrar gönderir (At-Least-Once).
  2. processed_message_ids Kontrolü: Eğer ağdaki bir gecikme nedeniyle aynı mesaj iki kez gelirse (duplicate), kodumuzun içindeki if bloğu bunu yakalar ve veritabanında ikinci kez işlem yapmaz (Idempotency).
  3. Hata Yönetimi: Eğer process_message fonksiyonu başarısız olursa, commit yapılmaz. Böylece sistem düzeldiğinde aynı mesaj tekrar okunur ve veri kaybı önlenir.

Gerçek Hayatta "Processed IDs" Nerede Tutulur?

Yukarıdaki örnekte basit bir set() kullandık ancak sistem kapandığında bu veri silinir. Profesyonel sistemlerde bu ID'ler şuralarda tutulur:

  • Redis: Çok hızlı olduğu için "duplicate" kontrolü için idealdir (genellikle TTL/zaman aşımı ile).
  • İlişkisel Veritabanı (PostgreSQL/MySQL): Mesaj ID'sini bir UNIQUE sütun olarak kaydedersiniz. İkinci kez aynı ID gelirse DB hata verir ve işlem gerçekleşmez.