EF CORE MIGRATIONS STRATEJİSİ

Üst üste binmiş veritabanı katmanları ve mor versiyon noktalarıyla EF Core migration sürüm hattı

Cuma akşamı saat 19:00. Üretim deploy'u başladı. dotnet ef database update komutu çalıştı, transaction yarıda kaldı, veritabanı yarı-migrated halde donuk. Müşteri telefon ediyor, sistem yarın açık olmak zorunda. Bu sahne neredeyse her .NET takımının bir noktada yaşadığı klasik bir migration kazasıdır. Sorunun kaynağı genellikle EF Core'un kendisi değildir; migration'ların stratejisiz biçimde üretim ortamına bırakılmış olmasıdır. Bu yazıda hangi senaryoda hangi yaklaşımın seçileceğini, dev/prod farkını ve veri kaybı riskinin nasıl kontrol altına alındığını ele alıyoruz.

EF Core Migrations Ne Yapar

Migration, kod tarafındaki DbContext ve entity sınıflarındaki değişikliklerden veritabanı şemasına karşılık gelen SQL'i üretir. Klasik akış üç komutla yürür: dotnet ef migrations add <Ad> ile değişikliği yakalar, dotnet ef migrations script ile SQL'i çıkarır, dotnet ef database update ile uygular. Üretilen migration dosyası iki metot içerir: Up() şemayı ileri taşır, Down() geri alır.

Bu mekanizma yerel makinede çalıştırırken çok rahattır. Bir property ekledin, migration üret, çalıştır, bitti. Sorun, aynı rahatlığı üretime taşımaya kalktığında başlar.

Geliştirme ve Üretim Ortamı Aynı Şey Değildir

Geliştirme ortamında veritabanı tek kullanıcılıdır, downtime kabul edilebilir, veri kaybı umursanmaz çünkü kolayca yeniden seed edilir. Üretimde ise tam tersi: çok kullanıcı, sıfıra yakın downtime, gerçek müşteri verisi. Aynı komutu (database update) iki ortamda çalıştırmak yaygın bir hatadır.

Üretime giden her migration'ın önceden gözden geçirilmesi gerekir. Auto-migrate akışı — yani uygulamanın açılışta db.Database.Migrate() çağırması — geliştirmede idealdir, üretimde tehlikelidir. Birden çok instance aynı anda ayağa kalkarsa race condition oluşur. Migration'ın yaratacağı kilit pattern'i, özellikle SQL Server'da büyük tablolarda ALTER TABLE, saatlerce sürebilir ve uygulama bu süre boyunca açılış aşamasında takılı kalır.

Üretim İçin Üç Sağlıklı Strateji

Pratikte üç temel yaklaşım yaygındır. Hangisinin seçileceği takım büyüklüğüne, deploy frekansına ve veritabanı boyutuna bağlıdır.

  • Idempotent SQL script: dotnet ef migrations script --idempotent komutu, hangi migration'ların uygulandığını kontrol eden, defalarca çalıştırılabilen bir SQL dosyası üretir. DBA bu scripti inceler, gerekirse manuel düzenler, ayrı bir adımda çalıştırır. Üretim için en güvenli yaklaşım budur.
  • Bundle (EF Core 6 ve sonrası): dotnet ef migrations bundle standalone bir executable üretir. .NET SDK'sı kurulu olmayan sunucuda da çalışır. CI/CD adımlarında kullanışlıdır, deploy artifact'ı tek dosyadır.
  • Otomatik Migrate (yalnızca küçük ölçek): Uygulama başlangıcında db.Database.Migrate(). Tek instance, küçük tablolar, kabul edilebilir downtime varsa kullanılabilir. Çoklu instance veya kritik üretim için uygun değildir.

Konunun detaylarını uçtan uca işleyen Entity Framework Core eğitimi migration stratejileri, performans tuning ve LINQ optimizasyonunu birlikte ele alır.

Soldaki açık kapılı geliştirme kasası ve sağdaki kilitli üretim kasası ile dev ve prod ortamı karşılaştırması

Veri Kaybına Karşı Önlemler

EF Core bazı şema değişikliklerini destructive olarak işaretler ve uyarı verir. Sütun silme, sütun tipi daraltma, NOT NULL ekleme bu sınıfa girer. Auto-generated migration veri kaybı taşıyorsa Up() metodu migrationBuilder.DropColumn gibi çağrılar içerir. Bunu üretimde fark etmemek tamamen mümkündür.

Rename işlemi özellikle tuzaklıdır. EF Core property adının değiştiğini anlayamaz — drop ve add olarak yorumlar. Bunun yerine migration dosyasını manuel açıp migrationBuilder.RenameColumn çağrısıyla değiştirmek gerekir. Aksi takdirde eski sütundaki veri tamamen kaybolur.

Pratik bir kural: her destructive migration için önce expand sonra contract adımı yapılır. Yeni sütun eklenir, uygulama iki sütuna da yazar, eski sütun ayrı bir deploy'da temizlenir. Bu yaklaşım Martin Fowler'ın yıllar önce yazdığı evolutionary database design prensiplerine dayanır ve resmi dokümantasyonda da bu sırayla önerilir.

CI/CD Pipeline'a Entegrasyon

Migration deploy'u uygulama deploy'undan ayrılmalıdır. Tipik bir pipeline'da sıralama şu olur:

  1. Build aşamasında dotnet ef migrations script --idempotent --output migrate.sql üretilir, artifact olarak saklanır
  2. Staging veritabanında otomatik çalıştırılır, smoke test koşulur
  3. Üretim deploy'undan önce migration adımı çalışır (uygulama instance'ları henüz yeni kodu görmüyor)
  4. Yeni kod deploy edilir

Bu sıralama, "uygulama yeni şemayı bekliyor ama şema eski" senaryosunu engeller. Tersi durumda — kod eski, şema yeni — genellikle daha az hasarlı olur çünkü iyi yazılmış bir migration backward-compatible olur. Şema değişiklikleri ve uygulama sürümleri arasındaki bu pencere SQL pratiklerini de ilgilendirir; konuyu derinleştirmek için SQL eğitimi içeriklerinden yararlanabilirsiniz.

Build, script, staging ve prod aşamalı CI pipeline akışı yanında geri alma okuyla yedek kalkanı sembolü

Yaygın Tuzaklar

  • Migration dosyasını sonradan düzenlemek: Üretimde uygulanmış bir migration'ın Up() metodu değişmez. Yeni bir migration ekleyerek düzelt.
  • Aynı feature branch'te birden çok migration: Merge çakışmaları kaçınılmaz. Branch'i merge etmeden önce migration'ları squash et veya tek bir migration olarak yeniden üret.
  • Migration history tablosunu manuel düzenlemek: __EFMigrationsHistory tablosuna elle row eklemek kısa vadede sorunu örter, uzun vadede kaos yaratır.
  • Test ortamında migration test edilmemesi: CI'da prod benzeri veri hacmiyle migration süresi ölçülmelidir. 10 kayıtla 200 ms süren bir ALTER TABLE 5 milyon kayıtla 40 dakika sürebilir.
  • Connection string'i build-time'da hard-code etmek: Bundle veya script üretilirken farklı bir veritabanına bağlandığını sanmak yaygın bir hata kaynağıdır. Environment variable üzerinden geçilmelidir.

Migration stratejisi bir kez kurulduktan sonra takım için disipline edilmiş bir akış haline gelir. Önemli olan üretimi "yerel makineden bir komut" olarak görmemek, deploy zincirinin denetlenebilir bir parçası haline getirmektir. İlk birkaç migration'da harcanan zaman, ileride yaşanmayacak gece nöbetlerinin karşılığıdır.