Optimistic vs Pessimistic Concurrency

Eşzamanlı veri değişikliklerinde çakışmayı önlemek için iyimser (version/rowversion) ve kötümser (lock) eşzamanlılık modellerinin farkları ve uygulamaları açıklanır.

Optimistic vs Pessimistic Concurrency

Optimistic vs. Pessimistic Concurrency: Veri Çakışmalarını Yönetme Yöntemleri

Çok kullanıcılı veritabanı uygulamalarında, aynı veri üzerinde eşzamanlı değişiklikler kaçınılmazdır. İki kullanıcı aynı anda aynı kaydı güncellemeye çalıştığında, son güncelleme öncekini geçersiz kılar (lost update) veya veri tutarsızlığı oluşur. Bu sorunu çözmek için iki temel eşzamanlılık denetimi (concurrency control) modeli vardır: İyimser (Optimistic) ve Kötümser (Pessimistic). Bu yazıda, her iki modelin çalışma prensiplerini, SQL Server ve Entity Framework Core'daki uygulama yöntemlerini ve hangi senaryoda hangisinin tercih edilmesi gerektiğini detaylıca ele alacağız.


1. Temel Felsefe: İyimserlik ve Kötümserlik

Kriter Optimistic Concurrency (İyimser) Pessimistic Concurrency (Kötümser)
Felsefe "Çakışma nadirdir, işlem bittiğinde kontrol ederim." "Çakışma sıktır, baştan kilitleyim."
Kilit Mekanizması Veritabanı kilidi yok. Sadece güncelleme anında kontrol. Kaynaklar (satır, sayfa, tablo) işlem boyunca kilitlenir.
Performans Yüksek eşzamanlılıkta iyi (kilit yok). Kilit nedeniyle düşük eşzamanlılık, bekleme süreleri.
Kaynak Kullanımı Düşük (kilit yok). Yüksek (kilitler sunucu kaynağı tüketir).
Deadlock Riski Düşük (kilit olmadığı için). Yüksek (kilit sıralamasına bağlı).
Veri Tutarlılığı Güncelleme anında çakışma kontrol edilir. İşlem boyunca veri tutarlılığı garanti edilir.
Kullanım Alanı Okuma ağırlıklı, düşük çakışma olasılığı. Yazma ağırlıklı, yüksek çakışma olasılığı (ör. bilet rezervasyonu).

2. Optimistic Concurrency (İyimser Eşzamanlılık)

Bu model, kullanıcıların aynı veriyi aynı anda değiştirme olasılığının düşük olduğunu varsayar. Hiçbir kilit kullanılmaz. Veri okunur, işlenir ve güncellenirken, verinin güncellenmesi sırasında bir sürüm (version) veya zaman damgası (timestamp) kontrol edilir.

Çalışma Mantığı:

  1. Veriyi Oku: Kullanıcı veriyi okur. Aynı zamanda mevcut sürüm numarası (rowversion) veya zaman damgası da okunur.

  2. İşlemi Yap: Kullanıcı veriyi değiştirir.

  3. Güncellemeyi Dene: UPDATE komutu, sadece veritabanındaki sürüm numarası, okunan sürüm numarasıyla eşleşiyorsa çalıştırılır ve sürüm numarası bir artırılır.

  4. Çakışma Tespiti: Eğer sürüm numaraları eşleşmezse (başka biri veriyi güncellediyse), güncelleme başarısız olur ve uygulama bir DbUpdateConcurrencyException fırlatır.

SQL Server ve EF Core ile Uygulama:

SQL Server, eşzamanlılık denetimi için ideal olan rowversion (eski adıyla timestamp) veri tipini sunar. Bu sütun, her güncellemede otomatik olarak artan bir binary sayıdır.

sql

-- Tablo oluşturma
CREATE TABLE Products (
    Id INT PRIMARY KEY,
    Name NVARCHAR(100),
    Price DECIMAL(18, 2),
    RowVersion ROWVERSION NOT NULL  -- Her güncellemede otomatik değişir!
);

EF Core'da Optimistic Concurrency:

csharp

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    
    // RowVersion sütununu eşle
    public byte[] RowVersion { get; set; }
}

// OnModelCreating içinde:
modelBuilder.Entity<Product>()
    .Property(p => p.RowVersion)
    .IsRowVersion(); // EF Core'a bu sütunun concurrency token olduğunu söyler

Bir güncelleme sırasında, EF Core, WHERE cümlesine otomatik olarak RowVersion kontrolü ekler. Eğer eşleşmezse, DbUpdateConcurrencyException fırlatılır.

Çakışma Durumunda Yeniden Deneme (Retry):

csharp

try
{
    await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
    // Çakışma var! Kullanıcıya bilgi ver ve yeniden dene veya çakışan verileri göster.
    var entry = ex.Entries.Single();
    var currentValues = entry.CurrentValues; // Kullanıcının değiştirdiği değerler
    var databaseValues = entry.GetDatabaseValues(); // Veritabanındaki güncel değerler

    // Kullanıcıya çakışma hakkında bilgi ver ve ne yapmak istediğini sor
    // Kullanıcı "Benim versiyonumla güncelle" derse:
    entry.OriginalValues.SetValues(databaseValues); // Veritabanındaki sürümü al
    await context.SaveChangesAsync(); // Tekrar dene
}

3. Pessimistic Concurrency (Kötümser Eşzamanlılık)

Bu model, çakışmaların sık olacağını varsayar. Veri okunduğu anda, güncelleme işlemi tamamlanana kadar kilitlenir. Başka hiçbir işlem bu veriyi okuyamaz veya değiştiremez.

Kilit Türleri:

SQL Server, farklı senaryolar için çeşitli kilit ipuçları sunar:

  • WITH (UPDLOCK): Güncelleme kilidi alır. Başka işlemler okuyabilir (NOLOCK olmadan) ancak güncelleyemez.

  • WITH (HOLDLOCK) / SERIALIZABLE: Kilit, transaction sonuna kadar tutulur. En katı izolasyon seviyesidir.

  • WITH (XLOCK): Özel (exclusive) kilit alır. Başka hiçbir işlem okuyamaz veya yazamaz.

SQL ile Örnek:

sql

BEGIN TRANSACTION;

SELECT * FROM Products WITH (UPDLOCK) WHERE Id = 1;
-- Bu satır, transaction sonlanana kadar kilitlenir.
-- Veriyi oku, işle...
UPDATE Products SET Price = 100 WHERE Id = 1;

COMMIT TRANSACTION; -- Kilit burada serbest kalır.

EF Core'da Pessimistic Concurrency (Raw SQL ile):

EF Core, doğrudan pessimistic concurrency desteği sunmaz (bu işlem genelde veritabanı tarafında yapılır). Ancak ExecuteSqlRaw veya FromSqlRaw ile ham SQL kullanabilirsiniz.

csharp

// Kilitli okuma için ham SQL
var product = await context.Products
    .FromSqlRaw("SELECT * FROM Products WITH (UPDLOCK) WHERE Id = {0}", id)
    .FirstOrDefaultAsync();

// Güncelleme yap...
await context.SaveChangesAsync(); -- Veya ham SQL ile UPDATE

Dikkat: Transaction'lar kullanılmazsa kilitler serbest kalmaz!


4. Hangi Model Ne Zaman Kullanılmalı?

Optimistic Concurrency (İyimser) Kullanılacak Senaryolar:

  • Çakışma Olasılığı Düşükse: Kullanıcıların aynı veriyi aynı anda değiştirme ihtimali azsa.

  • Yüksek Eşzamanlılık (Concurrency) Gerekiyorsa: Çok sayıda kullanıcı aynı anda okuma/yapıyorsa ve kilitler performansı öldürüyorsa.

  • Web Uygulamaları: Kullanıcı deneyimi, uzun süreli kilit beklemelerinden daha önemliyse.

  • Maliyet ve Yönetim: Kilit yönetiminin getirdiği karmaşıklıktan kaçınmak istiyorsanız.

Pessimistic Concurrency (Kötümser) Kullanılacak Senaryolar:

  • Çakışma Olasılığı Yüksekse: Bilet rezervasyonu, stok yönetimi, sınırlı kaynak tahsisi gibi aynı veriye sık erişilen durumlar.

  • Tutarlılık Kritikse: Finansal işlemlerde, aynı hesaptan yapılan çekimlerde, veri bütünlüğü hayati öneme sahipse.

  • Transaction Boyunca Tutarlılık Gerekiyorsa: Kullanıcı bir kaydı düzenlerken, başka bir kullanıcının o kaydı görmesini veya değiştirmesini istemiyorsanız.

  • Kısa Süreli Transaction'lar: Kilit süresi kısa tutulabildiğinde.


5. Sık Yapılan Hatalar ve İpuçları

Hata / Tuzak Çözüm / Öneri
Optimistic kullanırken rowversion sütununu modelde unutmak Sütunu mutlaka ekleyin ve IsRowVersion() ile işaretleyin.
Pessimistic kullanırken transaction'ı kapatmayı unutmak Transaction bloklarını using içinde yapın veya COMMITi unutmayın.
Kilit beklemelerini (lock wait) izlememek SQL Server'daki sys.dm_tran_locks ile kilitleri izleyin.
Çok uzun süren transaction'lar Transaction'ları kısa tutun. Kullanıcıyı bekletmeyin.
Çakışma durumunda kullanıcıya uygun mesaj göstermemek Çakışma detaylarını gösterip, kullanıcıya seçenek sunun (yeniden deneme, verileri göster).

Performans İpuçları:

  • Optimistic: rowversion sütununu indeksleyin. Bu, çakışma kontrolünü hızlandırır.

  • Pessimistic: Mümkünse READ COMMITTED SNAPSHOT izolasyon seviyesini kullanarak okuma işlemlerinin kilit almasını engelleyin.

  • Hibrit Yaklaşım: Veriyi oku (Optimistic), güncelleme sırasında kilit al (Pessimistic) gibi karma modeller de mümkündür.


6. .NET Core ve EF Core'da En İyi Pratikler

  1. Varsayılan: Optimistic Concurrency (EF Core'da IsRowVersion ile).

  2. Gerektiğinde Pessimistic: Sadece çakışma kritik olan kaynaklar için.

  3. Çakışma Yönetimi Stratejisi: DbUpdateConcurrencyException'ı yakalayın, kullanıcıya çakışan verileri gösterin ve ne yapmak istediğini sorun (kendi değerlerini mi, yoksa veritabanındaki değerleri mi kullanacağını).

  4. Transaction'ları Kısa Tutun: Kullanıcı arayüzü (UI) işlemlerinde uzun süren transaction'lardan kaçının.

  5. İzleme: Hangi modeli kullanırsanız kullanın, deadlock ve lock wait sürelerini düzenli olarak izleyin.

Sonuç:

Optimistic ve Pessimistic concurrency modelleri, veritabanı uygulamalarında veri tutarlılığını sağlamak için iki farklı felsefeyi temsil eder. Optimistic, çakışmaların nadir olduğu, yüksek performans gerektiren sistemlerde idealdir. Pessimistic, çakışmaların sık ve tutarlılığın kritik olduğu senaryolarda (bilet rezervasyonu, stok yönetimi, finansal işlemler) vazgeçilmezdir.

Seçim yaparken, uygulamanızın iş yükünü, eşzamanlılık beklentisini ve veri çakışma olasılığını göz önünde bulundurun. Çoğu web uygulaması için Optimistic concurrency yeterlidir ve daha az karmaşıklık sunar. Ancak, kritik veriler söz konusu olduğunda, Pessimistic concurrency veya hibrit bir yaklaşım tercih edilebilir. Unutmayın, doğru modeli seçmek, uygulamanızın kararlılığı ve kullanıcı deneyimi için hayati önem taşır.

Tüm yazılar