Buğra Şıkel
Event Choreography’den Saga Orchestration’a Geçiş: Dağıtık İş Akışlarını Kademeli Merkezileştirme

Giriş
2019 yılında, e-ticaret platformumuzun sipariş yönetim sistemini 45 farklı mikroservise böldüğümüzde, sistemin tamamen asenkron ve decupled olmasını hedeflemiştik. Sipariş oluşturulduğunda fırlatılan bir OrderCreated eventi, ödeme, stok, kargo ve fatura servisleri tarafından dinleniyor; her servis kendi işlemini bitirince yeni bir event fırlatıyordu. Sistemin p99 gecikme süresi 45ms seviyelerindeydi. Ancak aradan geçen üç yılın ardından, günlük 4.500 TPS yük altında sistem adeta bir langırt masasına (pinball machine) dönüştü.
Bir siparişin neden “Askıda (Pending)” kaldığını bulmak, 5 farklı Kibana dashboard’u arasında korelasyon ID’leri aramakla geçiyordu. Ödeme servisi hata verip PaymentFailed fırlattığında, stok servisinin bu eventi kaçırması (%1.2’lik kayıp oranı) sonucu 7.400 adet envanter açığı oluştu. Dağıtık bir sistemde Event Choreography (Olay Koreografisi) kullanmak, başlangıçta servisleri birbirinden bağımsızlaştırsa da, ölçeklendiğinde iş akışının (workflow) nerede olduğunu bilen tek bir otoritenin olmaması nedeniyle Mean Time To Recovery (MTTR) süremizi 14 dakikadan 4.2 saate çıkardı.
Bu rehberde, 150’den fazla mikroservisin iletişimde olduğu bir production ortamında, tamamen merkeziyetsiz event mimarisinden, Temporal tabanlı Saga Orchestration (Saga Orkestrasyonu) mimarisine sıfır kesintiyle nasıl geçtiğimizi, trade-off analizlerini ve geri dönüş stratejilerini adım adım inceleyeceğiz.
İçindekiler
- Mevcut Durum Analizi: Event Choreography Neden Tıkandı?
- Hedef Durum ve Trade-off Analizi: Neden Orchestration?
- Aşamalı Migration Planı: Strangler Fig Yaklaşımı
- Kod ve Konfigürasyon: Temporal ile Saga Uygulaması
- Geri Dönüş Stratejisi (Rollback) ve Risk Noktaları
- Pratik Öneriler / Production Notları
- Sık Sorulan Sorular
- Sonuç
Mevcut Durum Analizi: Event Choreography Neden Tıkandı?
Choreography yaklaşımında, merkezi bir kontrol birimi yoktur. Servisler arası iletişim Kafka veya RabbitMQ üzerinden domain event’leri ile sağlanır.
- Görünürlük (Observability) Kaybı: İş akışının güncel durumunu (state) sorgulayabileceğimiz bir veritabanı yoktu. “Sipariş #9983 nerede?” sorusunun cevabı, 4 farklı veritabanına dağılmış durumdaydı.
- Cyclomatic Complexity: Yeni bir iş kuralı eklendiğinde (Örn: Fraud kontrolü), mevcut 4 servisin event dinleme mantığını değiştirmemiz gerekiyordu. 17 farklı event tipinin birbirini tetiklediği bir senaryoda, test coverage %60’ın üzerine çıkamıyordu.
- Telafi (Compensation) Zorlukları: Dağıtık işlemlerde (Distributed Transactions) bir adım başarısız olduğunda, önceki adımların geri alınması (rollback) gerekir. Choreography’de bir telafi event’inin Dead Letter Queue‘ya (DLQ) düşmesi, sistemde tutarsız veriye (inconsistent state) yol açıyordu.
Hedef Durum ve Trade-off Analizi: Neden Orchestration?
Hedef mimaride, iş akışının durumunu tutan (stateful) ve hangi servisin ne zaman çalışacağına karar veren merkezi bir Orchestrator (Orkestratör) konumlandırdık. Bu mimaride mikroservisler iş mantığını (business logic) çalıştıran aptal işçilere (worker) dönüşür.
Trade-off Tablosu: Choreography vs Orchestration
| Metrik / Kavram | Event Choreography (Eski) | Saga Orchestration (Yeni) | Karar Gerekçemiz |
|---|---|---|---|
| p99 Latency | 45ms | 110ms | Orkestratörün veritabanı yazma maliyeti (state persistence) 65ms ekledi. İş tutarlılığı için bu gecikmeyi kabul ettik. |
| MTTR (Hata Çözüm Süresi) | 4.2 Saat | 12 Dakika | Merkezi UI üzerinden hatalı iş akışını tek tıkla Retry edebilmek, MTTR’da %95 iyileşme sağladı. |
| Coupling (Bağımlılık) | Çok Düşük | Yüksek (Mantıksal) | Domain servisleri birbirini bilmiyor, ancak hepsi Orkestratörün kontratına bağımlı hale geldi. |
| Single Point of Failure (SPOF) | Yok | Var (Orkestratör) | Temporal’ı 5 node’lu Cassandra cluster’ı ile destekleyerek %99.999 SLA sağladık. |
Aşamalı Migration Planı: Strangler Fig Yaklaşımı
Production ortamında günlük 350.000 sipariş işleyen bir sistemi bir gecede değiştiremezsiniz. Geçiş sürecini 3 faza böldük.
Faz 1: Shadow Orchestrator (Gölge Orkestrasyon – 1. ve 2. Hafta)
İlk aşamada Orkestratör hiçbir aksiyon almadı. Sadece Kafka’daki mevcut event’leri dinleyerek, kendi içindeki State Machine’i güncelledi. Amacımız, Orkestratörün mantığının production verisiyle doğru çalışıp çalışmadığını doğrulamaktı.
- Kafka Consumer’lar Orkestratöre bağlandı.
- Orkestratör, OrderCreated geldiğinde durumu “Started”, PaymentCompleted geldiğinde “PaymentDone” olarak işaretledi.
- Günün sonunda, eski sistemin veritabanı ile Orkestratörün veritabanını karşılaştıran bir Reconciliation (Mutabakat) scripti çalıştırdık. Hata oranı %0.01’in altına inene kadar bug fix yaptık.
Faz 2: Hybrid Routing ve Canary Release (3. ve 4. Hafta)
API Gateway (Kong) üzerinde bir Lua scripti ile trafik yönlendirmesi (traffic routing) yapılandırdık. Gelen isteklerin order_id hash değerinin son iki hanesi %10’un altındaysa, istek yeni Orkestratör API’sine, aksi halde eski Choreography yapısına yönlendirildi.
- Yeni akışta, Orkestratör mikroservislere doğrudan gRPC veya REST üzerinden komut gönderdi (Örn:
POST /payments/process). - Mikroservisler işi bitirince yine Orkestratöre senkron veya asenkron yanıt döndü.
- Eski servislerin event fırlatmasını (emit) engellemek için, feature flag’ler (Unleash) kullandık:
if (!unleash.isEnabled("orchestrator_mode_enabled")) { publishEvent(...) }.
Faz 3: Full Cut-over ve Temizlik (5. Hafta)
Trafiğin %100’ü Orkestratöre alındıktan sonra, 1 hafta boyunca monitoring yapıldı. Ardından eski kod blokları, Kafka’daki gereksiz event topic’leri ve dinleyici metodlar silindi. Kod satır sayısında (LOC) 4.500 satırlık azalma ölçüldü.
Kod ve Konfigürasyon: Temporal ile Saga Uygulaması
Temporal (veya Camunda), durumu veritabanında otomatik saklayan araçlardır. Aşağıda, Java ve Temporal SDK kullanarak uyguladığımız Sipariş Saga iş akışının gerçek kod parçacığını bulabilirsiniz.
// Sipariş Orkestrasyon Workflow Sınıfı
public class OrderWorkflowImpl implements OrderWorkflow {
// Aktivitelerin (Mikroservis çağrılarının) konfigürasyonu
private final ActivityOptions options = ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofSeconds(15)) // 15s timeout
.setRetryOptions(RetryOptions.newBuilder()
.setInitialInterval(Duration.ofSeconds(1))
.setMaximumAttempts(3)
.build())
.build();
// Activity Stub'lar (Gerçek mikroservis çağrılarını soyutlar)
private final OrderActivities activities = Workflow.newActivityStub(OrderActivities.class, options);
@Override
public void processOrder(OrderDTO order) {
// Saga yapısını başlat (Paralel telafiye izin ver)
Saga saga = new Saga(new Saga.Options.Builder().setParallelCompensation(true).build());
try {
// 1. Adım: Stok Rezervasyonu
saga.addCompensation(activities::releaseInventory, order.getItems());
activities.reserveInventory(order.getItems());
// 2. Adım: Ödeme Alınması
saga.addCompensation(activities::refundPayment, order.getAmount());
activities.processPayment(order.getAmount());
// 3. Adım: Kargo Planlama
activities.scheduleShipping(order.getId());
} catch (ActivityFailure e) {
// Hata durumunda kayıtlı telafi (compensation) metodlarını ters sırayla çalıştır
saga.compensate();
throw ApplicationFailure.newFailure("Sipariş iş akışı iptal edildi: " + e.getMessage(), "SAGA_FAILED");
}
}
}
Bu kodda en kritik nokta saga.addCompensation() metodudur. Bir adım atılmadan hemen önce, o adımın başarısız olması durumunda çalışacak geri alma mantığı (undo) Saga kuyruğuna eklenir. Catch bloğuna düşüldüğünde Temporal, eklenen bu metodları otomatik olarak trigger eder ve %100 transaction bütünlüğü sağlar.
Geri Dönüş Stratejisi (Rollback) ve Risk Noktaları
Migration sırasında en büyük kabus, veritabanı şemasında veya state’te geri dönülemez bir bozulma yaratmaktır. Rollback stratejimizi 3 temel prensibe dayandırdık:
1. Dual-Write (Çift Yazma) İzolasyonu
Faz 2 sırasında hem eski sistem hem de yeni Orkestratör aynı veritabanına yazıyordu. Çakışmayı önlemek için UUID çakışmalarını engelleyen UUIDv7 yapısına geçtik. Eğer Orkestratör akışında bir hata p99 metriklerini 500ms’nin üzerine çıkarsaydı, API Gateway üzerinden saniyeler içinde trafiği eski akışa (Event Choreography) döndürecek konfigürasyon hazırdı.
2. Kafka Offset Yönetimi
Trafiği geri almamız gerektiğinde, eski Choreography servislerinin aradaki zaman diliminde fırlatılan event’leri kaçırmaması gerekiyordu. Orkestratör çalışırken dahi, mikroservisler Audit Queue adı verilen pasif bir Kafka topic’ine event fırlatmaya devam etti. Rollback durumunda eski servisler bu topic’teki offset’i okumaya başlayacaktı.
3. Risk Noktası: Idempotency (Eşetkisellik)
“Bir Orkestratörün en büyük düşmanı, aynı komutu iki kez alan ancak birini işleyip diğerini tekrar eden mikroservislerdir.”
Temporal gibi orkestratörler, timeout durumunda komutları tekrar dener (Retry). Eğer Ödeme Servisiniz Idempotent değilse, müşteriden iki kez para çekersiniz. Bu riski bertaraf etmek için, tüm mikroservislere Redis tabanlı bir Idempotency-Key filtresi ekledik. Gelen istekteki UUID Redis’te varsa, işlem yapılmadan HTTP 200 OK ve önceki işlemin sonucu döndürüldü.
Pratik Öneriler / Production Notları
Bu geçişi yapmayı planlayan mimarlar için production ortamından çıkardığımız acı dersleri bir checklist haline getirdim:
- Senkron Çağrıları Sınırlandırın: Orkestratörden mikroservislere yapılan çağrılar senkron (HTTP REST/gRPC) ise, Thread havuzunuz hızlıca tükenebilir. 200ms’den uzun süren işlemler için Async Activity Completion (Asenkron Aktivite Tamamlama) pattern’ini kullanın.
- Payload Boyutu: Orkestratörün state history’sine (durum geçmişi) base64 formatında 5MB’lık PDF faturası kaydetmeyin. State veritabanı (Cassandra veya PostgreSQL) şişer. Sadece referans ID’leri (S3 bucket linki gibi) taşıyın.
- Monitoring ve Alarmlar: Orkestratör üzerinde
workflow_failed_countveschedule_to_start_latencymetriklerini Prometheus ile izleyin. Bir aktivitenin başlaması 5 saniyeden uzun sürüyorsa worker sayınızı artırmanız gerekiyordur. - DLQ Sizing: Compensate edilemeyen işlemler için DLQ mekanizmanız her zaman olsun. Eğer ödeme iadesi (refund) 3 kez denenip başarısız olursa, iş akışını manual inceleme (Human in the loop) durumuna çekecek bir mekanizma tasarlayın.
Sık Sorulan Sorular
Orkestratör Single Point of Failure (SPOF) yaratmaz mı?
Modern orkestratörler (Temporal, AWS Step Functions) dağıtık mimariler için tasarlanmıştır. Temporal cluster’ını 3 farklı Availability Zone (AZ) üzerine kurup, backend olarak multi-node Cassandra veya CockroachDB kullandığınızda, servis düzeyi SPOF riski ortadan kalkar. Bizim senaryomuzda Orkestratör altyapısının kesinti süresi yıllık 4.3 dakika olmuştur.
Tüm mikroservisleri yeniden yazmamız mı gerekiyor?
Hayır. Mikroservislerin içindeki iş mantığı (business logic) değişmez. Sadece tetiklenme şekilleri değişir. Eskiden Kafka topic dinleyen bir metodu, Orkestratörün çağırabileceği bir API endpoint’ine veya Worker methoduna çevirmeniz (adapter pattern) yeterlidir.
Hangi senaryoda Event Choreography’de kalmalıyım?
Eğer iş akışınız sadece 2 veya 3 servisten oluşuyorsa, iş kuralı (business rule) değişiklikleri çok nadirse ve 100ms’nin altındaki ağ gecikmeleri sizin için kritikse (Örn: High-Frequency Trading sistemleri), Choreography’nin düşük gecikmeli (low latency) doğasından faydalanmaya devam etmelisiniz. Kompleksite 4 servisi ve telafi işlemlerini aştığında Orchestration şarttır.
Orkestrasyonun veritabanı yükü (I/O) çok fazla değil mi?
Evet, her durum geçişi veritabanına yazılır. 4.500 TPS’lik bir sistemde bu, saniyede yaklaşık 18.000 veritabanı işlemi (write IOPS) demektir. SSD tabanlı diskler ve shard’lanmış bir veritabanı mimarisi kullanmanız zorunludur. Biz bu yükü karşılamak için AWS r5.4xlarge instance’lar kullandık.
Sonuç
Event Choreography’den Saga Orchestration’a geçiş, sadece bir teknoloji değişimi değil, sistemin kontrol felsefesinin değişimidir. 5 haftalık migration sürecinin sonunda, müşteri destek biletlerinin çözüm süresi 4 saatten dakikalar seviyesine indi. Kayıp event’ler nedeniyle yaşanan veri tutarsızlığı oranı %1.2’den %0.0001’e düştü ve development takımlarının yeni bir iş akışı ekleme eforu 14 adam/günden 3 adam/güne indi.
Eğer sizin sisteminizde de izlenebilirlik bir kabusa dönüştüyse ve “Bu event’i kim fırlattı, kim dinledi?” soruları günlük rutin haline geldiyse, mevcut event yapınızı bir gölge orkestratör (shadow orchestrator) ile dinlemeye başlayarak merkezi kontrolün ilk adımını bugün atabilirsiniz.
Bunları da beğenebilirsiniz

Triton Inference Server ile Fraud Tespitinde Gecikmeyi 300ms’den 25ms’ye Düşürmek: Dynamic Batching Vaka Çalışması
Ödeme altyapısındaki 4500 TPS yük altında ezilen FastAPI tabanlı fraud modelinin p99 çıkarım süresini, Triton ve ONNX kullanarak 300ms’den 25ms’ye nasıl indirdiğimizin üretim ortamı notları.
Devamını Oku

Otonom Veritabanı Şema Evrimi: AI Ajanları ile Sıfır Kesinti Süreli Migrasyon
AI tabanlı ajanların veritabanı şema yönetimini nasıl otomatikleştirdiğini ve sıfır kesinti süreli migrasyon mimarilerinin teknik detaylarını bu makalede keşfedin.
Devamını Oku

ClickHouse Dağıtık Tablo Mimarisinde Data Skew: Sharding Key Seçimi ve Resharding Stratejileri
ClickHouse kümelerinde performans darboğazlarına yol açan data skew (veri dengesizliği) problemini gidermek için doğru sharding key seçimi ve gelişmiş resharding tekniklerini keşfedin.
Devamını Oku