Tüm yazılara dön
·5 dk okuma

Kesintisiz veri tabanı şema değişikliği: genişlet, geçir, daralt

Şema değişikliği, uygulama kodu değişikliğinden farklı bir risk taşır. Kod hatalıysa önceki sürüme dönebilirsiniz; ancak kolonu silmiş, tipini değiştirmiş ya da tabloyu bölmüşseniz geri dönüş çoğu zaman veri kaybı veya uzun bir kurtarma işi demektir. Üstelik modern yayın akışında dağıtım anında yalnızca tek bir uygulama sürümü çalışmaz: rolling update sırasında eski ve yeni örnekler aynı veri tabanını bir süre birlikte kullanır.

Bu yazıda kesintisiz şema değişikliği için sık kullanılan “genişlet, geçir, daralt” (expand/contract) yaklaşımını ele alıyorum. Amaç, her adımı ayrı ayrı güvenli ve geri alınabilir tutmak.

Sorun: iki sürüm aynı şemayı kullanır

Bir Customers tablosunda Name kolonunu FullName olarak değiştirmek istediğinizi düşünün. Tek bir migrasyonda kolonu yeniden adlandırırsanız, henüz güncellenmemiş uygulama örnekleri Name kolonunu sorgulamaya devam eder ve hata verir. Uygulamayı önce yayınlarsanız bu kez yeni kod henüz var olmayan FullName kolonunu arar.

Hangi sırayı seçerseniz seçin, bir pencere oluşur. Çözüm, değişikliği şemayla uygulamanın aynı anda uyumlu olduğu küçük adımlara bölmektir. Her adımda eski sürüm de, yeni sürüm de çalışabilmelidir.

Genişlet: eskiyi bozmadan yeniyi ekle

İlk aşama yalnızca ekleme yapar. Yeni FullName kolonu eklenir ve boş değer kabul eder; çünkü mevcut satırlarda henüz değeri yoktur ve eski kod bu kolonu hiç yazmaz.

ALTER TABLE Customers ADD FullName nvarchar(200) NULL;

Bu adım eski uygulama için görünmezdir. Dikkat edilecek nokta, kolonu varsayılan değer veya NOT NULL ile eklemenin bazı veri tabanlarında büyük tabloyu kilitleyebilmesidir. Davranış motora ve sürüme göre değiştiği için değişikliği önce üretime benzer boyuttaki bir kopyada denemek gerekir. Büyük tablolarda kolonu boş ekleyip değerleri sonra doldurmak genellikle daha öngörülebilirdir.

İndeks eklemek de aynı dikkati ister. Birçok motor çevrimiçi indeks oluşturmayı destekler, fakat bunun koşulları ve ek yükü farklıdır. Üretimde indeks oluştururken ilgili motorun belgelerini kontrol edin.

Çift yazma: yeni kod iki yere yazsın

İkinci aşamada uygulama güncellenir. Yeni sürüm, kayıt oluştururken ve güncellerken hem Name hem FullName kolonuna yazar. Okuma tarafında ise önce FullName değerine bakar, boşsa Name değerine döner.

public sealed class Customer
{
    public string Name { get; set; } = "";
    public string? FullName { get; set; }

    public string DisplayName => FullName ?? Name;

    public void Rename(string value)
    {
        Name = value;      // eski sürümler hâlâ bu kolonu okuyor
        FullName = value;  // yeni kolon
    }
}

Bu geçiş sürümü iki yönden de uyumludur. Eski örnekler yalnızca Name ile çalışır; yeni örnekler her iki kolonu da doğru tutar. Rolling update ortasında bir istek hangi örneğe giderse gitsin sonuç tutarlıdır.

Geri doldurma: eski veriyi küçük parçalarla taşı

Çift yazma yalnızca yeni değişiklikleri kapsar. Mevcut satırların FullName değerini ayrıca doldurmak gerekir. Tek bir UPDATE ile milyonlarca satırı değiştirmek uzun bir işlem, büyük kilit ve şişen işlem günlüğü demektir. Bunun yerine veriyi sınırlı parçalarla işleyin.

UPDATE TOP (5000) Customers
SET FullName = Name
WHERE FullName IS NULL;

Bu komut etkilenen satır sayısı sıfır olana kadar döngüde çalıştırılır; her partiden sonra kısa bir bekleme koymak canlı trafiğe yer açar. İşi arka plan işi olarak yazarsanız yeniden başlatılabilir olmalıdır: koşul zaten WHERE FullName IS NULL olduğu için iş yarıda kesilirse kaldığı yerden devam eder, iki kez çalışması da zarar vermez.

Bittiğinde doğrulayın: FullName IS NULL olan satır kalmadığını ve iki kolonun eşit olduğunu sorgularla kontrol edin. Bu kontrol, sonraki adım için “devam” kararının dayanağıdır.

Okumayı değiştir ve kısıtları sıkılaştır

Veri tamamlandıktan sonra sürüm artık yalnızca FullName kolonunu okur. Yazma tarafında ise bir süre daha iki kolona yazmaya devam etmek mantıklıdır; çünkü bu yayında sorun çıkarsa önceki sürüme dönüş hâlâ mümkün olmalıdır.

Sorunsuz bir gözlem döneminden sonra FullName kolonuna NOT NULL kısıtı eklenebilir. Bu aşamada eski kodun bu kolonu yazmadığı hiçbir örneğin kalmadığından emin olmak gerekir. Aksi hâlde eski sürümün ekleme sorgusu kısıt nedeniyle hata verir. Dağıtım sistemindeki aktif sürümleri kontrol etmek ve zamanlanmış işleri de unutmamak bu yüzden önemlidir.

Daralt: eski kolonu ayrı bir yayınla kaldır

Son aşama, artık hiçbir kodun kullanmadığı Name kolonunu kaldırmaktır. Bunu geri dönüşü olmayan bir adım olarak görün ve ana yayından ayrı yapın. Kolonu silmeden önce şunları kontrol edin: depodaki tüm sorgular, raporlar, dışa aktarma işleri ve ETL süreçleri yeni kolona geçti mi, yedekten geri dönüş planı var mı?

Bazı ekipler kolonu hemen silmek yerine önce uygulama tarafında kullanımını kaldırır, bir veya iki sürüm bekler ve ancak sonra siler. Bu bekleme gereksiz görünebilir; fakat gece çalışan unutulmuş bir rapor ya da başka bir servisin doğrudan sorgusu çoğu zaman ancak böyle ortaya çıkar.

Pratik kurallar ve .NET tarafı

Birkaç kural işi kolaylaştırır. Migrasyonları küçük tutun ve her biri tek bir niyet taşısın. Ekleme ile silmeyi aynı yayında birleştirmeyin. Yıkıcı değişikliği uygulama sürümünden bağımsız ve en son adım olarak planlayın. Kilit süresini ve işlem günlüğü büyümesini üretime yakın veriyle ölçün.

EF Core kullanıyorsanız dotnet ef migrations script --idempotent ile üretilen betiği inceleyin ve uygulamanın açılışında otomatik migrasyon çalıştırmak yerine dağıtım hattında kontrollü bir adım olarak uygulayın. Rolling update sırasında birden fazla örnek aynı anda migrasyon çalıştırmaya kalkarsa yarış durumları oluşabilir. Üretilen betiğin yeniden adlandırma için kolonu silip yeniden oluşturabildiğine de dikkat edin; böyle bir işlem veri kaybıdır ve elle düzeltilmelidir.

Son olarak, her aşamayı geri alma açısından düşünün: genişletme adımı kolayca geri alınır, çift yazma sürümü önceki sürüme dönebilir, geri doldurma tekrar çalıştırılabilir. Yalnızca son daraltma adımı kalıcıdır; bu yüzden en son ve en dikkatli yapılan adım olmalıdır.

Kesintisiz şema değişikliği, tek bir akıllı komuttan çok disiplinli bir sıradır. Değişikliği genişletme, çift yazma, geri doldurma, okuma geçişi ve daraltma olarak ayırdığınızda her yayın küçük, gözlemlenebilir ve geri alınabilir olur; veri tabanı da uygulamayla aynı güvenle evrilebilir.