Uçak bileti alırken, konser bileti rezerve ederken veya sinemada yer seçerken hiç düşündünüz mü? Milyonlarca kullanıcının aynı anda sistemde gezindiği, sürekli koltuk seçtiği ve onayladığı bu karmaşık sistemler aslında arka planda nasıl çalışıyor? Özellikle de “Aynı koltuğu iki kişi birden rezerve edebilir mi?” gibi kritik soruların önüne nasıl geçiliyor? Bu makalede, ölçeklenebilir rezervasyon sistemlerinin koltuk tablolarını doğrudan güncellemek yerine neden daha sofistike yaklaşımlar kullandığını derinlemesine inceleyeceğiz. Bu sayede, performans, veri tutarlılığı ve kullanıcı deneyimi gibi hayati faktörlerin nasıl korunabildiğini anlayacaksınız.
Öncelikle, işin temelini oluşturan sorunlara odaklanalım. Bir rezervasyon sistemi düşündüğümüzde, en basit haliyle bir veritabanında “koltuklar” tablosu ve her koltuğun “durumu” (boş, rezerve, satıldı) sütunu olmasını bekleriz. Bir kullanıcı koltuk seçtiğinde, bu durumu doğrudan “rezerve” olarak güncellemeyi düşünebiliriz. Ancak, bu basit yaklaşım yüksek trafikli, ölçeklenebilir bir sistemde hızla bir kabusa dönüşebilir. Peki, neden?
En büyük sorunlardan biri eş zamanlılık (concurrency) meselesidir. Diyelim ki popüler bir konser için biletler satışa çıktı ve on binlerce kişi aynı anda sisteme yüklendi. Eğer herkes aynı “K12” koltuğunu seçip güncelleme komutunu veritabanına gönderirse ne olur? İşte bu noktada yarış koşulları (race conditions) ortaya çıkar. Birden fazla işlem, aynı anda aynı veriyi değiştirmeye çalıştığında, sistemin son durumu beklenmedik ve yanlış olabilir. Örneğin, iki kullanıcı K12 koltuğunu seçti. İlk kullanıcı güncellemeyi gönderir ve koltuğu “rezerve” yapar. İkinci kullanıcı da aynı anda güncellemeyi gönderirse, veritabanı kilitlenme (locking) mekanizmaları yüzünden bir çakışma yaşayabilir veya daha kötüsü, her iki işlemin de başarılı olduğunu zannedip koltuğu çifte rezerve edebilir.
Bu durumu engellemek için veritabanları genellikle kilitlenme (locking) mekanizmaları kullanır. Bir işlem bir satırı güncellerken, o satırı kilitler ve diğer işlemlerin o satıra erişmesini veya onu değiştirmesini engeller. Bu, veri tutarlılığı açısından kritik olsa da, ölçeklenebilirlik açısından ciddi bir darboğaz yaratır. Düşünsenize, aynı koltuk için yüzlerce istek bekletilirse, kullanıcılar sistemin yavaşladığını hatta donduğunu zannedebilir. Bu durum ölü kilit (deadlock) senaryolarına da yol açabilir; iki işlem birbirinin kilitlediği kaynağı bekler ve sonsuza dek askıda kalır. Bu da sistemin tamamen kilitlenmesi anlamına gelir.
Ayrıca, bu doğrudan güncelleme yaklaşımı ACID (Atomicity, Consistency, Isolation, Durability) prensiplerini korumakta zorlanır, özellikle de dağıtık sistemlerde. Bir rezervasyon işlemi sadece koltuk durumunu güncellemekle kalmaz; aynı zamanda ödeme işlemini, kullanıcının rezervasyon geçmişini ve belki de bilet gönderimini de içerir. Bu adımların her biri ayrı birer mikroservis veya veritabanı işlemi olabilir. Eğer koltuk güncellendi ancak ödeme başarısız olursa, veya ödeme başarılı oldu ancak koltuk tablosu güncellenemediyse ne olur? Bu tür kısmi başarısızlıklar, sistemin tutarsız bir duruma düşmesine neden olur ve manuel müdahale gerektirebilir, bu da operasyonel maliyetleri artırır.
Son olarak, performans faktörü var. Her rezervasyon isteğinde veritabanına doğrudan bir yazma işlemi yapmak, veritabanı sunucusu üzerinde yoğun bir yük oluşturur. Özellikle büyük ölçekli ve anlık yüksek trafik alan sistemlerde, veritabanı sunucusu bu yükü kaldıramaz hale gelebilir. Okuma işlemleri önbellekle (cache) hızlandırılabilirken, yazma işlemleri her zaman veritabanına ulaşmak zorundadır. Bu nedenle, doğrudan güncelleme, hem veri tutarlılığı hem de performans açısından sürdürülemez bir modeldir.
İşte bu karmaşık sorunlar zinciri, modern ölçeklenebilir rezervasyon sistemlerinin neden koltuk tablolarını doğrudan güncellemek yerine çok daha farklı, dolaylı ve asenkron yöntemlere başvurduğunun ana nedenidir. Bu yaklaşımlar, bir yandan veri bütünlüğünü korurken, diğer yandan milyonlarca kullanıcının sorunsuz bir deneyim yaşamasını sağlamayı hedefler.
Veri Tutarlılığı ve Eş Zamanlılık Zorlukları Nasıl Aşılır? Geleneksel Çözümlerin Sınırları
Eş zamanlılık ve veri tutarlılığı sorunlarını aşmak için geleneksel olarak veritabanı dünyasında çeşitli yaklaşımlar benimsenmiştir. Bunların başında kilitlenme mekanizmaları (locking) gelir. Kilitlenme, bir verinin birden fazla işlem tarafından aynı anda değiştirilmesini engelleyerek tutarlılığı garanti eder. Ancak, daha önce de bahsettiğimiz gibi, bu yaklaşımlar ölçeklenebilirlik açısından ciddi sınırlamalar getirir.
Veritabanı Kilitleri: Pessimistik ve Optimistik Yaklaşımlar Arasındaki Fark Nedir?
Kilitlenme mekanizmaları iki ana kategoriye ayrılır:
-
Pessimistik Kilitlenme (Pessimistic Locking): Bu yaklaşım, bir veriye erişildiğinde, başka herhangi bir işlemin o veriye aynı anda erişmesini engellemeyi hedefler. Veri, işlem tamamlanana kadar kilitli kalır. Uçak bileti rezervasyon sistemlerinde, bir kullanıcı belirli bir koltuğu seçtiği anda, o koltuk başkası tarafından seçilemez hale getirilir. Bu genellikle veritabanı seviyesinde
SELECT ... FOR UPDATEgibi SQL komutlarıyla uygulanır.-- Pessimistik Kilitlenme Örneği BEGIN; SELECT koltuk_durumu FROM koltuklar WHERE koltuk_id = 'K12' FOR UPDATE; -- Eğer koltuk boşsa, rezervasyon yap UPDATE koltuklar SET koltuk_durumu = 'rezerve', kullanici_id = 123 WHERE koltuk_id = 'K12'; COMMIT;Avantajları: Veri tutarlılığını çok güçlü bir şekilde garanti eder. Yarış koşullarını neredeyse tamamen ortadan kaldırır.
Dezavantajları: Ölçeklenebilirlik sorunları yaratır. Yüksek eş zamanlılıkta performans düşüşleri, bekleme süreleri ve ölü kilit (deadlock) riskleri artar. Kullanıcı deneyimi, özellikle yoğun anlarda, olumsuz etkilenebilir. -
Optimistik Kilitlenme (Optimistic Locking): Bu yaklaşım, veriye aynı anda birden fazla işlemin erişebileceğini varsayar ve çakışmaların nadiren yaşanacağını öngörür. Veri üzerinde herhangi bir kilit koyulmaz; ancak veri güncellenirken bir versiyon kontrolü yapılır. Genellikle bir
versionveyatimestampsütunu kullanılır. Bir kullanıcı veriyi okuduğunda, o verinin versiyon numarasını da okur. Güncelleme yaparken, sadece okuduğu versiyon numarası ile veritabanındaki mevcut versiyon numarası aynıysa işlemi gerçekleştirir. Eğer farklıysa, başka bir işlem veriyi değiştirmiş demektir ve güncelleme reddedilir. Kullanıcıya "bu koltuk başkası tarafından rezerve edildi" gibi bir mesaj gösterilir.-- Optimistik Kilitlenme Örneği -- Kullanıcı koltuk bilgilerini okur (veya UI'da görür) SELECT koltuk_durumu, versiyon FROM koltuklar WHERE koltuk_id = 'K12'; -- Kullanıcının gördüğü versiyon: 1 (örneğin) -- Kullanıcı rezervasyon yapmaya çalışır UPDATE koltuklar SET koltuk_durumu = 'rezerve', kullanici_id = 123, versiyon = versiyon + 1 WHERE koltuk_id = 'K12' AND versiyon = 1; -- Eğer 0 satır etkilendiyse, başka bir işlem bu koltuğu güncellemiş demektir.Avantajları: Yüksek eş zamanlılıkta daha iyi performans sunar çünkü kilit bekleme süreleri yoktur. Ölü kilit riski düşüktür.
Dezavantajları: Çakışma durumunda işlemin baştan yapılması gerekir (retry), bu da kullanıcı deneyimini etkileyebilir. Çakışma oranı yüksekse performans dezavantajı yaratabilir. Uygulama seviyesinde daha fazla iş yükü gerektirir.
Her iki kilitlenme yöntemi de belirli senaryolarda faydalıdır, ancak büyük ölçekli ve saniyede binlerce işlem gerçekleştiren modern rezervasyon sistemleri için her ikisi de tek başına yeterli değildir. Özellikle pessimistik kilitlenme, dağıtık sistem mimarilerinde (mikroservisler gibi) yönetilmesi çok zor ve performans düşürücü bir hale gelebilir. Bu nedenle, daha esnek ve daha performanslı çözümlere ihtiyaç duyulmuştur. Bu çözümler genellikle veritabanı katmanından bir katman yukarı çıkarak, uygulama veya altyapı katmanında eş zamanlılık ve tutarlılık sorunlarını ele alır.
Geleneksel veritabanı odaklı bu yaklaşımlar, merkezi bir veritabanı etrafında dönen monolitik uygulamalar için bir yere kadar işe yarasa da, modern bulut tabanlı, dağıtık sistemlerdeki zorluklarla başa çıkmakta yetersiz kalır. Bu noktada, asenkron işlem modelleri ve olay tabanlı mimariler devreye girer.
Asenkron İşlem Modelinin Kurtarıcı Rolü: Kuyruk Sistemleri Nelerdir ve Nasıl Çalışır?
Doğrudan veritabanı güncellemelerinin getirdiği eş zamanlılık ve performans sorunlarını aşmanın en etkili yollarından biri, işlemleri asenkron hale getirmektir. Bu noktada mesaj kuyruk sistemleri (message queue systems) devreye girer. Mesaj kuyrukları, servisler arasında iletişimi dolaylı hale getirerek ve işlemleri kuyruğa alarak sistemi yüksek yüke karşı daha dirençli ve ölçeklenebilir kılar.
Rezervasyon Talepleri Nasıl Kuyruğa Alınır?
Bir kullanıcı bir koltuk seçtiğinde ve rezervasyon butonuna tıkladığında, bu istek doğrudan koltuk tablosunu güncellemeyi denemek yerine, bir mesaj kuyruğuna gönderilir. Bu mesaja "Koltuk Rezervasyon Talebi" diyebiliriz ve içinde hangi kullanıcının, hangi koltuğu rezerve etmek istediği gibi bilgiler bulunur.
Bu akışı bir örnekle açıklayalım:
- Kullanıcı İsteği: Kullanıcı, web sitesi veya mobil uygulama üzerinden 'K12' numaralı koltuğu rezerve etmek ister.
- Talep Oluşturma: Rezervasyon servisi, bu talebi alır ve anında koltuk tablosunu güncellemek yerine, bir "rezervasyon talebi" mesajı oluşturur. Bu mesaj genellikle JSON veya benzeri bir formatta olur ve gerekli tüm bilgileri (kullanıcı ID'si, koltuk ID'si, etkinlik ID'si vb.) içerir.
- Kuyruğa Gönderme: Oluşturulan mesaj, bir mesaj kuyruğu sistemine (örn: Apache Kafka, RabbitMQ, Amazon SQS) gönderilir. Bu işlem genellikle çok hızlıdır ve kullanıcının isteği anında "işleme alındı" olarak işaretlenir.
- Asenkron İşleme: Mesaj kuyruğunda bekleyen bu talepleri dinleyen başka bir servis (örn: "Koltuk İşleme Servisi"), mesajları sırayla kuyruktan alır. Bu servis, her mesajı aldığında gerekli veritabanı işlemlerini (koltuk durumunu "rezerve" olarak güncelleme) yapar.
- Yanıt ve Bildirim: Koltuk İşleme Servisi işlemi başarıyla tamamladığında, başka bir mesaj kuyruğuna "Rezervasyon Başarılı" veya "Rezervasyon Başarısız" mesajı gönderebilir. Kullanıcıya bu mesajlar üzerinden e-posta, SMS veya uygulama içi bildirim ile bilgi verilir.
Bu modelin temel faydaları şunlardır:
- Ölçeklenebilirlik: Yüksek trafik anında, kuyruk sistemi gelen tüm talepleri hızla kabul eder. Arka plandaki işleme servisleri, iş yüküne göre ölçeklendirilebilir. Yani daha fazla talep geldiğinde, daha fazla işleme servisi devreye sokularak kuyruktaki mesajlar daha hızlı tüketilebilir.
- Dayanıklılık (Resilience): Eğer koltuk işleme servisi geçici olarak bir sorun yaşarsa (örn: veritabanı bağlantı hatası), mesajlar kuyrukta güvende bekler. Servis düzeldiğinde kaldığı yerden devam eder. Hiçbir istek kaybolmaz.
- Gevşek Bağlılık (Loose Coupling): Rezervasyon servisi ile koltuk işleme servisi birbirinden bağımsız hale gelir. Birinin değişmesi diğerini doğrudan etkilemez, bu da sistemin bakımını ve gelişimini kolaylaştırır.
- Yük Dengeleme: Mesaj kuyrukları, gelen istekleri birden fazla işleme servisine dağıtarak yük dengelemesi sağlar.
İşte basit bir senaryoda bu akışın nasıl görünebileceğine dair bir örnek:
// Rezervasyon servisi tarafından kuyruğa gönderilen mesaj
// (Örnek: Apache Kafka'ya gönderilecek bir JSON mesajı)
{
"eventType": "KoltukRezervasyonTalebi",
"transactionId": "UUID-12345",
"userId": "user-A",
"seatId": "K12",
"eventId": "konser-abc",
"timestamp": "2023-10-27T10:30:00Z"
}
// Koltuk İşleme Servisi (Consumer) tarafındaki mantık
// Pseudo-kod
function processSeatReservationRequest(message) {
const { userId, seatId, eventId, transactionId } = message;
// Veritabanında optimistik kilitlenme veya uygun bir mekanizma ile koltuğu kontrol et
const seat = database.getSeat(seatId);
if (seat && seat.status === 'BOS') {
// Koltuğu rezerve et
const success = database.updateSeatStatus(seatId, 'REZERVE', userId);
if (success) {
// Başarı mesajını başka bir kuyruğa gönder
messageQueue.publish('RezervasyonBasarili', { transactionId, seatId, userId });
console.log(Koltuk ${seatId} kullanıcı ${userId} için rezerve edildi.);
} else {
// Başarısızlık (optimistik kilitlenme çakışması vb.)
messageQueue.publish('RezervasyonBasarisiz', { transactionId, seatId, userId, reason: 'ConcurrencyConflict' });
console.log(Koltuk ${seatId} rezervasyonunda çakışma yaşandı.);
}
} else {
// Koltuk zaten rezerve veya mevcut değil
messageQueue.publish('RezervasyonBasarisiz', { transactionId, seatId, userId, reason: 'SeatNotAvailable' });
console.log(Koltuk ${seatId} zaten dolu veya mevcut değil.);
}
}
Bu model sayesinde, yoğun bir talep anında dahi, kullanıcıya anında bir geri bildirim (örn. "Rezervasyonunuz işleme alındı, onay maili gönderilecektir") verilebilirken, asıl veritabanı güncelleme işlemi arka planda kontrollü bir şekilde, yarış koşullarını ve kilitlenmeleri minimuma indirerek gerçekleştirilir. Bu, modern ölçeklenebilir rezervasyon sistemlerinin temel taşlarından biridir.
Olay Tabanlı Mimari (Event-Driven Architecture) ve Etkinlik Kaydı (Event Sourcing): Gerçek Zamanlı ve Güvenilir İşlemler
Mesaj kuyrukları, asenkron işlemleri yönetmede harika bir başlangıç olsa da, ölçeklenebilir rezervasyon sistemlerinin gerçekten parlamasını sağlayan daha ileri bir konsept vardır: Olay Tabanlı Mimari (Event-Driven Architecture - EDA) ve bunun bir uygulaması olan Etkinlik Kaydı (Event Sourcing). Bu yaklaşımlar, sistemin durumunu doğrudan güncellemek yerine, sistemde meydana gelen olayları kaydetmeye odaklanır.
Sistem Durumunu Değiştirmek Yerine, Olayları Kaydetmek Nasıl Fayda Sağlar?
Geleneksel veritabanı tasarımında, bir kaydın mevcut durumu (örn. bir koltuğun 'boş' veya 'rezerve' olması) tutulur ve bu durum sürekli güncellenir. Olay Tabanlı Mimaride ise, sistemin bir anlık görüntüsünü (snapshot) tutmak yerine, o anki duruma yol açan olayların (events) kronolojik sırasını kaydederiz. Örneğin, bir koltuğun durumu 'boş'tan 'rezerve'ye geçtiğinde, biz 'KoltukRezerveEdildi' adlı bir olay kaydederiz.
Bu yaklaşımın temel faydaları şunlardır:
- Denetlenebilirlik ve İzlenebilirlik (Auditability): Her işlem bir olay olarak kaydedildiği için, sistemin geçmişine ait tam bir denetim izi oluşur. "Bu koltuk neden rezerve edildi? Kim rezerve etti? Ne zaman rezerve edildi?" gibi soruların cevapları kolayca bulunabilir. Bu, hata ayıklama ve sorun giderme süreçlerini basitleştirir.
- Zaman Yolculuğu (Time Travel): Olayların kronolojik sırası sayesinde, sistemi geçmişteki herhangi bir ana geri döndürebilir ve o anki durumunu görebiliriz. Bu, hata durumlarında veya iş zekası analizlerinde paha biçilmez bir özelliktir.
- Gelişmiş Ölçeklenebilirlik: Olaylar genellikle salt okunur (immutable) ve ekleme odaklı (append-only) olarak kaydedilir. Bu, yazma işlemlerini basitleştirir ve yarış koşulları riskini azaltır. Okuma ve yazma işlemlerini ayırmak, sistemin farklı bileşenlerinin bağımsız olarak ölçeklenmesine olanak tanır.
- Gevşek Bağlılık ve Entegrasyon Kolaylığı: Farklı servisler, ilgilendikleri olayları dinleyerek kendi iç durumlarını güncelleyebilirler. Bu, mikroservis mimarileri için idealdir ve yeni servislerin sisteme entegrasyonunu kolaylaştırır.
- Veri Tutarlılığı: Bir olay meydana geldiğinde, bu olay kaydedilir ve ardından bu olaya tepki veren diğer servisler tetiklenir. Bu, sistem genelinde veri tutarlılığını sağlamanın daha esnek bir yoludur, özellikle de "Nihai Tutarlılık (Eventual Consistency)" modelini benimserken.
Event Sourcing (Etkinlik Kaydı), olay tabanlı mimarinin somut bir uygulama şeklidir. Burada, veritabanı olarak bir "olay günlüğü" (event log) kullanılır. Sistemdeki tüm durum değişiklikleri, bu günlüğe olay olarak eklenir. Bir koltuğun son durumunu öğrenmek için, o koltukla ilgili tüm olayları baştan sona oynatmak (replay) gerekir. Gerçek sistemlerde performans için, olay günlüğünden belirli aralıklarla anlık görüntüler (snapshots) alınır ve bu sayede tüm olayları baştan oynatmaya gerek kalmaz.
// Bir koltuk rezervasyonu olayının JSON formatında gösterimi
// Olay günlüğüne (örneğin Kafka) kaydedilir
{
"eventId": "event-1001",
"eventType": "KoltukRezerveEdildi",
"aggregateId": "koltuk-K12", // Hangi varlığa ait olay olduğunu belirtir
"version": 1, // Koltuğun bu olaydan sonraki versiyonu
"payload": {
"koltukId": "K12",
"kullaniciId": "user-A",
"etkinlikId": "konser-abc",
"rezervasyonTarihi": "2023-10-27T10:30:00Z"
},
"metadata": {
"source": "RezervasyonServisi",
"timestamp": "2023-10-27T10:30:00Z"
}
}
// Koltuk iptali olayı
{
"eventId": "event-1002",
"eventType": "KoltukIptalEdildi",
"aggregateId": "koltuk-K12",
"version": 2, // Koltuğun bu olaydan sonraki versiyonu
"payload": {
"koltukId": "K12",
"kullaniciId": "user-A",
"iptalNedeni": "OdemeBasarisizligi"
},
"metadata": {
"source": "OdemeServisi",
"timestamp": "2023-10-27T10:31:00Z"
}
}
Bu olaylar, "Event Store" adı verilen özel bir veritabanında saklanır ve genellikle bir Mesaj Kuyruğu (Event Bus) üzerinden diğer servislere dağıtılır. Örneğin, "KoltukRezerveEdildi" olayı yayınlandığında:
- Envanter servisi, ilgili koltuğun envanterini düşürür.
- Ödeme servisi, ödeme işlemini başlatır.
- Bildirim servisi, kullanıcıya rezervasyon onayı maili göndermek için bir görev oluşturur.
- Raporlama servisi, satış raporlarını güncellemek için bu olayı kaydeder.
Bu yapı, rezervasyon sisteminin sadece mevcut durumuyla değil, aynı zamanda bu duruma nasıl ulaştığıyla da ilgilenmesini sağlar. Bu, sistemin daha şeffaf, daha güvenilir ve daha esnek olmasını sağlar, bu da modern, yüksek ölçekli sistemler için vazgeçilmez bir özelliktir.
CQRS (Command Query Responsibility Segregation) ve Saga Deseni ile Karmaşık İş Akışları Yönetimi
Büyük ölçekli rezervasyon sistemlerinde yalnızca koltuk durumunu güncellemek yetmez. Ödeme almak, bilet göndermek, envanteri kontrol etmek, kullanıcıya bildirim göndermek gibi birçok farklı işlem zinciri bulunur. Bu karmaşık iş akışlarını yönetmek, özellikle dağıtık sistemlerde zorlu olabilir. İşte bu noktada CQRS (Command Query Responsibility Segregation) ve Saga Deseni devreye girerek bu zorlukları aşmaya yardımcı olur.
CQRS: Okuma ve Yazma İşlemlerini Ayırmak Neden Önemlidir?
Geleneksel uygulamalarda genellikle tek bir veritabanı ve aynı veri modeli üzerinden hem okuma (query) hem de yazma (command) işlemleri yapılır. Ancak, rezervasyon sistemleri gibi yoğun trafik alan uygulamalarda, okuma yükü (kullanıcıların koltuk müsaitliklerini kontrol etmesi) yazma yükünden (koltuk rezerve etme) çok daha fazla olabilir. CQRS, bu iki sorumluluğu birbirinden ayırır:
- Komut (Command) Modeli: Bu kısım, veri değişikliklerini yönetir. Rezervasyon talepleri, iptal istekleri gibi tüm yazma işlemleri buradan geçer. Genellikle bir olay günlüğü (event log) veya olay deposu (event store) ile Event Sourcing prensiplerini takip eder. Komut modeli, veri tutarlılığını sağlamak için tasarlanmıştır.
- Sorgu (Query) Modeli: Bu kısım, veriyi okuma işlemleri için optimize edilmiştir. Genellikle kolayca sorgulanabilen, denormalize edilmiş bir veritabanı (örn. bir NoSQL veritabanı veya hatta Redis gibi bir önbellek) kullanılır. Kullanıcı arayüzünde koltuk haritasının görüntülenmesi gibi işlemler bu model üzerinden gerçekleşir.
Bu ayrım sayesinde, okuma işlemleri için ayrı bir veritabanı veya önbellek katmanı kullanılabilir ve bu katman bağımsız olarak ölçeklendirilebilir. Yazma işlemleri ise, tutarlılığı garanti altına alacak şekilde, daha kontrollü bir ortamda yürütülür. Böylece, sistemin genel performansı ve ölçeklenebilirliği önemli ölçüde artırılır. Bir kullanıcı koltukları görüntülerken (sorgu), bu işlem hızlıca önbellekten döner. Bir koltuğu rezerve ederken (komut), bu işlem asenkron bir kuyruğa alınır ve Event Sourcing ile işlenir.
// CQRS: Komut örneği (rezervasyon talebi)
public class RezervasyonYapKomutu {
public string KoltukId { get; set; }
public string KullaniciId { get; set; }
public string EtkinlikId { get; set; }
}
// CQRS: Sorgu örneği (koltuk müsaitliğini sorgulama)
public class KoltukMüsaitlikSorgusu {
public string EtkinlikId { get; set; }
}
// Sorgu modelinden dönen veri (önbellekte saklanabilir)
public class KoltukMüsaitlikDurumuDto {
public string KoltukId { get; set; }
public string Durum { get; set; } // "BOŞ", "REZERVE", "SATILDI"
public decimal Fiyat { get; set; }
}
Saga Deseni: Dağıtık İşlemlerin Orkestrasyonu Nasıl Sağlanır?
Bir rezervasyon işlemi, aslında birden fazla mikroservisi ilgilendiren dağıtık bir işlem olabilir: koltuk rezervasyonu, ödeme, bilet gönderimi, envanter güncelleme gibi. Eğer bu adımlardan biri başarısız olursa, diğerlerinin de geri alınması (rollback) gerekebilir. Geleneksel veritabanı işlemleri (ACID işlemleri) tek bir veritabanı üzerinde atomiklik sağlarken, dağıtık sistemlerde bu mümkün değildir. İşte Saga Deseni, bu tür uzun süreli, dağıtık işlemleri yönetmek için kullanılır.
Saga, bir dizi yerel işlemden (local transactions) oluşur ve her bir yerel işlem, kendi veritabanında atomik olarak başarılı veya başarısız olabilir. Eğer bir yerel işlem başarısız olursa, Saga, daha önce başarılı olmuş işlemleri geri almak için telafi edici işlemler (compensating transactions) başlatır. Saga'lar iki ana şekilde uygulanabilir:
- Koreografi (Choreography): Servisler birbirlerinin yayınladığı olayları dinler ve bu olaylara tepki olarak kendi işlemlerini başlatır veya telafi edici işlemler yapar. Servisler arası doğrudan iletişim yerine olaylar üzerinden dolaylı iletişim kurulur. Daha az merkezi kontrol gerektirir, ancak karmaşık Saga'larda izlemesi zorlaşabilir.
- Orkestrasyon (Orchestration): Merkezi bir "Orkestratör" servisi bulunur. Bu orkestratör, tüm Saga'nın adımlarını yönetir, ilgili servislere komutlar gönderir ve her adımın sonucunu bekler. Bir adım başarısız olursa, orkestratör telafi edici işlemleri başlatır. Daha fazla merkezi kontrol sağlar, karmaşık akışları yönetmek daha kolaydır.
Bir rezervasyon Saga'sının Orkestrasyon yaklaşımıyla nasıl görünebileceğine dair basitleştirilmiş bir örnek:
- Kullanıcı rezervasyon talebi gönderir.
- Rezervasyon Orkestratör Servisi:
- Koltuk Rezervasyon Servisine "KoltukRezerveEt" komutu gönderir.
- Koltuk Rezervasyon Servisi: Koltuğu rezerve eder (durumunu "beklemede" yapar) ve "KoltukRezerveEdildi" olayını yayınlar.
- Orkestratör: Bu olayı alır.
- Orkestratör: Ödeme Servisine "ÖdemeYap" komutu gönderir.
- Ödeme Servisi: Ödemeyi işler ve "ÖdemeBasarili" veya "ÖdemeBasarisiz" olayını yayınlar.
- Orkestratör: Olayı alır.
- Eğer "ÖdemeBasarili" ise:
- Bilet Servisine "BiletOluştur" komutu gönderir.
- Bilet Servisi: Bileti oluşturur ve "BiletOlusturuldu" olayını yayınlar.
- Orkestratör: "BiletOlusturuldu" olayını alır ve Koltuk Rezervasyon Servisine "KoltuguOnayla" komutu gönderir.
- Koltuk Rezervasyon Servisi: Koltuk durumunu "satıldı" olarak günceller.
- Orkestratör: Rezervasyon tamamlandı olarak işaretler.
- Eğer "ÖdemeBasarisiz" ise:
- Orkestratör: Koltuk Rezervasyon Servisine "KoltuguIptalEt" komutu gönderir. (Telafi edici işlem)
- Koltuk Rezervasyon Servisi: Koltuğun durumunu "boş" olarak geri döndürür.
- Orkestratör: Rezervasyon başarısız olarak işaretler.
- Eğer "ÖdemeBasarili" ise:
Saga deseni sayesinde, dağıtık sistemlerde dahi karmaşık iş akışlarının tutarlılığı sağlanabilir. Bu, doğrudan veritabanı güncellemelerinin getireceği tutarsızlık ve ölçeklenebilirlik sorunlarını çözmenin güçlü bir yoludur.
Gerçek Dünya Uygulamaları ve Vaka Analizleri: Büyük Oyuncular Nasıl Başarıyor?
Şimdiye kadar bahsettiğimiz tüm bu mimari desenler ve yaklaşımlar, teorik olmaktan çok uzakta. Aslında, dünyadaki en büyük ve en başarılı rezervasyon sistemleri, uçak bileti şirketleri, konser bilet satış platformları ve otel rezervasyon motorları, bu prensipleri kendi altyapılarında aktif olarak kullanmaktadırlar. Peki, bu büyük oyuncular bu karmaşıklığı nasıl yönetiyorlar?
Hava Yolları ve Biletleme Platformları: Yüksek Eş Zamanlılık ve Veri Tutarlılığı
Bir havayolu şirketinin biletleme sistemi, ölçeklenebilirliğin ve veri tutarlılığının zirve yaptığı noktalardan biridir. Belirli bir uçuşta sadece sınırlı sayıda koltuk vardır ve bu koltuklar, dünyanın dört bir yanından aynı anda rezervasyon yapmaya çalışan binlerce kişi tarafından hedeflenir. Böyle bir senaryoda doğrudan koltuk tablosunu güncellemek, yukarıda bahsettiğimiz tüm felaket senaryolarına yol açar.
- Uçak Bileti Rezervasyonunda Kuyruk Sistemleri: Bir kullanıcı bir uçuş için koltuk seçip ödeme adımına geçtiğinde, genellikle koltuk geçici olarak "beklemede" (on hold) durumuna alınır. Ancak bu kilit, sınırlı bir süre içindir. Asıl rezervasyon ve ödeme işlemi, arka plandaki mesaj kuyrukları aracılığıyla asenkron olarak işlenir. Bu, sistemin aynı anda çok sayıda rezervasyon talebini kabul etmesine olanak tanır.
- Event Sourcing ile Tam İzlenebilirlik: Havayolları için her koltuğun durumu, fiyat değişiklikleri, iptaller ve geri ödemeler büyük önem taşır. Event Sourcing, her koltuğun yaşam döngüsündeki tüm olayları (KoltukRezerveEdildi, KoltukFiyatGuncellendi, RezervasyonIptalEdildi vb.) kronolojik olarak kaydetmelerini sağlar. Bu, hem yasal denetimler hem de müşteri hizmetleri için kritik bir geçmiş sunar.
- CQRS ile Hızlı Arama ve Güncel Envanter: Uçak bileti arama motorları, yüz binlerce uçuş için anlık koltuk müsaitliklerini ve fiyatları göstermelidir. Bu, yoğun bir okuma yükü demektir. CQRS sayesinde, okuma için optimize edilmiş ayrı bir veritabanı veya önbellek (örn: Redis, Elasticsearch) kullanırlar. Bu, kullanıcıların saniyeler içinde binlerce uçuş seçeneğini görmesini sağlarken, arka plandaki karmaşık yazma işlemleri etkilenmez.
Konser ve Etkinlik Biletleme Platformları (Ticketmaster, Biletix): Flash Sale Zorlukları
Popüler bir sanatçının konser biletleri satışa çıktığında yaşanan "flash sale" durumu, ölçeklenebilirlik testlerinin en zorlu senaryolarından biridir. Saniyeler içinde milyonlarca kullanıcı sisteme yüklenir ve binlerce koltuk için aynı anda talep oluşturulur. Bu tür platformlar da benzer yaklaşımları benimser:
- Talebi Yumuşatma (Throttling) ve Kuyruklar: Bazı platformlar, aşırı yüklenmeyi önlemek için kullanıcıları sanal bir bekleme odasına alabilir. Aslında bu, arka plandaki mesaj kuyruklarının bir uzantısıdır; her kullanıcı kuyrukta bir yer edinir ve sırası geldiğinde rezervasyon akışına yönlendirilir.
- Geçici Kilitler ve Optimistik Yaklaşım: Bir koltuk seçildiğinde, genellikle birkaç dakikalık bir süre için geçici olarak kilitlenir. Eğer bu süre içinde ödeme tamamlanmazsa, koltuk tekrar boşaltılır. Bu, optimistik bir yaklaşımla birleşerek, çok sayıda kullanıcının aynı anda sistemle etkileşime girmesine olanak tanır. Ancak, asıl güncelleme yine asenkron bir süreçle gerçekleşir.
- Saga Deseni ile Ödeme ve Biletleme: Konser bileti satışı, ödeme, e-posta ile bilet gönderimi, üye profilinin güncellenmesi gibi birden fazla adımı içerir. Bu adımlar, başarısızlık durumunda geri alınabilmesi için Saga deseni kullanılarak yönetilir. Örneğin, ödeme başarısız olursa, rezerve edilen koltuğun tekrar serbest bırakılması bir telafi edici işlem olarak devreye girer.
Bu vaka analizleri, doğrudan koltuk tablosu güncellemesinin büyük ölçekli sistemlerde ne kadar yetersiz kaldığını ve neden asenkron, olay tabanlı ve dağıtık mimarilerin vazgeçilmez olduğunu açıkça göstermektedir. Bu sistemler, karmaşıklığı yönetilebilir parçalara bölerek, her bir parçanın kendi sorumluluğunu yerine getirmesini ve genel sistemin hem yüksek performanslı hem de güvenilir olmasını sağlamayı hedefler.
Sonuç: Geleceğin Rezervasyon Sistemleri Nereye Evriliyor?
Bu makalede, ölçeklenebilir rezervasyon sistemlerinin koltuk tablolarını neden doğrudan güncellemediğini derinlemesine inceledik. Geleneksel yaklaşımların eş zamanlılık, veri tutarlılığı ve performans açısından getirdiği sınırlamaları gördük. Ardından, modern sistemlerin bu zorluklarla nasıl başa çıktığını anlamak için mesaj kuyruk sistemlerini, olay tabanlı mimariyi (Event-Driven Architecture), etkinlik kaydını (Event Sourcing), CQRS'i ve Saga desenini detaylıca ele aldık.
Özetle, doğrudan veritabanı güncellemeleri, özellikle yüksek trafikli ve dağıtık sistemlerde bir darboğaz oluşturur, yarış koşullarına ve veri tutarsızlıklarına yol açar. Bu nedenle, modern rezervasyon sistemleri:
- İşlemleri asenkron hale getirerek kullanıcı deneyimini iyileştirir ve sistem yükünü yönetilebilir kılar.
- Mesaj kuyrukları kullanarak servisler arası bağımlılığı azaltır, ölçeklenebilirliği ve dayanıklılığı artırır.
- Olay tabanlı mimariler ve Event Sourcing ile sistem durumunu olay akışları üzerinden temsil eder, bu da denetlenebilirlik, zaman yolculuğu ve daha esnek entegrasyon imkanları sunar.
- CQRS ile okuma ve yazma işlemlerini ayırarak performans ve ölçeklenebilirliği optimize eder.
- Saga deseni ile dağıtık işlemlerdeki karmaşık iş akışlarını ve telafi edici durumları güvenilir bir şekilde yönetir.
Bu yaklaşımların birleşimi, günümüzün en büyük ve en kritik rezervasyon sistemlerinin temelini oluşturmaktadır. Gelecekte de bu trendin devam etmesi ve yapay zeka destekli optimizasyonlar, daha kişiselleştirilmiş deneyimler ve blockchain tabanlı güvenli biletleme gibi yeniliklerle bu mimarilerin daha da gelişmesi beklenmektedir. Artık bir dahaki sefere bir konser bileti alırken, arka planda ne kadar sofistike bir mühendislik harikasının çalıştığını daha iyi anlayacaksınız.
Sıkça Sorulan Sorular
-
Doğrudan güncelleme yöntemleri hiç mi kullanılmıyor?
Hayır, tamamen terk edilmiş değillerdir. Düşük trafikli, kritik olmayan veya tek bir veritabanı etrafında dönen küçük ölçekli uygulamalarda hala kullanılabilirler. Ancak, ölçeklenebilirlik, yüksek eş zamanlılık ve dağıtık sistemler söz konusu olduğunda modern asenkron yaklaşımlar tercih edilir.
-
Asenkron işlemler veri tutarlılığını nasıl etkiler?
Asenkron işlemler genellikle "nihai tutarlılık (eventual consistency)" modelini benimser. Bu, verinin anında tüm sistemde tutarlı olmayabileceği, ancak belirli bir süre sonra tüm değişikliklerin yayıldığı ve sistemin tutarlı bir duruma geleceği anlamına gelir. Bu model, anlık tutarlılıktan ödün vererek performansı ve ölçeklenebilirliği artırır.
-
Hangi mesaj kuyruğu sistemi tercih edilmeli?
Seçim, projenin ihtiyaçlarına bağlıdır. Apache Kafka yüksek hacimli ve gerçek zamanlı veri akışları için tercih edilirken, RabbitMQ daha çok kurumsal mesajlaşma ve karmaşık yönlendirme senaryoları için uygundur. Amazon SQS veya Azure Service Bus gibi bulut tabanlı çözümler ise yönetilebilirlik ve entegrasyon kolaylığı sunar.
-
Event Sourcing kullanmak veritabanı boyutunu artırır mı?
Evet, Event Sourcing her durumu değişimini bir olay olarak kaydettiği için veritabanı boyutu geleneksel yaklaşımlara göre daha fazla olabilir. Ancak, olaylar genellikle salt okunur (immutable) olduğu için depolama maliyetleri düşüktür ve optimize edilmiş olay depoları bu artışı yönetebilir. Ayrıca, anlık görüntüler (snapshots) alarak ve eski olayları arşivleyerek bu durum yönetilebilir.