Service Bus Batch İşlemede Mesaj Bazlı Settlement Devrimi
Ne yalan söyleyeyim, Bir finans müşterisinde geçen ay yaşadığımız olay tam olarak şuydu: Functions" data-glossary-term="Azure Functions">Azure Functions üzerinde Service Bus trigger’ı batch modda çalışıyordu, 50 mesajlık bir batch geliyordu ve içinden bir tanesi — sadece bir tanesi — bozuk formatlı olduğu için tüm batch başarısız oluyordu. 49 mesaj tekrar kuyruğa düşüyordu. Sonra yine işleniyordu. Sonra aynı bozuk mesaj tekrar geliyordu. Döngü başa sarıyordu. Peki bunu neden söylüyorum? Kısacası, sınır bozucu bir kısır döngüydü.
Küçük bir detay: Açık konuşayım, bu sorun Azure Service Bus kullanan hemen herkesin bir noktada karşısına çıkıyor. Ama çoğu kişi “idempotent yazarız, hallederiz” deyip geçiyor. Geçmeyin. Çünkü artık daha iyi bir yol var.
Batch İşlemedeki “Ya Hep Ya Hiç” Sorunu Nedir?
Size bir şey söyleyeyim, Azure Functions’ı Service Bus trigger ile batch modda kullandığınızda, fonksiyonunuz bir seferde birkaç mesaj alıyor. Throughput açısından fena değil; tek (söylemesi ayıp) tek mesaj çekmek yerine 10, 20, 50 mesajı toplu işliyorsunuz, invocation sayısı azalıyor, maliyet de biraz nefes alıyor. Neden önemli bu? Kağıt üstünde baya iş görüyor.
Ama işte sorun tam burada başlıyor.
Varsayılan davranışta batch içindeki mesajların tamamı ya başarılı oluyor ya da başarısız. Ortası yok. Diyelim 50 mesajlık bir batch aldınız. 49’u sorunsuz işlendi. Bir tanesi — belki eksik bir JSON alanı yüzünden, belki downstream bir API timeout’u yüzünden — patladı. Ne oluyor? Tüm batch başarısız sayılıyor (evet, doğru duydunuz). 50 mesajın hepsi kuyruğa geri dönüyor.
Bence, Bunun pratikteki sonuçları insanı gerçekten yoruyor:
- Tekrarlı işleme: Zaten başarıyla tamamlanmış 49 mesaj tekrar işleniyor. Downstream sistemleriniz aynı veriyi iki kere, üç kere, bazen beş kere bile alabiliyor. — ciddi fark yaratıyor
- Boşa giden compute maliyeti: Her retry’da aynı işi yeniden yapıyorsunuz ve Azure faturası şişiyor. Bir müşterimde bu durum aylık Function maliyetini %35 artırmıştı — abartmıyorum. (bence en önemlisi)
- Zehirli mesaj tuzağı: O bozuk mesaj her batch’te karşınıza çıkıyor ve tüm batch’i sürekli blokluyor. MaxDeliveryCount’a ulaşana kadar bu böyle sürüp gidiyor.
- Idempotency yükü: Her consumer’ın “bu mesajı daha önce işledim mi?” kontrolü yapması gerekiyor. Bu da ekstra veritabanı sorguları, ekstra karmaşıklık demek.
2022’de bir e-ticaret projesinde tam olarak bu senaryoyu yaşadık (evet, doğru duydunuz). Sipariş işleme kuyruğu vardı, batch mode açıktı ve stok servisi arada timeout veriyordu. Sonuç? Bazı siparişler müşteriye 3 kere fatura edildi. Düzeltmesi haftalar sürdü. O günden sonra batch processing konusuna çok daha temkinli yaklaşmaya başladım.
Çözüm: Mesaj Bazlı Settlement
Azure Functions artık per-message settlement desteği sunuyor ve bu gerçekten oyunun yönünü değiştiriyor. Lafı gevelemeden söyleyeyim; bu özelliğin gelmesini yıllardır bekliyordum.
Mantık basit aslında: batch’i tek parça gibi görmek yerine her mesajı ayrı ayrı ele alıyorsunuz (işlenen mesaj complete oluyor, hatalı olan abandon ediliyor ya da dead-letter’a gönderiliyor). Diğerleri bundan etkilenmiyor; yanı biri tökezledi diye bütün grup yere kapaklanmıyor.
Settlement Aksiyonları
| Aksiyon | Ne Yapıyor? | Ne Zaman Kullanmalı? |
|---|---|---|
| Complete | Mesajı kuyruktan kalıcı olarak siliyor | Başarıyla işlenmiş mesajlar |
| Abandon | Kilidi bırakıyor, mesaj kuyruğa geri dönüyor | Geçici hatalar (timeout, rate limit vs.) |
| Dead-letter | Mesajı dead-letter kuyruğuna taşıyor | Bozuk format, asla başarılı olamayacak mesajlar |
| Defer | Mesajı kuyrukta tutuyor ama sadece sequence number ile erişilebilir hâle getiriyor | Müdahale gerektiren veya sırası gelmemiş mesajlar |
Şahsen, Yanı bir batch’te 50 mesaj aldığınızda, 47’sını complete, 2’sını abandon, 1’ını dead-letter yapabiliyorsunuz. Hepsi tek function invocation içinde oluyor. Tekrar işleme yok, response nesnesi oluşturma yok, ya hep ya hiç yok.
.NET’te Nasıl Görünüyor?
Kodu göstermeden önce şunu söyleyeyim: bu özelliği kullanmak için ServiceBusMessageActions‘ı Function parametresi olarak inject ediyorsunuz (küçük. Kritik detay). İşte basit ama gerçekçi bir örnek:
[Function("ProcessOrders")]
public async Task Run(
[ServiceBusTrigger("orders", Connection = "SBConnection",
IsBatched = true)] ServiceBusReceivedMessage[] messages,
ServiceBusMessageActions messageActions)
{
foreach (var message in messages)
{
try
{
var order = JsonSerializer.Deserialize<Order>(message.Body);
if (order == null || string.IsNullOrEmpty(order.CustomerId))
{
// Bozuk mesaj — direkt dead-letter'a
await messageActions.DeadLetterMessageAsync(message,
"InvalidFormat", "CustomerId alanı boş veya null");
continue;
}
await ProcessOrderAsync(order);
// Başarılı — complete et
await messageActions.CompleteMessageAsync(message);
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// Rate limit — abandon et, sonra tekrar denensin
await messageActions.AbandonMessageAsync(message,
new Dictionary<string, object>
{
{ "RetryCount", (message.ApplicationProperties.TryGetValue("RetryCount", out var rc) ? (int)rc : 0) + 1 }
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Mesaj işlenemedi: {MessageId}", message.MessageId);
await messageActions.AbandonMessageAsync(message);
}
}
}
Dikkat edin ne kadar temiz dürüyor. Her mesaj kendi kaderine sahip oluyor; biri patlarsa diğerleri etkilenmiyor. Logosoft’ta bir bankacılık projesinde bunu uyguladık — önceki haftaya kıyasla duplicate işlem sayısı sıfıra düştü. Sıfır yanı. Müşteri açıkçası inanamadı.
Neden Bu Kadar Önemli?
Tamam, teknik olarak hoş görünüyor olabilir ama gerçek dünyada neden fark yaratıyor? Birkaç açıdan bakalım.
Granüler Hata Yönetimi
Her hata aynı değil; bunu kabul etmek lazım. Bozuk bir JSON mesajını retry etmenin pek anlamı yoktur, önü dead-letter’a atarsınız gider (ben de ilk duyduğumda şaşırmıştım). Ama bir veritabanı timeout’u? O geçici olabilir; iki dakika sonra düzelebilir. Abandon edip tekrar denemek mantıklı olur.
Bir de manuel müdahale gerektiren işler var tabiî; o durumda defer edip sequence number’ını saklarsınız, gerektiğinde geri çekersiniz.
Şunu söyleyeyim, Eskiden bu ayrımı yapmak için batch dışına çıkmanız ya da kendi retry mekanizmanızı yazmanız gerekiyordu. Şimdi tek fonksiyon içinde bunların hepsini toparlayabiliyorsunuz. Copilot Student’ta GPT-5.3-Codex Kalktı: Ne Yapmalı? yazımızda bu konuya da değinmiştik.
E sonra? Ek Altyapı Kurmadan Retry Takibi Yapabiliyorsunuz
Ama şu küçük detay baya önemli: abandon yaparken application properties’i değiştirebilmeniz güzel çalışıyor.
RetryCount gibi bir property ekleyip her abandon’da artırabiliyorsunuz; sonra fonksiyonun başında bu değeri kontrol edip mesela 5’ten fazla retry olan mesajı direkt dead-letter’a gönderebiliyorsunuz.
Mesaj bazlı settlement ile exponential backoff, retry tracking ve poison message handling’i tek bir Function içinde ek altyapıya gerek kalmadan yapabilirsiniz.
Bu da serverless mesaj işlemenin nihayet düzgün davranması demek.
Saha Tarafında Bu İş Nasıl Dürüyor?
Peki Türkiye’deki kurumsal yapılarda durum nasıl? Hmm… açıkçası global dokümantasyonda okuduğunuz kadar pürüzsüz gitmiyor her zaman. Daha fazla bilgi için PowerToys 0.99: Monitör Kontrolü ve Pencere Yönetimi Kolaylaştı yazımıza bakabilirsiniz.
Bende gördüğüm kadarıyla Service Bus kullanan şirketlerin büyük bölümü hâlâ tek mesaj modunda ilerliyor.
Batch mode’a geçmeye çekiniyorlar çünkü tam da bu yazıda anlattığım “ya hep ya hiç” problemini istemiyorlar.
Per-message settlement bu korkuyu ciddi şekilde azaltıyor. Farkındalık hâlâ düşük kalıyor. Daha fazla bilgi için SPFx Yol Haritası Nisan 2026: AI Özellikleri ve 1.23 RC yazımıza bakabilirsiniz.
Bir de regülasyon tarafı var ki orası biraz sert.
En çok da finans ve sağlıkta “mesaj kayboldu mu?” sorusuna rahat cevap vermek zorundasınız.
BDDK denetimlerinde “şu kayıt neden iki kere işlendi?” sorusu hiç hoş olmaz.
Dead-letter kuyruğuna düşen kayıtları izlemek için ayrı monitöring kurmak şart; Azure Monitör ve Application Insights ile yapılabiliyor ama çoğu ekip bunu sonradan hatırlıyor.
Hatırlayınca da genelde biraz geç olmuş oluyor.
.NET için
Microsoft.Azure.Functions.Worker.Extensions.ServiceBus paketinin 5.x veya üzeri versiyonu gerekiyor.Python ve JavaScript/TypeScript tarafında da ilgili SDK’ların son sürümlerine bakın.
Küçük Ekip Mi Enterprise Mimarisi Mi?
Doğrusu, Küçük bir startup’sanız ve ekip üç-beş kişiyse işi fazla büyütmeyin.
Complete ve dead-letter çoğu zaman yeterli olur; abandon’ı bile aşırı karmaşıklaştırmayın.
Basit bir retry counter ekleyin, üç denemede dead-letter’a gönderin.
Bitti gitti.
Bakın, burayı atlarsanız yazının kalanı anlamsız kalır. Daha fazla bilgi için .NET 10’da API Versiyonlama ve OpenAPI Entegrasyonu: Pratik Rehber yazımıza bakabilirsiniz.
Garip gelecek ama, Ama enterprise seviyeye çıktıysanız işler değişir; mesela yüzlerce microservice’in Service Bus üzerinden konuştuğu yapılarda monitöring kısmını hafife alamazsınız.
Dead-letter kuyruklarına otomatik alert kurun.
Retry count’ları Application Insights’a custom metric olarak gönderin.
Hatta defer ettiğiniz kayıtlar için ayrı bir “deferred message processor” Function yazın.
Bir arkadaşım bana “per-message settlement’ı açtık ama monitöring yapmadık, dead-letter kuyruğunda 15_000 kayıt birikmiş kimsenin haberi olmamış” demişti.
Evet, gerçek hikâye bu. Copilot Chat PR İnceleme: Diff Üzerinde Yapay Zeka Desteği yazımızda bu konuya da değinmiştik.
Maliyet tarafını da unutmayın.
Batch mode + per-message settlement kullandığınızda Function invocation sayısı düşüyor çünkü aynı anda çok kayıt işliyorsunuz — valla güzel iş çıkarmışlar —. Her kayıt için ayrı settlement API çağrısı yapıyorsunuz.
Çok yüksek throughput senaryolarında — saniyede binlerce ileti gibi — bunun maliyetini hesaba katmak lazım.
Küçük-orta ölçekte pek hissettirmez ama günde milyonlarca ileti varsa TL bazında fark yaratabilir.
Neyse uzatmayayım; nokta net zaten.
Böyle event-driven yapılarda agent’lar arası iletişim de ayrı mesele tabiî.
Farklı platformlardaki agent’ların haberleşmesi gerekiyorsa,
A2A v1 ile.NET’te Çapraz Platform Agent İletişimi
// kod değil tabiî :)
yazımda bunu ayrıca anlatmıştım.
Sız ne dersiniz?
Python ve JavaScript Tarafında Ne Oluyor?
Yanı, This özellik sadece.NET’e özel değil;
Python. JavaScript/TypeScript tarafında da kullanılabiliyor.
Ama burada küçük bir not düşeyim: her dil SDK’sının olgunluk seviyesi aynı değil,
yanı görünürde benzer duran şeylerin altında ufak farklar çıkabiliyor.
.NET tarafı en oturmuş olan kısım gibi dürüyor;
settlement aksiyonları temiz ilerliyor,daha tip güvenli diyelim doğrudan type-safe demeyeyim...
Sıkça Sorulan Sorular
Per-message settlement için Azure Functions’ın hangi versiyonu lazım?
Isolated worker model (out-of-process) kullanıyorsanız.NET için Microsoft.Azure.Functions.Worker.Extensions.ServiceBus 5.x veya üzeri gerekiyor. In-process model’de de destekleniyor aslında, (yanlış duymadınız). Microsoft yeni projelerde isolated model’i önerdiğini söylüyor — bunu aklınızın bir köşesinde tutun. Python ve Node.js için de en güncel extension bundle’ları kullanmanız yeterli.
Dead-letter kuyruğundaki mesajları otomatik tekrar işleyebilir mıyım?
Evet, dead-letter kuyruğuna ayrı bir Function trigger bağlayabilirsiniz. Ama bence bunu hemen otomatikleştirmek yerine, önce mesajın neden dead-letter’a düştüğünü analiz eden bir mekanizma kurmanız çok daha mantıklı. Sız hiç denediniz mi? Yoksa aynı bozuk mesajı sürekli tekrar işlemeye çalışırsınız — yanı tam da kaçınmak istediğimiz o kısır döngüye girmiş olursunuz.
Batch mode’da maxMessageCount’u ne kadar yüksek tutmalıyım?
Dürüst olmak gerekirse, Bu tamamen iş yükünüze bağlı. Tecrübeme göre 16-32 arasında başlayıp monitöring verilerine göre ayarlamak en sağlıklısı. Çok yüksek tutarsanız (belki yanılıyorum ama) — mesela 100+ gibi — lock timeout sorunları yaşayabilirsiniz, hani tüm mesajları işlemeniz lock süresi içinde bitmeyebilir. Çok düşük tutarsanız da batch kullanmanın avantajını kaybedersiniz zaten.
Per-message settlement kullanıyorsam idempotency’ye hâlâ ihtiyaç var mı?
Kısa cevap: evet, ama çok daha az. Complete edilen mesajlar kuyruktan siliniyor, yanı normal akışta duplicate gelmiyor. Açıkçası nadir bir senaryo, ama network partition gibi durumlarda mesaj complete edilmiş olsa bile SDK tarafında onay alınamayıp tekrar işlenebiliyor. Bu yüzden kritik iş süreçlerinde yine de temel seviyede bir idempotency koruması bulundurun.
Peki neden?
Managed Identity ile per-message settlement çalışıyor mu?
Evet, çalışıyor. Ama connection string yerine managed identity kullanıyorsanız, Service Bus namespace’ine doğru RBAC rollerini atadığınızdan emin olun. En azından “Azure Service Bus Data Receiver” rolü gerekiyor — settlement işlemleri için de bu rol ya da “Azure Service Bus Data Owner” yeterli oluyor.
Kaynaklar ve İleri Okuma
Azure Functions Service Bus Trigger — Resmî Dokümantasyon
Per-message Settlement in Azure Service Bus — Azure SDK Blog
Azure Service Bus Message Sessions ve Settlement — Microsoft Learn
İçeriği paylaş:
Bu içerik işinize yaradı mı?
Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.








Yorum gönder