Alışveriş Sepeti

Sepetiniz boş

Veritabanı & Önbellek · 15 dk

Neden Redis? Flash Sale Sırasında Aşırı Satışı (Overselling) Önleyen Atomik Stok ve Kilit Mimarisi

On binlerce kullanıcının aynı anda son 3 stoğa saldırdığı Black Friday senaryolarında PostgreSQL'i kilitlemeden, yarış durumlarını (Race Condition) 0.8ms'de çözen Redis Lua ve Redlock mimarisi.

Yazar: Stilgen Core Engineering

Neden Redis? Flash Sale Sırasında Aşırı Satışı (Overselling) Önleyen Atomik Stok ve Kilit Mimarisi

1. Klasik İlişkisel Veritabanı Stok Yönetiminin Çöküş Senaryosu

Tipik bir e-ticaret altyapısında (WooCommerce, Magento veya ham ORM modelleri), stok düşümü doğrudan PostgreSQL veya MySQL üzerinde çalışan tekil bir SQL güncellemesiyle yapılmaya çalışılır:

-- Klasik ve riskli SQL stok düşümü
UPDATE products 
SET stock = stock - 1 
WHERE id = 'prod_9921' AND stock > 0;

Bu yaklaşım sakin günlerde sorunsuz görünür. Ancak sınırlı sayıda üretilen bir koleksiyon lansmanında veya Black Friday indiriminde aynı saniyede 5.000 kullanıcı aynı ürünü sepete ekleyip ödemeye kalktığında felaket başlar:

  • Row-Level Lock Çıkmazı: Veritabanı motoru her istek için ilgili satıra Exclusive Lock koyar. Geriye kalan 4.999 istek veritabanı bağlantı havuzunda (Connection Pool) beklemeye geçer.
  • Bağlantı Havuzunun Tükenmesi (Pool Exhaustion): Varsayılan olarak 100-200 olan bağlantı limiti 200 milisaniye içinde dolar. Sitenin geri kalanındaki tüm sayfalar 504 Gateway Timeout vermeye başlar.
  • Aşırı Satış (Overselling): İzolasyon seviyesi READ COMMITTED olan ortamlarda aynı stok satırı aynı anda birden fazla transaction tarafından okunur. Sonuç: Stokta 10 adet olan ürün 28 kişiye satılır. Müşteri desteği kilitlenir, iade maliyetleri ve marka itibarı yerle bir olur.

2. Stilmerce Mimarisi: Bellek İçi Atomik Lua Scriptleri

Stilmerce'te yüksek frekanslı stok işlemleri asla diske yazan ana veritabanı üzerinde kilitlenmez. Bunun yerine, tek iş parçacıklı (single-threaded) yapısıyla doğası gereği yarış durumlarına izin vermeyen Redis kullanılır.

Kullanıcı "Satın Al" butonuna bastığında NestJS çekirdeği Redis üzerinde çalışan atomik bir Lua scripti yürütür:

-- stilmerce_reserve_stock.lua
-- KEYS[1]: Ürünün aktif stok anahtarı ('stock:sku_laptop_pro')
-- KEYS[2]: Sipariş bazlı rezervasyon anahtarı ('reservation:order_abc123')
-- ARGV[1]: Talep edilen adet (Örn: 1)
-- ARGV[2]: Rezervasyon TTL süresi (Örn: 900 saniye = 15 dakika)

local current_stock = tonumber(redis.call('GET', KEYS[1]) or '0')
local requested_qty = tonumber(ARGV[1])

if current_stock >= requested_qty then
    -- 1. Stoğu bellek üzerinde anında ve atomik olarak düş
    redis.call('DECRBY', KEYS[1], requested_qty)
    
    -- 2. Siparişe 15 dakikalık geçici rezervasyon kaydı yaz
    redis.call('SETEX', KEYS[2], tonumber(ARGV[2]), requested_qty)
    
    -- Kalan stoğu döndür (>= 0)
    return current_stock - requested_qty
else
    -- Yetersiz stok: -1 döndür
    return -1
end

Donanım Seviyesinde Atomisite

Redis, bir Lua scriptini çalıştırırken araya başka hiçbir işlemin girmesine izin vermez. Stokta son 1 ürün kalsa ve 1.000 kişi aynı milisaniyede istek atsa dahi, yalnızca ilk gelen istek >= 0 cevabı alır; diğer 999 istek anında -1 (Tükendi) cevabı alarak 0.8 milisaniyede sonuçlandırılır.

3. Geçici Rezervasyon (TTL Leases) ve Otomatik İade Döngüsü

Müşteri ödeme sayfasına geçtiğinde stok sonsuza kadar kilitlenmez. Stilmerce, Kiralama (Lease) Prensibi uygular:

+-------------------------------------------------------------------------------+
|                    STILMERCE ATOMİK REZERVASYON AKIŞI                         |
|                                                                               |
|  [Müşteri: "Ödemeye Geç"] ──► [Redis Lua Script] ──► [Stok Yeterli mi?]       |
|                                                            │                  |
|                        ┌───────────────────────────────────┴───────┐          |
|                        ▼ EVET                                      ▼ HAYIR    |
|             [15 Dakikalık TTL Kilidi]                           [0.8ms Hata:  |
|                        │                                         Stok Tükendi]|
|            ┌───────────┴───────────┐                                          |
|            ▼                       ▼                                          |
|     [Ödeme Başarılı]         [Zaman Aşımı / Sekme Kapatıldı]                  |
|            │                       │                                          |
|            ▼                       ▼                                          |
|  [PostgreSQL'e Kalıcı        [Redis Keyspace Notification:                    |
|   Sipariş Kaydı Yaz]          Stok Otomatik Ana Havuza İade]                  |
+-------------------------------------------------------------------------------+

Müşteri kart limitinin yetersiz olması sebebiyle ödemeyi tamamlayamazsa veya tarayıcıyı kapatırsa hiçbir arka plan cron'una ihtiyaç duyulmaz. Redis TTL süresi dolduğu an tetiklenen Keyspace Notification ile rezerve edilen stok anında ana stoğa iade edilir.

4. Dağıtık Kilit (Redlock): Çoklu Düğüm Güvenliği

Tek bir Redis düğümünün çökme ihtimaline karşı Stilmerce, Redis Cluster ve Redlock Algoritması kullanır:

// stock-reservation.service.ts
import Redlock from 'redlock';
import { Injectable } from '@nestjs/common';

@Injectable()
export class StockReservationService {
  private redlock: Redlock;

  constructor() {
    this.redlock = new Redlock([redisClient1, redisClient2, redisClient3], {
      driftFactor: 0.01,
      retryCount: 3,
      retryDelay: 200,
    });
  }

  async acquireLock(sku: string, ttlMs: number = 3000) {
    // sku bazlı dağıtık kilit al
    return await this.redlock.acquire([`lock:sku:${sku}`], ttlMs);
  }
}

Redlock sayesinde birden fazla Redis düğümünden çoğunluk (Quorum: 3'te 2) onayı alınarak kilit tahsis edilir. Ağ bölünmelerinde (Network Partition) dahi çifte kilitlenme engellenir.

5. Benchmark: SQL Tabanlı Kilitleme vs Stilmerce Redis Mimarisi

100.000 eşzamanlı sanal kullanıcı (VU) ile gerçekleştirilen yük testi sonuçları:

Ölçüt / Metrik Geleneksel SQL Kilitleme Stilmerce Redis Lua + Redlock
Saniye Başına İstek (RPS) ~420 RPS (Sonrası çöküş) 41.200+ RPS
Ortalama Gecikme (p99) 4.850 ms (Timeout riskli) 1.8 ms
Hata / Timeout Oranı %43.2 (Connection pool çöktü) %0.00 (Sıfır Hata)
Aşırı Satış Riski (Overselling) Yüksek (%3-6 Hata Payı) %0 (Mutlak Doğruluk)
Veritabanı CPU Yükü %100 (Tıkalı) %4 (Sakin)

Mühendislik Özeti

İlişkisel veritabanlarını doğalarına aykırı şekilde yüksek frekanslı stok yarışlarında kilit motoru olarak kullanmak yerine; bellek içi atomik Lua scriptleri ve Redlock kiralama mimarisi ile hem hız hem de kesin finansal tutarlılık garanti edilir.