Veritabanı “Başarılı” Dedi, Mesaj Brokerı “Tekrar Dene” Neden? Dağıtık Sistemlerde Tutarlılık Zorlukları
Modern yazılım mimarilerinde, özellikle mikroservislerin yaygınlaşmasıyla birlikte, sistemler arasındaki iletişim ve veri tutarlılığı her zamankinden daha karmaşık hale geldi. Bir işlemin veritabanında başarıyla tamamlanmasına rağmen, ilgili bir olayın mesaj brokerına iletilememesi, birçok geliştiricinin kâbusu haline gelebilir. Bu durum, sistemler arasında veri tutarsızlıklarına yol açarak iş süreçlerini aksatabilir ve kullanıcı deneyimini olumsuz etkileyebilir. Peki, veritabanı “başarılı” derken, mesaj brokerı neden “tekrar dene” der ve bu çelişkiyi nasıl çözebiliriz?
Asenkron İşlemlerin Karanlık Yüzü: Tutarsızlıklar Neden Ortaya Çıkar?
Günümüzün ölçeklenebilir ve esnek uygulamaları genellikle asenkron (eş zamansız) iletişim modellerini benimser. Bu modellerde, bir işlem başlatıldığında, sonucun anında dönmesi beklenmez; bunun yerine, işlem arka planda yürütülür ve sonuç daha sonra iletilir. Bu yaklaşım, sistem performansını artırır ve kaynak kullanımını optimize ederken, beraberinde önemli zorlukları da getirir. Özellikle, bir işlemin birden fazla bağımsız bileşeni etkilediği dağıtık sistemlerde, bu bileşenlerin tümünün aynı anda başarılı olması garanti edilemez.
Tipik bir senaryoda, bir kullanıcı bir e-ticaret sitesinde sipariş verdiğinde, bu işlem sadece siparişin veritabanına kaydedilmesiyle bitmez. Aynı zamanda, ödeme sistemine bir bildirim gönderilmesi, envanterin güncellenmesi, müşteriye e-posta onayı gönderilmesi ve belki de kargo şirketine sipariş bilgisinin iletilmesi gibi birçok ardışık veya paralel adım tetiklenir. Bu adımların her biri farklı servisler tarafından yönetilebilir ve genellikle mesaj brokerları (message brokers) aracılığıyla iletişim kurarlar. Örneğin, sipariş servisi veritabanına kaydı yaptıktan sonra, bir OrderCreated (Sipariş Oluşturuldu) olayını Kafka veya RabbitMQ gibi bir mesaj brokerına yayımlar. Diğer servisler bu olayı dinleyerek kendi iş mantıklarını yürütürler.
Ancak, burada kritik bir nokta ortaya çıkar: Veritabanına yazma işlemi başarılı olduğunda, mesaj brokerına mesaj gönderme işlemi de aynı atomik (atomic) işlem içinde mi gerçekleşir? Çoğu zaman hayır. Veritabanı işlemi kendi içinde bir atomik birimdir (ACID özelliklerini sağlar: Atomicity, Consistency, Isolation, Durability). Yani ya tamamen başarılı olur ya da tamamen geri alınır (rollback). Ancak, veritabanı işlemi başarılı olduktan sonra ayrı bir adım olarak mesaj brokerına mesaj göndermeye çalışıldığında, bu iki işlem birbirinden bağımsız hale gelir. Eğer tam bu noktada bir ağ kesintisi, mesaj brokerının geçici olarak kullanılamaması veya uygulamanın kendisinin çökmesi gibi bir sorun yaşanırsa, veritabanındaki veri güncelken, ilgili olay mesaj brokerına ulaşamaz.
Bu durum, sistemde bir tutarsızlık yaratır. Veritabanında sipariş “oluşturuldu” olarak görünürken, ödeme servisi, envanter servisi veya e-posta bildirim servisi bu siparişten haberdar olmaz. Sonuç olarak, müşteri ödeme yapmasına rağmen ürününü alamaz, envanter güncellenmediği için stok hataları yaşanır veya müşteri onay e-postası almadığı için endişelenir. Bu tür tutarsızlıklar, manuel müdahale gerektiren karmaşık sorunlara yol açar, operasyonel maliyetleri artırır ve müşteri memnuniyetini ciddi şekilde düşürür. Bu nedenle, dağıtık sistemlerde bu tür senaryoları öngörmek ve güvenilir çözümler geliştirmek, modern yazılım mimarilerinin temel taşlarından biridir.
Temel Kavramlar: Veritabanı, Mesaj Brokerı ve Atomik İşlemler Nedir?
Bu karmaşık konuyu anlamak için öncelikle temel yapı taşlarını netleştirmemiz gerekiyor. Veritabanları, mesaj brokerları ve atomik işlemler, dağıtık sistemlerin temel bileşenleridir ve her birinin kendine özgü rolleri ve zorlukları bulunur.
Veritabanı İşlemleri ve ACID Özellikleri
Bir veritabanı (database), verileri düzenli bir şekilde depolayan, yöneten ve erişim sağlayan bir sistemdir. İlişkisel veritabanları (relational databases) genellikle ACID (Atomicity, Consistency, Isolation, Durability – Atomiklik, Tutarlılık, İzolasyon, Kalıcılık) prensiplerini destekler. Bu prensipler, veritabanı işlemlerinin güvenilirliğini sağlar:
- Atomiklik (Atomicity): Bir işlemdeki tüm adımlar ya hep birlikte başarılı olur (commit) ya da hiçbiri gerçekleşmez (rollback). Yani, bir işlem bölünemez bir birimdir.
- Tutarlılık (Consistency): Bir işlem başladığında ve bittiğinde, veritabanı tutarlı bir durumda olmalıdır. İşlem, veritabanının bütünlük kısıtlamalarını (integrity constraints) ihlal etmemelidir.
- İzolasyon (Isolation): Eş zamanlı (concurrent) çalışan işlemler birbirini etkilememelidir. Her işlem, sanki sistemde tek başına çalışıyormuş gibi görünmelidir.
- Kalıcılık (Durability): Başarıyla tamamlanan (commit edilen) bir işlemin sonuçları, sistem arızası durumunda bile kalıcı olmalıdır.
Bu özellikler sayesinde, bir veritabanına yapılan kayıt işlemi, örneğin bir siparişin kaydedilmesi, ya tamamen gerçekleşir ve kalıcı olur ya da herhangi bir hata durumunda tamamen geri alınır, veritabanını önceki tutarlı durumuna döndürür. Bu, veritabanı içindeki tutarlılığı sağlamak için kritik öneme sahiptir.
Mesaj Brokerları ve Asenkron İletişim
Mesaj brokerı (message broker), farklı uygulamaların veya servislerin birbirleriyle asenkron olarak iletişim kurmasını sağlayan bir ara yazılımdır (middleware). Kafka, RabbitMQ, ActiveMQ, Azure Service Bus veya AWS SQS gibi popüler mesaj brokerları, mesajları göndericiden (producer) alıp alıcıya (consumer) iletmek için bir aracı görevi görür. Temel işlevleri şunlardır:
- Mesaj Kuyrukları (Message Queues): Mesajları geçici olarak depolayarak, alıcının meşgul olması veya çevrimdışı olması durumunda bile mesajların kaybolmamasını sağlar.
- Yayınla/Abone Ol (Publish/Subscribe – Pub/Sub) Deseni: Göndericilerin belirli konulara (topics) mesaj yayımlamasını, alıcıların ise bu konulara abone olarak ilgili mesajları almasını sağlar. Bu, gönderici ve alıcı arasındaki bağımlılığı azaltır.
- Yük Dengeleme (Load Balancing): Birden fazla tüketici olduğunda, mesajları aralarında dağıtarak iş yükünü dengeleyebilir.
- Güvenilirlik: Mesajların teslimatını garanti etmek için çeşitli mekanizmalar (kalıcı mesajlar, onay mekanizmaları) sunar.
Mesaj brokerları, dağıtık sistemlerde servisler arası iletişimi kolaylaştırır, sistemin esnekliğini ve ölçeklenebilirliğini artırır. Bir servis, bir olayı mesaj brokerına göndererek işini tamamlar ve diğer servislerin bu olayla ne yapacağını dert etmez. Ancak, bu ayrışma, yukarıda bahsettiğimiz tutarsızlık sorunlarının da ana kaynağı olabilir.
Atomik İşlemler ve Dağıtık Sistemlerdeki Zorlukları
Atomik işlem kavramı, tek bir mantıksal iş birimi olarak kabul edilen ve ya tamamen başarılı olan ya da tamamen başarısız olan bir dizi operasyonu ifade eder. Veritabanları bu atomikliği kendi sınırları içinde garanti ederken, dağıtık bir sistemde birden fazla bağımsız bileşeni (örneğin, bir veritabanı ve bir mesaj brokerı) kapsayan atomik bir işlem sağlamak çok daha zordur. Bu durum, dağıtık işlem (distributed transaction) olarak adlandırılır.
Geleneksel olarak, dağıtık işlemler için İki Fazlı Taahhüt (Two-Phase Commit – 2PC) gibi protokoller geliştirilmiştir. 2PC, bir koordinatörün tüm katılımcılardan (veritabanları, mesaj kuyrukları vb.) işlemi taahhüt etmeye hazır olduklarına dair onay almasını (prepare fazı) ve ardından tüm katılımcılara taahhüt etme veya geri alma komutunu göndermesini (commit fazı) içerir. Ancak 2PC, performans darboğazları, kilitlenme riski ve tek hata noktası (single point of failure) gibi ciddi sınırlamalara sahiptir. Bu nedenle, modern mikroservis mimarilerinde genellikle 2PC’den kaçınılır ve bunun yerine olası tutarlılık (eventual consistency) modelleri tercih edilir.
Olası tutarlılık, sistemdeki verilerin anında tutarlı olmak zorunda olmadığı, ancak bir süre sonra tüm kopyaların nihayetinde aynı değere ulaşacağı anlamına gelir. Bu model, yüksek ölçeklenebilirlik ve kullanılabilirlik sunarken, geliştiricilerin tutarsızlık pencerelerini yönetmek ve bu durumları telafi etmek için özel stratejiler geliştirmesini gerektirir. İşte bu noktada, veritabanı “başarılı” dediğinde mesaj brokerının “tekrar dene” demesi gibi durumlar, olası tutarlılığın en zorlayıcı yüzlerinden biri olarak karşımıza çıkar.
Problem Senaryosu: Veritabanı Başarısı ve Mesaj Brokerı Hatası Nasıl Mümkün Olur?
Dağıtık sistemlerde yaşanan bu tutarsızlıkların temelinde, iki farklı ve bağımsız kaynağın (veritabanı ve mesaj brokerı) tek bir mantıksal işlemin parçası olmasına rağmen, kendi içlerinde atomikliklerini koruyup, birbirleriyle olan iletişimin atomik olmaması yatar. Hadi bu durumu adım adım inceleyelim ve neden bir sorun haline geldiğini görelim.
Tipik İş Akışı ve Hata Noktası
Bir uygulamanın tipik bir iş akışını ele alalım:
- Uygulama Veritabanına Kayıt Yapar: Bir kullanıcı yeni bir hesap oluşturduğunda veya bir ürün satın aldığında, uygulamanın ilgili servisi (örneğin, Kullanıcı Servisi veya Sipariş Servisi) verileri kendi veritabanına kaydeder. Bu kayıt işlemi, veritabanı içinde bir işlem (transaction) olarak başlar.
- Veritabanı İşlemi Başarıyla Tamamlanır (COMMIT): Eğer kayıt işlemi sırasında herhangi bir hata oluşmazsa (örneğin, benzersiz anahtar ihlali, veri tipi uyuşmazlığı vb.), veritabanı işlemi başarıyla taahhüt edilir (COMMIT). Bu, verilerin kalıcı olarak depolandığı ve diğer veritabanı işlemleri için görünür hale geldiği anlamına gelir. Veritabanı, “başarılı” dedi.
- Uygulama İlgili Bir Olayı Mesaj Brokerına Göndermeye Çalışır: Veritabanı işlemi tamamlandıktan hemen sonra, uygulama, bu değişikliği diğer servislere bildirmek için bir mesaj (olay) oluşturur. Örneğin,
UserCreatedEvent(Kullanıcı Oluşturuldu Olayı) veyaOrderPlacedEvent(Sipariş Verildi Olayı) gibi bir olay mesaj brokerına gönderilmek üzere hazırlanır. - Mesaj Brokerına Mesaj Gönderilemez: Tam bu kritik anda, mesaj brokerı ile uygulama arasındaki bağlantıda bir sorun yaşanır. Bu sorunlar çeşitli nedenlerden kaynaklanabilir:
- Ağ Hatası: Uygulama sunucusu ile mesaj brokerı sunucusu arasındaki ağ bağlantısı geçici olarak kesilmiş olabilir.
- Mesaj Brokerı Kullanılamaz Durumda: Mesaj brokerı sunucusu çökebilir, yeniden başlatılıyor olabilir veya aşırı yük nedeniyle mesaj kabul etmeyi reddedebilir.
- Uygulama Hatası: Uygulamanın kendisinde, mesajı doğru formatta oluşturma veya gönderme mantığında bir hata olabilir.
- Zaman Aşımı: Mesaj gönderme işlemi belirli bir süre içinde tamamlanamaz ve zaman aşımına uğrar.
Bu gibi durumlarda, mesaj brokerı uygulamaya bir hata mesajı döner veya bağlantı kopar. Mesaj brokerı, “tekrar dene” veya “işlem başarısız” der.
Bu Durum Ne Gibi Sorunlara Yol Açar?
Yukarıdaki senaryonun en büyük sonucu, sistemde oluşan veri tutarsızlığıdır. Veritabanında bir gerçeklik (örneğin, yeni bir kullanıcı kaydı), ancak diğer sistemlerde farklı bir gerçeklik (diğer servisler bu yeni kullanıcıdan habersiz) bulunur. Bu tutarsızlıklar ciddi iş sorunlarına yol açabilir:
- E-ticaret Örneği (Sipariş Onayı): Bir müşteri online mağazadan sipariş verdi. Sipariş bilgileri veritabanına başarıyla kaydedildi. Ancak,
OrderPlacedEventmesaj brokerına gönderilemedi. Sonuç olarak:- Ödeme servisi, siparişin verildiğinden haberdar olmadığı için ödeme işlemini başlatamaz.
- Envanter servisi, ürün stoğunu düşüremez.
- E-posta servisi, müşteriye sipariş onayı gönderemez.
- Kargo servisi, gönderi hazırlığına başlayamaz.
Müşteri, siparişinin başarılı olduğunu düşünürken, aslında hiçbir şey ilerlememiştir. Bu durum, müşteri şikayetlerine, iade taleplerine ve marka itibarının zedelenmesine neden olur.
- Kullanıcı Yönetimi: Yeni bir kullanıcı başarıyla veritabanına kaydedildi. Ancak,
UserCreatedEventmesajı broker’a ulaşamadı. Bu durumda:- Kimlik doğrulama servisi, yeni kullanıcının kimlik bilgilerini senkronize edemez.
- Bildirim servisi, hoş geldiniz e-postasını veya SMS’ini gönderemez.
- Analitik servisleri, yeni kullanıcı kaydını raporlayamaz.
Kullanıcı, hesabına giriş yapamayabilir veya beklediği karşılama e-postasını almayabilir, bu da hayal kırıklığı yaratır.
Bu tür senaryolar, sistemin güvenilirliğini ve tutarlılığını ciddi şekilde tehlikeye atar. Geliştiricilerin bu zorlukları aşmak için sağlam ve hata toleranslı çözümler tasarlaması gerekmektedir. Aksi takdirde, operasyonel ekipler sürekli olarak manuel müdahalelerle bu tutarsızlıkları gidermeye çalışmak zorunda kalır ki bu da sürdürülebilir bir durum değildir.
Sorunun Kökenleri: Dağıtık Sistemlerde Güvenilirlik Neden Zorludur?
Dağıtık sistemlerin doğası gereği, birden fazla bağımsız bileşenin (servisler, veritabanları, mesaj brokerları vb.) aynı anda hatasız çalışmasını beklemek gerçekçi değildir. Bu karmaşıklık, güvenilirliği sağlamayı zorlaştıran çeşitli faktörlerden kaynaklanır:
- Ağ Gecikmeleri ve Hataları: İnternet veya yerel ağlar, doğası gereği güvenilmezdir. Paket kayıpları, gecikmeler, bant genişliği sorunları ve bağlantı kopmaları her zaman yaşanabilir. Uygulama ile mesaj brokerı arasındaki iletişim bu ağ üzerinden gerçekleştiği için, ağdaki herhangi bir sorun mesaj iletimini engelleyebilir. Ağın durumu sürekli değiştiği için, bir an önce başarılı olan bir gönderim, bir sonraki anda başarısız olabilir.
- Sistem Arızaları: Bir mesaj brokerı sunucusu çökebilir, bakım nedeniyle kapatılabilir, disk alanı dolabilir veya beklenmedik bir yazılım hatası nedeniyle hizmet dışı kalabilir. Aynı şekilde, mesajı gönderen uygulama servisi de çökebilir veya yeniden başlatılabilir. Bu tür arızalar, mesajın gönderilmesini veya işlenmesini engeller. Tek bir bileşenin arızalanması, tüm dağıtık işlemin başarısız olmasına yol açabilir.
- Zaman Aşımları ve Tekrar Denemeler: Dağıtık sistemlerde bir işlem sonsuza kadar bekleyemez. Bir servis, başka bir servisten yanıt beklerken belirli bir zaman aşımı süresi tanımlar. Eğer bu süre içinde yanıt gelmezse, işlemi başarısız olarak işaretler. Bu durumda, mesajın gerçekten gönderilip gönderilmediği veya sadece yanıtın geciktiği belirsizleşir (network partition – ağ bölümlemesi). Tekrar deneme mekanizmaları bu belirsizliği yönetmek için kullanılır, ancak doğru yapılandırılmazlarsa, aynı mesajın birden çok kez gönderilmesine (duplicate messages) veya sistemin aşırı yüklenmesine neden olabilirler. Ne zaman tekrar denemeli, ne zaman vazgeçmeli ve ne zaman bir hatayı kalıcı olarak kabul etmeli soruları karmaşıktır.
- İki Fazlı Taahhüt (Two-Phase Commit – 2PC) ve Sınırlılıkları: Daha önce bahsedildiği gibi, 2PC, birden fazla kaynağı kapsayan atomik işlemleri sağlamak için bir protokoldür. Ancak, koordinatörün tek hata noktası olması, uzun süreli kilitlenmeler ve düşük performans gibi ciddi sınırlamalara sahiptir. Özellikle yüksek performans ve ölçeklenebilirlik gerektiren modern mikroservis mimarilerinde 2PC genellikle tercih edilmez. Çünkü 2PC, katılımcıların birbirlerini beklemesini gerektirir, bu da sistemin genel yanıt süresini ve iş hacmini (throughput) düşürür.
- Veri Tutarlılığı Modelleri: Dağıtık sistemlerde genellikle “anında tutarlılık” (strong consistency) yerine “olası tutarlılık” (eventual consistency) tercih edilir. Olası tutarlılık, sistemin bir noktada tutarsız olabileceği ancak zamanla tutarlı hale geleceği anlamına gelir. Bu model, yüksek kullanılabilirlik ve ölçeklenebilirlik sağlarken, geliştiricilerin tutarsızlık pencerelerini yönetmek için ek desenler ve stratejiler geliştirmesini gerektirir. Bu pencereler, yukarıda bahsedilen “veritabanı başarılı, mesaj brokerı tekrar dene” senaryolarının ortaya çıkmasına neden olur.
Bu faktörler bir araya geldiğinde, dağıtık sistemlerde güvenilirliği sağlamak, sadece kod yazmaktan öte, sistemin tüm bileşenlerinin etkileşimini, olası hata durumlarını ve bu hataların nasıl telafi edileceğini derinlemesine anlamayı gerektiren mimari bir meydan okumaya dönüşür.
Çözüm Yöntemleri: Bu Tutarsızlıkları Nasıl Önleyebiliriz?
Dağıtık sistemlerdeki bu tutarsızlıkları önlemek veya en aza indirmek için çeşitli mimari desenler ve stratejiler geliştirilmiştir. Bu çözümler, sistemin hem güvenilirliğini artırır hem de hata durumlarında daha zarif bir şekilde kurtarma yapmasını sağlar.
Idempotent İşlemler: Aynı İşlemin Tekrarı Sorun Yaratmasın
Idempotentlik (idempotence), bir işlemin birden fazla kez uygulanmasının, sanki tek bir kez uygulanmış gibi aynı sonucu vermesi durumudur. Dağıtık sistemlerde, ağ hataları veya tekrar deneme mekanizmaları nedeniyle aynı mesajın birden çok kez gönderilmesi yaygın bir durumdur. Eğer alıcı servisler idempotent değilse, aynı mesajı birden çok kez işleyerek veri tekrarına, hatalı durumlara veya yanlış hesaplamalara yol açabilirler. Örneğin, bir ödeme işlemi idempotent değilse, aynı sipariş için birden çok kez para çekilebilir.
Idempotentliği sağlamak için genellikle benzersiz bir işlem kimliği (transaction ID) kullanılır. Alıcı servis, bir mesajı işlerken önce bu kimliği kontrol eder. Eğer bu kimliğe sahip bir mesaj daha önce işlenmişse, mesajı tekrar işlemez veya sadece ilk işlemin sonucunu döner. Bu, özellikle mesaj brokerına mesaj gönderme işleminin başarısız olup, daha sonra tekrar denendiği durumlarda çok önemlidir.
// Pseudocode: Idempotent Ödeme İşlemi
function ProcessPayment(transactionId, orderId, amount) {
if (PaymentService.IsTransactionProcessed(transactionId)) {
console.log("Bu işlem zaten işlenmiş: " + transactionId);
return; // İşlemi tekrar yapma
}
// Ödeme işlemini gerçekleştir
PaymentGateway.Charge(orderId, amount);
// İşlemi işlenmiş olarak işaretle
PaymentService.MarkTransactionAsProcessed(transactionId);
console.log("Ödeme başarıyla işlendi: " + transactionId);
}
Bu örnekte, transactionId sayesinde aynı ödeme isteği birden fazla kez gelse bile, ödeme sadece bir kez işlenir.
Outbox Deseni (Transactional Outbox Pattern): Atomikliği Garanti Altına Almak
Outbox deseni, veritabanı işlemi ile mesaj gönderme işlemini atomik hale getirmek için kullanılan en yaygın ve etkili desenlerden biridir. Temel fikir şudur: mesajı doğrudan mesaj brokerına göndermek yerine, veritabanı işlemiyle birlikte aynı veritabanı işlemi içinde bir “outbox” (giden kutusu) tablosuna kaydedilir.
Nasıl Çalışır?
- Veritabanı İşlemi ve Outbox Kaydı: Uygulama, ana veritabanı işleminde (örneğin, bir sipariş kaydı) değişiklikleri yapar. Aynı veritabanı işlemi içinde, ilgili olayı temsil eden bir mesajı da özel bir
OutboxMessagestablosuna kaydeder. Bu iki kayıt (ana veri ve outbox mesajı) aynı veritabanı işlemi içinde olduğu için, ya ikisi de başarılı olur ya da ikisi de geri alınır. Bu, atomikliği garanti eder. - Mesaj Rölesi (Message Relay) Servisi: Ayrı bir servis veya süreç (message relay, outbox poller), düzenli aralıklarla
OutboxMessagestablosunu tarar. İşlenmemiş mesajları bulur. - Mesajı Broker’a Gönderme: Mesaj rölesi, bulduğu mesajları alır ve mesaj brokerına gönderir.
- Mesajı İşlenmiş Olarak İşaretleme: Mesaj brokerına başarıyla gönderildikten sonra, outbox tablosundaki mesajın durumu “işlenmiş” olarak güncellenir veya mesaj silinir.
Bu desenin en büyük avantajı, veritabanı ve mesaj gönderme arasında atomikliği sağlamasıdır. Mesaj brokerı geçici olarak kullanılamaz olsa bile, mesaj outbox tablosunda güvende bekler ve broker tekrar kullanılabilir olduğunda gönderilir. Ayrıca, mesaj rölesi idempotent bir şekilde çalışacak şekilde tasarlanabilir, böylece aynı mesajın birden çok kez gönderilmesi durumunda bile sorun yaşanmaz.
-- Örnek Outbox tablosu yapısı
CREATE TABLE OutboxMessages (
Id UUID PRIMARY KEY,
OccurredOn TIMESTAMP NOT NULL,
TypeName VARCHAR(255) NOT NULL,
Payload TEXT NOT NULL, -- JSON formatında mesaj içeriği
ProcessedOn TIMESTAMP,
Attempts INT DEFAULT 0
);
-- Veritabanı işlemi ve Outbox kaydı (pseudo-code)
BEGIN TRANSACTION; -- Veritabanı işlemi başlat
INSERT INTO Orders (Id, CustomerId, Amount, Status)
VALUES ('order-123', 'customer-456', 100.00, 'Pending');
INSERT INTO OutboxMessages (Id, OccurredOn, TypeName, Payload)
VALUES ('message-789', NOW(), 'OrderCreatedEvent', '{"orderId": "order-123", "customerId": "customer-456", "amount": 100.00}');
COMMIT; -- Veritabanı işlemi taahhüt et
Yukarıdaki kod örneğinde, hem sipariş kaydı hem de outbox mesaj kaydı tek bir veritabanı işlemi içinde gerçekleşir. Bu, veritabanında bir sipariş varsa, outbox’ta da o siparişin olay mesajının mutlaka olacağını garanti eder.
Saga Deseni (Saga Pattern): Uzun Süreli Dağıtık İşlemler İçin
Saga deseni, birden fazla yerel işlemden (local transactions) oluşan uzun süreli, dağıtık işlemlerin yönetimi için kullanılır. Her yerel işlem kendi veritabanı içinde atomiktir, ancak tüm saga genelinde atomiklik, telafi edici işlemler (compensating transactions) ile sağlanır. Bir adım başarısız olursa, önceki adımların etkilerini geri almak için telafi edici işlemler tetiklenir.
Saga’lar iki şekilde uygulanabilir:
- Koreografi (Choreography): Servisler doğrudan birbirleriyle olaylar aracılığıyla iletişim kurar. Her servis, bir olayı yayımlar ve diğer servisler bu olayı dinleyerek kendi işlemlerini başlatır. Bu, merkezi bir koordinatöre ihtiyaç duymadığı için daha esnek olabilir, ancak iş akışının takibi daha zorlaşabilir.
- Orkestrasyon (Orchestration): Merkezi bir orkestratör (saga orkestratörü), tüm iş akışını yönetir. Orkestratör, her servise hangi işlemi yapacağını söyler ve servislerden yanıt bekler. Bir adım başarısız olursa, orkestratör telafi edici işlemleri başlatır. Bu, iş akışının daha net görünmesini sağlar ancak orkestratörün tek hata noktası olma riskini taşır.
Saga deseni, özellikle karmaşık iş akışlarına sahip, birden fazla mikroservisi ve veritabanını içeren senaryolarda kullanılır. Örneğin, bir rezervasyon sistemi, hem otel rezervasyonu, hem uçak bileti alımı, hem de araç kiralama gibi adımları içeren bir saga ile yönetilebilir.
Tekrar Deneme Mekanizmaları ve Geri Çekilme Stratejileri (Retry Mechanisms & Backoff Strategies)
Geçici ağ hataları veya servislerin kısa süreli kullanılamazlığı durumlarında, işlemleri tekrar denemek (retry) akıllıca bir stratejidir. Ancak, tekrar denemeleri doğru bir şekilde yönetmek önemlidir:
- Üstel Geri Çekilme (Exponential Backoff): Tekrar denemeler arasında giderek artan bir bekleme süresi uygulamaktır (örneğin, 1 saniye, 2 saniye, 4 saniye, 8 saniye…). Bu, sistemin aşırı yüklenmesini önler ve geçici sorunların çözülmesi için zaman tanır.
- Jitter Ekleme: Geri çekilme sürelerine rastgele bir miktar eklemek, tüm tekrar denemelerinin aynı anda çakışmasını (thundering herd problem) önler.
- Maksimum Deneme Sayısı: Sonsuz tekrar denemek yerine, belirli bir sayıda deneme sonrası işlemi başarısız olarak işaretlemek ve Dead Letter Queue (DLQ) gibi bir yere göndermek önemlidir.
- Circuit Breaker (Devre Kesici) Deseni: Bir servis sürekli olarak başarısız oluyorsa, ona istek göndermeyi geçici olarak durduran bir mekanizmadır. Bu, başarısız servisin daha fazla yüklenmesini önler ve sistemin genel sağlığını korur. Belirli bir süre sonra servis tekrar denenebilir.
İzleme ve Uyarı (Monitoring & Alerting): Tutarsızlıkları Erken Tespit Etme
Hiçbir çözüm %100 kusursuz değildir. Bu nedenle, sistemdeki olası tutarsızlıkları ve hataları erken tespit etmek için güçlü izleme (monitoring) ve uyarı (alerting) sistemleri kurmak hayati öneme sahiptir. Mesaj kuyruklarında biriken mesaj sayıları, başarısız mesaj teslimatları, outbox tablosunda uzun süre işlenmeyi bekleyen mesajlar gibi metrikler izlenmelidir. Anormal durumlar algılandığında, ilgili ekiplere otomatik uyarılar gönderilmelidir. Bu, sorunlara hızlıca müdahale edilmesini ve potansiyel veri tutarsızlıklarının büyümeden giderilmesini sağlar.
Bu çözüm yöntemleri, tek başına veya kombinasyon halinde kullanılarak dağıtık sistemlerdeki güvenilirlik ve tutarlılık sorunlarının üstesinden gelmeye yardımcı olur. Doğru deseni seçmek, uygulamanın özel gereksinimlerine, ölçeklenebilirlik ihtiyaçlarına ve hata toleransı beklentilerine bağlıdır.
Vaka Analizi: Gerçek Dünya Uygulamalarında Tutarlılık Sorunları ve Çözümleri
Teorik bilgileri somutlaştırmak için, gerçek bir e-ticaret platformunun sipariş işleme sürecindeki tutarlılık sorunlarını ve bu sorunlara uygulanan çözümleri inceleyelim. Bu vaka analizi, “Veritabanı ‘Başarılı’ Dedi, Mesaj Brokerı ‘Tekrar Dene'” senaryosunun pratikte nasıl ortaya çıktığını ve Outbox deseni gibi yaklaşımların nasıl bir kurtarıcı olduğunu gösterecektir.
Senaryo: E-ticaret Platformunda Sipariş İşleme
Bir e-ticaret platformunda, müşteri bir ürün sepetini onaylayıp “Sipariş Ver” butonuna tıkladığında aşağıdaki adımlar beklenir:
- Sipariş Oluşturma: Sipariş Servisi, müşterinin sepetindeki ürünleri ve adres bilgilerini alarak yeni bir sipariş kaydını kendi veritabanına (örneğin, PostgreSQL) yapar.
- Ödeme İşlemi: Siparişin ödemesinin alınması için Ödeme Servisi’ne bir mesaj gönderilir.
- Envanter Güncelleme: Sipariş edilen ürünlerin stoktan düşülmesi için Envanter Servisi’ne bir mesaj gönderilir.
- Müşteri Bildirimi: Müşteriye sipariş onayı e-postası göndermek için E-posta Servisi’ne bir mesaj gönderilir.
- Kargo Hazırlığı: Kargo firmasına gönderi bilgilerini iletmek için Kargo Servisi’ne bir mesaj gönderilir.
Bu akışta, Sipariş Servisi, veritabanına siparişi kaydettikten sonra, diğer servisleri bilgilendirmek için bir mesaj brokerı (örneğin, Kafka) kullanır. Yani, Sipariş Servisi, veritabanına kaydı yaptıktan sonra OrderCreatedEvent (Sipariş Oluşturuldu Olayı) mesajını Kafka’ya yayımlar. Ödeme, Envanter, E-posta ve Kargo servisleri bu olaya abone olmuştur ve kendi iş mantıklarını bu olayı aldıklarında tetiklerler.
Problem Ortaya Çıkıyor: Veritabanı Kaydı Başarılı, Kafka Hatası
Bir gün, platformda yoğun bir kampanya dönemi yaşanıyor. Milyonlarca sipariş aynı anda işlenmeye çalışılıyor. Bu yoğunluk sırasında, Sipariş Servisi veritabanına yeni siparişleri başarıyla kaydediyor. Ancak, tam bu sırada Kafka kümesinde geçici bir ağ kesintisi veya broker sunucularından birinin aşırı yüklenmesi yaşanıyor. Sipariş Servisi, OrderCreatedEvent mesajını Kafka’ya göndermeye çalıştığında, Kafka’dan bir hata yanıtı alıyor veya bağlantı kopuyor.
Sonuç: Sipariş veritabanında “oluşturuldu” durumunda, ancak Kafka’ya mesaj gönderilemediği için Ödeme Servisi ödeme işlemini başlatamıyor, Envanter Servisi stokları düşüremiyor ve müşteriye onay e-postası gitmiyor. Müşteriler, siparişlerinin “beklemede” kaldığını görüyor ve şikayetler yağmaya başlıyor. Operasyonel ekip, veritabanında olan siparişlerin neden işlenmediğini anlamaya çalışırken büyük bir karmaşa yaşıyor.
Çözüm Uygulaması: Outbox Deseni ve Idempotent İşlemler
Bu tür bir felaketi önlemek için e-ticaret platformu, Sipariş Servisi’ne Outbox Deseni’ni entegre etmeye karar verdi. Ayrıca, Ödeme Servisi gibi kritik servisler için idempotent işlem yeteneğini de ekledi.
Outbox Deseni Uygulaması:
- Outbox Tablosu: Sipariş Servisi’nin veritabanına
OutboxMessagesadında yeni bir tablo eklendi. Bu tablo, gönderilmesi gereken tüm olay mesajlarını depolayacak. - Atomik Kayıt: Sipariş Servisi, bir siparişi veritabanına kaydederken, aynı veritabanı işlemi içinde, ilgili
OrderCreatedEventmesajını daOutboxMessagestablosuna kaydeder.BEGIN TRANSACTION; INSERT INTO Orders (Id, CustomerId, Amount, Status) VALUES ('order-456', 'customer-789', 250.00, 'Pending'); INSERT INTO OutboxMessages (Id, OccurredOn, TypeName, Payload, ProcessedOn, Attempts) VALUES ('event-101', NOW(), 'OrderCreatedEvent', '{"orderId": "order-456", "customerId": "customer-789"}', NULL, 0); COMMIT;Bu sayede, veritabanında sipariş varsa, outbox tablosunda da o siparişin olayı mutlaka bulunur. Atomiklik garanti altına alınmıştır.
- Mesaj Rölesi Servisi: Ayrı bir mikroservis (veya Sipariş Servisi içinde çalışan bir arka plan görevi), düzenli aralıklarla (örneğin, her 5 saniyede bir)
OutboxMessagestablosunu sorgular veProcessedOnalanıNULLolan mesajları (yani henüz işlenmemiş mesajları) alır. - Kafka’ya Gönderim ve Güncelleme: Mesaj rölesi, bu mesajları alır ve Kafka’ya gönderir. Eğer Kafka’ya gönderim başarılı olursa, outbox tablosundaki ilgili mesajın
ProcessedOnalanını güncel tarihiyle doldurur veAttemptssayısını sıfırlar. Eğer Kafka’ya gönderim başarısız olursa,Attemptssayısını artırır ve belirli bir tekrar deneme stratejisi (örneğin, üstel geri çekilme) ile daha sonra tekrar denemeyi dener. Belirli bir deneme sayısını aşan mesajlar için, manuel müdahale gerektirebilecek bir “dead letter” (ölü mektup) durumuna düşürülür veya özel bir hata kuyruğuna yönlendirilir.
Idempotent Ödeme İşlemi Uygulaması:
Ödeme Servisi, OrderCreatedEvent mesajını aldığında, ödeme işlemini başlatır. Ancak, aynı orderId için birden fazla ödeme isteği gelirse (örneğin, Outbox rölesi aynı mesajı birden çok kez gönderirse), Ödeme Servisi bunu algılayıp sadece ilkini işlemek üzere idempotent hale getirildi. Bu, ödeme işlemi sırasında bir transactionId veya orderId‘yi kontrol ederek daha önce işlenip işlenmediğini anlamasını sağlar.
Sonuçları ve Kazanımlar:
Bu çözüm sayesinde, e-ticaret platformu aşağıdaki önemli kazanımları elde etti:
- Veri Tutarlılığı: Veritabanında bir sipariş varsa, o siparişin olayı er ya da geç mesaj brokerına ulaşır. Geçici Kafka kesintileri artık sipariş işleme akışını tamamen durdurmuyor.
- Güvenilirlik: Sistem, geçici hatalara karşı daha dayanıklı hale geldi. Mesajlar, outbox tablosunda güvenli bir şekilde bekleyebiliyor.
- Daha Az Manuel Müdahale: Operasyonel ekibin, veritabanında olup da diğer sistemlere yansımayan siparişler için manuel müdahale ihtiyacı önemli ölçüde azaldı.
- Gelişmiş Müşteri Deneyimi: Müşteriler, siparişlerinin sorunsuz bir şekilde işlendiğini ve onay e-postalarını zamanında aldığını gördü, bu da müşteri memnuniyetini artırdı.
Bu vaka analizi, Outbox deseni ve idempotent işlemlerin, dağıtık sistemlerdeki “veritabanı başarılı, mesaj brokerı tekrar dene” sorununu çözmek için ne kadar kritik olduğunu açıkça göstermektedir. Bu desenler, hem geliştiricilerin yükünü azaltır hem de iş süreçlerinin kesintisiz devamlılığını sağlar.
Gelişmiş Stratejiler: Daha Karmaşık Senaryolar İçin İpuçları
Yukarıda ele aldığımız çözümler, birçok temel tutarlılık sorununu giderse de, dağıtık sistemlerin karmaşıklığı bazen daha ileri düzey stratejiler gerektirebilir. Özellikle büyük ölçekli ve yüksek performanslı sistemlerde, bu ipuçları kritik rol oynar.
Dağıtık İzleme (Distributed Tracing): İşlemlerin Görünürlüğünü Artırma
Mikroservis mimarilerinde bir kullanıcı isteği, birden fazla servisten geçebilir. Her servis kendi içindeki işlemleri izleyebilirken, bir isteğin tüm sistem boyunca nasıl ilerlediğini anlamak zorlaşır. İşte burada dağıtık izleme (distributed tracing) devreye girer. Zipkin, Jaeger veya OpenTelemetry gibi araçlar, bir isteğin başlangıcından bitişine kadar tüm servisler arasındaki yolculuğunu takip etmeyi sağlar.
Her servis, aldığı isteğe benzersiz bir “izleme kimliği” (trace ID) ekler ve bu kimliği sonraki servislere iletir. Bu sayede, “Veritabanı ‘Başarılı’ Dedi, Mesaj Brokerı ‘Tekrar Dene'” gibi bir sorun yaşandığında, hangi servisin ne zaman hata verdiğini, mesajın nerede takıldığını veya geciktiğini kolayca tespit edebiliriz. Örneğin, bir siparişin veritabanına kaydedildiği ancak mesaj brokerına gönderilemediği bir durumda, izleme araçları sayesinde, Sipariş Servisi’nin Kafka’ya mesaj gönderme çağrısının başarısız olduğunu ve bunun nedenini (ağ hatası, Kafka’nın yanıt vermemesi vb.) anında görebiliriz. Bu, sorun giderme süresini (MTTR – Mean Time To Recovery) önemli ölçüde kısaltır ve sistemin genel sağlığı hakkında derinlemesine bilgi sağlar.
İşlem Günlüğü (Transaction Log) Tabanlı Çözümler: CDC (Change Data Capture)
Outbox deseni, uygulama koduna bağımlı bir çözümdür. Alternatif olarak, bazı durumlarda veritabanının işlem günlüğünü (transaction log) kullanarak değişiklikleri yakalamak (Change Data Capture – CDC) daha uygun olabilir. Debezium, Maxwell veya Striim gibi CDC araçları, veritabanı işlem günlüğünü izleyerek (örneğin, PostgreSQL’in WAL’ı, MySQL’in binlog’u) veritabanında gerçekleşen tüm değişiklikleri gerçek zamanlı olarak yakalar ve bu değişiklikleri bir mesaj brokerına (genellikle Kafka’ya) olay olarak yayımlar.
CDC’nin avantajı, uygulama koduna herhangi bir değişiklik yapmadan, veritabanındaki her değişikliğin otomatik olarak bir olaya dönüştürülmesidir. Bu, outbox tablosu yönetimi ve mesaj rölesi servisi geliştirme yükünü ortadan kaldırır. Veritabanı işlemi başarılı olduğunda, bu değişiklik otomatik olarak işlem günlüğüne yazılır ve CDC aracı tarafından hemen algılanır. Bu yaklaşım, veritabanı ve olay yayımlama arasındaki atomikliği doğal olarak sağlar, çünkü her ikisi de veritabanının kendi atomik işlem garantileri altındadır. Ancak, CDC araçlarının kurulumu ve yönetimi ek bir karmaşıklık getirebilir ve her veritabanı için uygun olmayabilir.
Event Sourcing (Olay Kaynaklama): Durum Değişikliklerini Olay Akışı Olarak Kaydetme
Event Sourcing (Olay Kaynaklama), bir uygulamanın mevcut durumunu doğrudan depolamak yerine, bu duruma yol açan tüm olayları (değişiklikleri) bir olay deposunda (event store) sıralı bir şekilde kaydetme desenidir. Uygulamanın mevcut durumu, bu olay akışının yeniden oynatılmasıyla (replay) elde edilir. Her olay, uygulamanın durumunda yapılan bir değişikliği temsil eder ve değiştirilemezdir (immutable).
Olay Kaynaklama, doğası gereği “Veritabanı ‘Başarılı’ Dedi, Mesaj Brokerı ‘Tekrar Dene'” sorununu farklı bir şekilde ele alır. Burada, veritabanı kaydı aslında bir olayın olay deposuna kaydedilmesidir. Olay deposu, aynı zamanda bir mesaj brokerı gibi davranabilir veya bir CDC aracıyla entegre edilebilir. Bir olay olay deposuna başarıyla kaydedildiğinde, bu olay diğer servisler tarafından tüketilebilir hale gelir. Bu, veritabanı ve mesaj gönderme arasında atomikliği doğal olarak sağlar, çünkü olay deposuna yazma işlemi hem kalıcı depolamayı hem de olay yayımlamayı temsil eder.
Event Sourcing’in faydaları arasında, denetlenebilirlik (auditing), zamana göre geri sarma (time travel debugging), karmaşık iş mantıklarını modelleme ve olası tutarlılıkla doğal uyum yer alır. Ancak, bu desenin uygulanması, geleneksel CRUD (Create, Read, Update, Delete) tabanlı mimarilere göre daha karmaşıktır ve özel araçlar gerektirebilir.
Bu gelişmiş stratejiler, özellikle büyük ve karmaşık dağıtık sistemlerde, tutarlılık, hata toleransı ve izlenebilirlik ihtiyaçlarını karşılamak için güçlü araçlar sunar. Doğru stratejiyi seçmek, sistemin mimarisine, mevcut altyapısına ve iş gereksinimlerine bağlıdır.
Sıkça Sorulan Sorular:
Veritabanı ve mesaj brokerı arasında atomikliği sağlamanın en iyi yolu nedir?
Genel olarak, Outbox Deseni (Transactional Outbox Pattern), veritabanı ve mesaj brokerı arasında atomikliği sağlamak için en pratik ve yaygın olarak kabul görmüş yoldur. Bu desen, mesajın veritabanı işlemiyle birlikte aynı işlem içinde bir outbox tablosuna kaydedilmesini garanti eder. Daha sonra ayrı bir servis, bu outbox tablosundan mesajları okuyarak mesaj brokerına iletir. Bu sayede, veritabanında bir kayıt varsa, o kayda ait olayın er ya da geç broker’a ulaşması garanti edilir.
Outbox deseni performans sorunlarına yol açar mı?
Evet, Outbox deseni, ek bir veritabanı yazma işlemi ve outbox tablosunu periyodik olarak sorgulayan bir mesaj rölesi servisi gerektirdiğinden belirli bir performans yükü getirebilir. Ancak bu yük genellikle yönetilebilir düzeydedir. Performans sorunlarını azaltmak için şunlar yapılabilir:
- Outbox tablosuna uygun indeksler eklemek.
- Mesaj rölesi servisini ölçeklenebilir hale getirmek (birden fazla röle örneği çalıştırmak).
- Röle servisi için veritabanı sorgularını optimize etmek ve toplu (batch) işlem yapmak.
- CDC (Change Data Capture) araçlarını kullanarak outbox tablosunu sorgulama yükünü azaltmak.
Doğru uygulandığında, Outbox deseninin getirdiği performans maliyeti, sağladığı tutarlılık ve güvenilirlik avantajları karşısında genellikle kabul edilebilir düzeydedir.
Saga deseni ne zaman kullanılmalıdır?
Saga deseni, birden fazla bağımsız servisi ve onların yerel veritabanı işlemlerini kapsayan uzun süreli, karmaşık dağıtık iş akışları için kullanılmalıdır. Örneğin, bir e-ticaret sitesinde siparişin alınması, ödemenin işlenmesi, envanterin güncellenmesi ve kargonun ayarlanması gibi adımların her birinin ayrı bir servis tarafından yönetildiği ve bu adımların birinin başarısız olması durumunda önceki adımların telafi edilmesi gereken senaryolarda Saga deseni çok uygundur. Basit, tek adımlı olay yayımlama senaryoları için Outbox deseni genellikle yeterlidir.
Dead Letter Queue (DLQ) ne işe yarar?
Dead Letter Queue (DLQ – Ölü Mektup Kuyruğu), mesaj brokerlarında, belirli bir sayıda tekrar denemeye rağmen başarıyla işlenemeyen veya geçersiz olduğu tespit edilen mesajların gönderildiği özel bir kuyruktur. DLQ’nun temel amacı, işlenemeyen mesajların ana kuyruğu tıkamasını önlemek ve bu mesajları ayrı bir yerde toplayarak manuel inceleme, hata ayıklama veya yeniden işleme imkanı sunmaktır. Bu sayede, sistemin genel işleyişi aksamazken, başarısız mesajların nedenleri araştırılabilir ve gerektiğinde düzeltilebilir.
Idempotent işlemler neden bu kadar önemlidir?
Idempotent işlemler, dağıtık sistemlerdeki güvenilirliğin temel taşlarından biridir. Ağ hataları, tekrar deneme mekanizmaları veya mesaj brokerlarının “en az bir kez teslimat” (at-least-once delivery) garantisi nedeniyle aynı mesajın alıcı servise birden çok kez ulaşması yaygın bir durumdur. Eğer bir işlem idempotent değilse, aynı mesajın birden çok kez işlenmesi, veri tekrarına, yanlış durumlara (örneğin, aynı ödemenin iki kez çekilmesi, envanterin birden çok kez düşülmesi) veya iş mantığı hatalarına yol açabilir. Idempotentlik, bir işlemin birden çok kez uygulanmasının aynı sonucu vermesini sağlayarak bu tür sorunları önler ve sistemin tutarlılığını korur.
Sonuç: Güvenilir Dağıtık Sistemler İnşa Etmek
Modern yazılım mimarileri, özellikle mikroservislerin yaygınlaşmasıyla birlikte, sistemler arasındaki iletişim ve veri tutarlılığı konusunda önemli zorlukları beraberinde getiriyor. “Veritabanı ‘Başarılı’ Dedi, Mesaj Brokerı ‘Tekrar Dene'” senaryosu, dağıtık sistemlerin doğasında var olan bu tutarsızlık potansiyelinin en çarpıcı örneklerinden biridir. Bu durum, veritabanı düzeyinde atomiklik sağlanırken, farklı sistemler arasındaki iletişimin atomik olmamasından kaynaklanır ve ciddi iş süreçleri aksaklıklarına yol açabilir.
Ancak, bu zorluklar aşılamaz değildir. Outbox deseni, Saga deseni, idempotent işlemler, sağlam tekrar deneme mekanizmaları ve gelişmiş izleme araçları gibi mimari desenler ve stratejiler, bu tür sorunları çözmek için güçlü bir araç seti sunar. Outbox deseni, veritabanı işlemi ile mesaj gönderme işlemini atomik hale getirerek veri tutarlılığını garanti altına alırken, idempotentlik aynı mesajın birden çok kez işlenmesi durumunda bile sistemin doğru çalışmasını sağlar. Saga deseni ise, daha karmaşık ve uzun süreli dağıtık işlemlerin güvenilir bir şekilde yönetilmesine olanak tanır.
Güvenilir dağıtık sistemler inşa etmek, sadece kod yazmakla bitmez; aynı zamanda sistemin tüm bileşenlerinin etkileşimini, olası hata durumlarını ve bu hataların nasıl telafi edileceğini derinlemesine anlamayı gerektirir. Geliştiricilerin bu desenleri ve yaklaşımları kavraması ve kendi projelerine uygun bir şekilde uygulaması kritik öneme sahiptir. Unutmayalım ki, bir sistemin gerçek gücü, sorunsuz çalıştığı zamanlarda değil, beklenmedik hatalarla karşılaştığında nasıl tepki verdiğinde ortaya çıkar. Bu stratejiler sayesinde, daha esnek, ölçeklenebilir ve hata toleranslı uygulamalar geliştirerek kullanıcılarımıza kesintisiz bir deneyim sunabiliriz.
#Teknoloji #WebGeliştirme #Mikroservis #DağıtıkSistemler #VeriTutarlılığı
