Cuty'de bir salonun belirli bir saat dilimine en fazla bir randevu konabilir — bunu garanti etmek tek başına basit gibi görünür. Ama gerçek dünyada, aynı slotu hedefleyen iki kullanıcı milisaniyeler içinde "Onayla" düğmesine basabilir, hatta bir tanesinin ödemesi yarıda kalmış olabilir. Sistem bu yarışı kim kazanır, kim kaybeder konusunda net bir karar vermek zorunda.
Sorunun şekli
Klasik bir "optimistik kilit" veya "pesimistik kilit" tartışması, çoğu zaman akademik bir teori gibi sunulur. Cuty'de bu, gerçek bir kullanıcı deneyimi tasarımı problemidir: randevu almaya çalıştığını sandığı kullanıcıya iki saniye sonra "başkası aldı" demek, sistemi gözden düşürür. Hızlı yanıt kadar, kullanıcının ne olduğunu anladığı bir akış da kritik.
İki temel kısıtla çalışıyoruz:
- Veri tutarlılığı: Aynı slot iki kişiye verilemez — bu mutlak.
- Kullanıcı deneyimi: Yarışı kaybeden kullanıcıyı hızla bilgilendirip alternatifler sunmalıyız.
- Ödeme gecikmesi: Kullanıcı kart bilgilerini girerken slotu makul bir süre tutmalıyız; ama sonsuza kadar değil.
Yaklaşımımız: kademeli rezervasyon
Cuty'de bir randevu rezervasyonu üç aşamalıdır:
- Soft hold(5 dakika): Kullanıcı "Bu slotu istiyorum" dediğinde Postgres'te {slot_id, user_id, expires_at}satırı oluşur.
expires_at5 dakika sonrası. Bu süre içinde başka kullanıcı aynı slotu seçemez. - Confirm: Ödeme başarılı olunca soft hold "confirmed" statüsüne geçer; iptal süresi yönetmeliklere göre belirlenir.
- Garbage collection:
expires_atgeçmiş ama confirmed olmamış kayıtlar arka plan görevi ile temizlenir; slot tekrar hazır.
Yarışı kim kazanır?
Soft hold satırını oluştururken bir UNIQUE kısıtla slot_id üzerinde çakışmayı engelliyoruz. Postgres tarafında bu bir INSERT ... ON CONFLICT DO NOTHINGifadesidir. İki kullanıcı aynı saniyede dener; biri yazar, biri Reddedilir. Reddedilen kullanıcı "Bu slot az önce alındı, şu üç alternatif nasıl?" mesajını alır — sessiz bir hatayla bırakılmaz.
Neden optimistik değil?
Optimistik kilit, "çakışma az olur" varsayımıyla yapılır. Ama Cuty'de en talepkar slot saatlerinde (örneğin Cumartesi 18:00) gerçekten yoğun rekabet var. Pesimistik kilit yerine UNIQUE kısıt + kısa soft hold süresi seçtik çünkü:
- Pesimistik
SELECT FOR UPDATEPostgres'te kilit tutar; bu, transaction süresi kadar diğer kullanıcıları bekletir. - UNIQUE kısıt + INSERT, atomic ve hızlıdır; transaction süresi milisaniyelerle ölçülür.
- Soft hold, "ben bu slotu seçtim" mesajını UI tarafına hızla döndürmeyi sağlar.
Edge case'ler
Ödeme zaman aşımı: Kullanıcı 5 dakikadan uzun süre kart bilgilerinde takılırsa? Frontend timer'ı 4. dakikada kullanıcıyı uyarır; 5. dakikada otomatik olarak slotu serbest bırakırız. Eğer ödeme provider'ında işlem başlamış ama sonuçlanmamışsa, idempotency key ile çift rezervasyondan kaçınırız.
Sunucu çökerse?: expires_attabanlı garbage collection süreci dakikada bir çalışır. Yani sunucu olası hata durumunda da slot "sonsuza dek" kilitli kalmaz.
Saat dilimi farkları?: Tüm expires_at UTC olarak saklanır; sadece UI dönüştürür. Bu, Türkiye yaz/kış saati değişikliğinde de güvenilir kalır.
Sonuç
Cuty'de basit görünen bir "randevu çakışması" sorunu, gerçekte UI, backend, veri tabanı ve ödeme provider'ı arasındaki bir orkestrasyon problemidir. Çözümümüz; UNIQUE kısıt ile kanonik kararı veri tabanına bırakmak, soft hold ile UX'e nefes payı bırakmak ve garbage collection ile sistemin kendini toparlamasına izin vermek üzerine kurulu.
Cuty'nin TestFlight beta sürümünde bu mimariyi gözleyeceğiz. İlk gerçek dünya verileriyle birlikte hangi varsayımların doğru, hangilerinin revizyona ihtiyaç duyduğunu yine bu blogda paylaşacağız.