Zero-Downtime Database Migration

Canlı sistemlerde veritabanı şeması değişikliklerini kesinti yaratmadan yapmak için expand/contract pattern, backward-compatible migration ve araçlar (Flyway, DbUp) anlatılır.

Zero-Downtime Database Migration

Zero-Downtime Database Migration: Canlı Sistemlerde Kesintisiz Veritabanı Güncelleme Stratejileri

Veritabanı şeması değişiklikleri (schema migrations), yazılım yaşam döngüsünün en riskli ve en zorlu operasyonlarından biridir. Geleneksel yaklaşımda, uygulamayı durdurup (maintenance mode), veritabanını güncellemek ve ardından uygulamayı yeniden başlatmak, günümüzün 7/24 çalışan sistemleri için kabul edilemez bir durumdur. Zero-Downtime (Sıfır Kesinti) Veritabanı Geçişi, bu riski ortadan kaldırmak için kullanılan bir dizi strateji ve deseni ifade eder.

Bu yazıda, veritabanı şemasını canlı sistemlerde kesintisiz olarak nasıl güncelleyeceğinizi, Expand/Contract (Genişlet/Sıkıştır) desenini, Backward-Compatible (Geriye Dönük Uyumlu) migration'ları ve bu süreci otomatikleştiren araçları (Flyway, DbUp) inceleyeceğiz. Ayrıca, .NET/EF Core ile bu stratejilerin nasıl uygulanacağına dair pratik ipuçları vereceğiz.


1. Neden Zero-Downtime Migration? Geleneksel Yaklaşımın Riskleri

Geleneksel veritabanı güncelleme süreci genellikle şu şekildedir:

  1. Uygulamayı durdur (Maintenance mode).

  2. Veritabanı yedeklemesi al.

  3. SQL script'lerini çalıştır (ALTER TABLE, ADD COLUMN, vb.).

  4. Uygulamayı yeniden başlat.

Bu yöntem, küçük projeler için çalışabilir, ancak büyük ve kritik sistemlerde aşağıdaki riskleri taşır:

  • Uzun Süreli Kesinti (Downtime): Büyük tablolarda ALTER TABLE işlemleri saatler sürebilir.

  • Riskli Geri Dönüş (Rollback): Hata durumunda, veritabanını eski haline döndürmek (restore) uzun sürer ve veri kaybına neden olabilir.

  • Kullanıcı Memnuniyetsizliği: 7/24 hizmet veren uygulamalarda kesinti kabul edilemez.

Zero-Downtime Migration, bu riskleri en aza indirir ve uygulamanın çalışmaya devam etmesini sağlar.


2. Expand/Contract (Genişlet/Sıkıştır) Deseni

Bu desen, veritabanı şeması değişikliklerini birden fazla aşamaya (phase) bölerek uygulamak ve eski/yeni kod sürümlerinin aynı anda çalışabilmesini sağlamak üzerine kuruludur.

Aşama 1: Expand (Genişlet) - Yeni Sütun veya Tablo Ekleme

  • Yeni bir sütun (veya tablo) ekleyin, ancak bu sütunun boş (nullable) veya varsayılan bir değeri olduğundan emin olun.

  • Yeni kodu (yeni sürüm) dağıtın. Yeni kod, hem eski sütunu hem de yeni sütunu kullanabilir.

  • Eski kod (eski sürüm) çalışmaya devam eder ve bu değişiklikten etkilenmez (çünkü yeni sütun, eski kod tarafından görmezden gelinir).

Aşama 2: Contract (Sıkıştır) - Eski Kodu Devre Dışı Bırakma ve Sütunu Zorunlu Yapma

  • Tüm uygulama instance'ları yeni sürüme geçtikten (eski kod kalmadıktan) sonra, yeni sütunu doldurmak için bir veri taşıma işlemi (backfill) yapılır.

  • Veriler taşındıktan sonra, yeni sütun NOT NULL yapılabilir (eğer isteniyorsa) veya eski sütun kaldırılabilir (drop column).

  • Eğer eski sütun kaldırılıyorsa, bu işlem en son aşamada yapılmalıdır ve eski kodun artık bu sütuna ihtiyacı olmadığından emin olunmalıdır.

Örnek: Bir sütunu yeniden adlandırmak (rename) veya tipini değiştirmek için Expand/Contract:

  • Expand: Yeni sütunu (new_name) ekleyin. Verileri eski sütundan (old_name) yeni sütuna kopyalayın.

  • Contract (2): Kodu, yeni sütunu (new_name) kullanacak şekilde güncelleyin ve dağıtın.

  • Contract (3): Eski sütunu (old_name) kaldırın.


3. Backward-Compatible (Geriye Dönük Uyumlu) Migration'lar

Bir migration'ın backward-compatible (geriye dönük uyumlu) olması, eski kod sürümünün, yeni şema ile çalışmaya devam edebilmesi anlamına gelir. Bu, zero-downtime için kritiktir.

Başarılı (Zero-Downtime) Migration Kriterleri:

  1. Yeni Sütunlar NULLABLE veya Varsayılan Değerli Olmalıdır:

    • Eski kod, yeni sütunu görmezden gelir. Yeni sütuna bir değer yazılmazsa, NULL veya varsayılan değer kalır. NOT NULL bir sütun eklemek, eski kodun INSERT işlemlerini kırar.

  2. Sütun Silme İşlemi En Son Yapılmalıdır:

    • Önce kodu, sütunu kullanmayacak şekilde güncelleyin. Tüm instance'lar yeni koda geçtikten sonra sütunu silin.

  3. Tablo veya Sütun Yeniden Adlandırma (Rename) İki Aşamada Yapılmalıdır:

    • Yeni adı ekleyin, eski adı koruyun (çift yazma/okuma). Ardından kodu güncelleyip, eski adı kaldırın.

  4. İndeks Değişiklikleri Dikkatli Yapılmalıdır:

    • Yeni bir indeks eklemek (CREATE INDEX CONCURRENTLY ile) genellikle güvenlidir. Eski bir indeksi kaldırmak, sorgu performansını etkileyebilir, bu nedenle dikkatli olunmalıdır.

Sık Yapılan Hata: NOT NULL Sütun Eklemek:

sql

-- KÖTÜ! Bu işlem eski kodun INSERT çalıştırmasını engeller.
ALTER TABLE orders ADD COLUMN status INT NOT NULL;

Doğrusu:

sql

-- Önce NULLABLE ekle, verileri doldur, sonra NOT NULL yap.
ALTER TABLE orders ADD COLUMN status INT NULL;
-- Verileri güncelle (Backfill)
UPDATE orders SET status = 0 WHERE status IS NULL;
-- Artık NOT NULL yapılabilir (kod uyumlu hale geldikten sonra)
ALTER TABLE orders ALTER COLUMN status SET NOT NULL;

4. Zero-Downtime Migration Araçları (Flyway, DbUp)

Bu stratejileri manuel olarak uygulamak zordur ve hataya açıktır. Bu nedenle, veritabanı göçlerini otomatikleştiren araçlar kullanılır.

A. DbUp (.NET Ekosistemi)

DbUp, .NET uygulamaları için en popüler veritabanı migration aracıdır. SQL script'lerini (.sql dosyaları) sırayla çalıştırır ve hangi script'lerin uygulandığını bir tabloda (SchemaVersions) tutar.

  • Avantajları: .NET ile tam entegre, basit, CI/CD pipeline'larına kolay eklenir.

  • Kullanım: Script'leri bir klasöre ekler, uygulama başlangıcında (startup) çalıştırılır veya ayrı bir konsol uygulaması olarak tetiklenir.

csharp

// DbUp ile migration'ı çalıştırma
var upgrader = DeployChanges.To
    .SqlDatabase(connectionString)
    .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
    .LogToConsole()
    .Build();

var result = upgrader.PerformUpgrade();

B. Flyway (Java, ancak tüm platformlarla çalışır)

Flyway, daha çok Java ekosisteminde bilinse de, .NET dahil her ortamda kullanılabilir (CLI aracı olarak). Version kontrolü, basit ve güvenilirdir.

  • Avantajları: Çok popüler, çok sayıda veritabanını destekler, CI/CD ile kolay entegre olur.

  • Kullanım: flyway migrate komutu ile çalıştırılır.

C. EF Core Migrations (Geliştirme ve Yayın için)

EF Core'un kendi migration sistemi, geliştirme aşamasında çok kullanışlıdır. Ancak, zero-downtime stratejilerini uygulamak için yeterince esnek değildir (özellikle ALTER COLUMN veya DROP COLUMN durumlarında). Bu nedenle, genellikle DbUp veya Flyway ile birlikte kullanılır.

  • Önerilen Yaklaşım:

    • Geliştirme: EF Core migrations (hızlı ve kolay).

    • Production: DbUp / Flyway (daha kontrollü ve zero-downtime odaklı). EF Core'dan çıkan SQL script'leri, DbUp ile uygulanmak üzere manuel olarak düzenlenir.


5. Pratik Örnek: Zero-Downtime ile Sütun Eklemek (Expand/Contract)

Senaryo: Bir Orders tablosuna total_amount adında yeni bir sütun eklemek istiyoruz. Ancak bu işlemi kesintisiz yapmalıyız.

1. Aşama (Expand): Yeni Sütunu Ekle (NULLABLE)

sql

-- Migration 1 (Expand)
ALTER TABLE orders ADD COLUMN total_amount DECIMAL(18, 2) NULL;

2. Aşama (Backfill): Verileri Doldur

sql

-- Migration 2 (Data Backfill)
UPDATE orders SET total_amount = subtotal + tax + shipping 
WHERE total_amount IS NULL;

(Bu işlem, büyük tablolarda chunk'lar halinde yapılmalıdır.)

3. Aşama (Contract): Kodu Güncelle ve Eski Sütunları Kaldır

  • Önce uygulama kodunu, total_amount sütununu kullanacak şekilde güncelleyin.

  • Eski sütunlar (ör. subtotal, tax, shipping) hala kullanılıyorsa, kodu güncelledikten sonra eski sütunları kaldırabilirsiniz.

sql

-- Migration 3 (Contract)
ALTER TABLE orders DROP COLUMN subtotal;
ALTER TABLE orders DROP COLUMN tax;
ALTER TABLE orders DROP COLUMN shipping;

-- İsterseniz artık NOT NULL yapabilirsiniz (optional)
ALTER TABLE orders ALTER COLUMN total_amount SET NOT NULL;

6. Riskler ve İpuçları

  • Büyük Tablolarda ALTER TABLE: Büyük tablolarda ALTER TABLE ADD COLUMN işlemi (özellikle NOT NULL veya varsayılan değerle) çok uzun sürebilir. ADD COLUMN işlemini NULL olarak yapmak genellikle anlıktır (metadata değişikliği). Varsayılan değer eklemek, tüm satırları günceller, bu da uzun sürer.

  • Veritabanı Kilitleri (Locks): Uzun süren ALTER veya UPDATE işlemleri, tabloyu kilitler ve diğer işlemleri engeller. Bu nedenle, bu işlemleri düşük trafik saatlerinde veya sırayla (batch) yapın.

  • Transaction ve Log Tüketimi: Büyük UPDATE işlemleri, transaction log'larını doldurabilir. İşlemi küçük parçalara bölün (chunk).

  • Sürüm Yönetimi: Migration script'lerini sürümleyin (V1, V2, ...). Her script, ya ileri (up) ya da geri (down) migration içermelidir (opsiyonel).

Sonuç:

Zero-downtime veritabanı geçişi, modern yazılım geliştirme pratiklerinin bir gereğidir. Expand/Contract deseni, backward-compatible migration'lar ve doğru araçların (DbUp, Flyway) kullanımı ile bu hedefe ulaşmak mümkündür. Unutmayın: Veritabanı geçişleri, uygulama dağıtımları ile koordine edilmelidir. Her zaman eski kodun yeni şema ile çalışabildiğinden emin olun ve kritik operasyonları düşük trafik saatlerinde planlayın. Bu stratejileri uygulayarak, canlı sistemlerinizde sıfır kesinti ile veritabanı güncellemeleri yapabilirsiniz.

Tüm yazılar