Veritabanı Migration’ı Sistemi Kapatmadan Nasıl Yapılır? Zero-Downtime Geçişin Temelleri
Veritabanını kapatmadan migration yapın. Expand-contract, backfill, concurrent index, constraint validation ve güvenli rollout yöntemlerini öğrenin.

- Hedef kitle
- Geliştirici
- İçerik türü
- Teknik geçiş rehberi
Kısa özet Zero-downtime database migration’ın temel prensibi migration’ı tek bir ALTER TABLE komutu olarak görmek değil, eski ve yeni uygulama sürümlerinin geçici bir süre aynı veritabanı şemasıyla birlikte çalışabilmesini sağlamaktır. En güvenli yaklaşım çoğu zaman: Expand → Deploy → Backfill → Validate → Switch → Contract modelidir. Örneğin bir kolonu yeniden adlandırmak istiyorsanız eski kolonu doğrudan rename etmek yerine önce yeni kolonu eklemek, uygulamayı iki alanla uyumlu hale getirmek, eski veriyi kontrollü olarak taşımak, okumaları yeni alana geçirmek ve eski kolonu ancak hiçbir eski uygulama instance’ı kalmadıktan sonra silmek daha güvenlidir. PostgreSQL’in güncel dokümantasyonu CREATE INDEX CONCURRENTLY ile index oluşturulurken normal insert/update/delete işlemlerinin devam edebildiğini; NOT VALID + VALIDATE CONSTRAINT yaklaşımının ise büyük tablolarda constraint eklemenin concurrent update üzerindeki etkisini azaltabildiğini açıklıyor. MySQL 8.4 ise DDL operasyonlarını INSTANT, INPLACE ve COPY gibi farklı algoritmalarla çalıştırıyor; bazı online DDL işlemlerinde concurrent DML devam edebiliyor. (PostgreSQL) Ancak önemli bir nüans var: Zero downtime, “hiç kilit alınmaz” veya “migration hiçbir zaman performansı etkilemez” anlamına gelmez. Amaç planlı servis kesintisi gerektirmeden migration yapabilmektir.
Kısa doğrudan cevap
Bir production database migration’ını kesintisiz yapmanın genel modeli şöyledir:
1. Yeni schema'yı geriye uyumlu biçimde ekle
↓
2. Eski uygulamanın çalışmaya devam ettiğini doğrula
↓
3. Yeni uygulamayı deploy et
↓
4. Gerekirse old + new alanlara birlikte yaz
↓
5. Eski veriyi batch'ler halinde backfill et
↓
6. Veri bütünlüğünü doğrula
↓
7. Okumaları yeni schema'ya geçir
↓
8. Eski uygulama sürümlerinin kalmadığını doğrula
↓
9. Constraint/index'leri güvenli yöntemlerle tamamla
↓
10. Eski column/table/path'i daha sonraki release'te kaldır
En önemli kural:
Schema migration ile destructive cleanup aynı deployment içinde yapılmamalıdır. Özellikle rolling deployment kullanan sistemlerde eski application instance birkaç dakika daha çalışıyor olabilir.
Zero-downtime migration tam olarak neyi çözüyor?
Basit development ortamında şu akış sorun yaratmayabilir:
Uygulamayı durdur ↓ Migration çalıştır ↓ Yeni kodu deploy et ↓ Uygulamayı aç Production SaaS sisteminde ise:
- aktif kullanıcılar,
- API istekleri,
- background workers,
- webhook’lar,
- mobil client’lar,
eski deploy instance’ları aynı anda database kullanıyor olabilir. Dolayısıyla: Database migration ile: Application deployment aynı anda gerçekleşen tek atomik işlem değildir. Asıl problem burada başlar. En tehlikeli varsayım: “Deploy sırasında sadece yeni kod çalışıyor” Modern cloud platformlarında rollout çoğu zaman şöyledir: App v1 App v1 App v1 ↓ deploy App v1 App v2 App v2 ↓ App v2 App v2 App v2 Kısa bir süre: v1 + v2 aynı anda çalışabilir. Eğer database migration:
- RENAME COLUMN name TO full_name;
yaptıysa yeni application: full_name beklerken eski instance hâlâ: name okumaya çalışabilir. Sonuç: column does not exist hatasıdır. Zero-downtime migration’ın asıl konusu bu compatibility window’dur.
Expand and Contract modeli nedir?
En yaygın migration pattern’lerinden biridir. Prisma’nın güncel migration dokümantasyonu da production schema değişikliklerini kesintisiz gerçekleştirmek için expand and contract yaklaşımını açıkça dokümante ediyor. Prisma örneğinde eski alan korunurken yeni alan ekleniyor, uygulama geçici olarak her iki alanla çalışıyor, veri migrate ediliyor ve eski alan daha sonraki aşamada kaldırılıyor. (Prisma) Model iki ana bölümden oluşur. Expand Database eski uygulamayı kırmadan genişletilir. OLD schema + NEW schema birlikte bulunur. Contract Bütün application sürümleri yeni schema’ya geçtikten sonra eski yapı kaldırılır. OLD schema ❌ NEW schema ✅ Örnek: Kolon adını değiştirmek Şu tablomuz olsun: users id name email Yeni requirement: name → full_name Riskli migration
- RENAME COLUMN name TO full_name;
ve hemen ardından yeni kod deploy etmek. Tek deployment instance bulunan küçük bir sistemde çalışabilir. Rolling deployment’ta: Old App → name arıyor New DB → name yok problemine yol açabilir. Zero-downtime yaklaşımı Aşama 1 — Yeni kolonu ekle
- ADD COLUMN full_name TEXT;
Database: id name full_name email haline gelir. Eski application hâlâ name kullanabildiği için bozulmaz. PostgreSQL’in güncel sürümünde nullable bir column eklemek veya non-volatile bir default ile column eklemek normalde tüm tabloyu rewrite etmeyi gerektirmez; default değer metadata üzerinden tutulabilir. Buna karşılık volatile default veya birçok column type değişikliği tüm tablo ve index’lerin yeniden yazılmasını gerektirebilir. (PostgreSQL) Aşama 2 — Yeni application iki alana yazsın Örneğin:
- id: userId,
- },
- name: value,
- fullName: value,
- },
}) Geçici olarak: name + full_name ikisi birlikte güncellenir. Buna dual write denir. Aşama 3 — Okumaya eski kolondan devam edin İlk rollout’ta: WRITE → old + new READ → old kullanılabilir.
Neden?
Çünkü yeni alan henüz bütün eski kayıtlar için doldurulmamıştır. Aşama 4 — Eski kayıtları backfill edin Örneğin:
- WHERE full_name IS NULL;
Küçük tabloda yeterli olabilir. Ama: 50 milyon row bulunan production tablosunda tek transaction ile çalıştırmak risklidir.
Büyük backfill neden tek sorguyla yapılmamalı?
Şu sorgu:
- SET full_name = name;
teknik olarak basittir. Ancak büyük tabloda:
- uzun transaction,
- yüksek WAL/binlog,
- replication lag,
- disk I/O,
- row locks,
- autovacuum baskısı,
- database CPU,
connection contention oluşturabilir. Daha kontrollü yaklaşım: 1.000 kayıt ↓
↓ 1.000 kayıt ↓
gibi batch migration olabilir. Örnek batch backfill Pseudo-code: while (true) {
- fullName: null,
- },
- take: 1000,
})
break }
users.map((user) => db.user.update({
- id: user.id,
- },
- fullName: user.name,
- },
}) ) )
} Gerçek batch büyüklüğü:
- row boyutu,
- database kapasitesi,
- replication,
trafik ölçülerek belirlenmelidir.
Backfill neden application deployment’tan ayrılmalı?
Şu deployment:
Deploy ↓ 50 milyon row migration ↓ App başlasın CI/CD pipeline’ınızı dakikalar veya saatler boyunca bekletebilir. Ayrıca migration başarısız olursa: application release ile:
tek incident haline gelir. Daha sağlıklı: Schema migration ↓ Application deploy ↓ Background backfill olarak ayrılabilir. makaledeki queue + worker modeli büyük data migration’larda burada da kullanılabilir. Aşama 5 — Backfill doğrulanmalı Backfill: script finished dedi diye tamamlanmış kabul edilmemelidir. Örneğin:
- WHERE full_name IS NULL;
sonucu: 0 olmalıdır. Daha kritik migration’larda ayrıca karşılaştırma yapılabilir:
- WHERE full_name IS DISTINCT FROM name;
Aşama 6 — Okumaları yeni alana geçir Sonraki release: WRITE → old + new READ → new haline gelebilir. Böylece yeni alan production read path üzerinde test edilir. Sorun oluşursa: READ → old rollback hâlâ mümkündür. Aşama 7 — Eski alana yazmayı bırak Yeni kolon doğrulandıktan sonra: WRITE → new READ → new yapılır. Eski kolon artık kullanılmıyor. Ama henüz silinmez. Aşama 8 — Bekleyin Bu adım gereksiz görünür fakat önemlidir. Şunların kalmadığından emin olun: Eski deploy instance Eski worker Cron job Admin script Legacy API Eski mobile backend path Data export script Eski kolon gerçekten hiç kullanılmıyorsa contract aşamasına geçilir. Aşama 9 — Eski kolonu kaldır Artık:
- DROP COLUMN name;
uygulanabilir. PostgreSQL’de DROP COLUMN işlemi kolonu mantıksal olarak erişilemez hale getirir ancak kapladığı disk alanını hemen fiziksel olarak geri kazanmayabilir; alan daha sonraki row rewrite’ları sırasında geri kazanılır. (PostgreSQL) Bu nedenle cleanup migration: aynı gün yapılmak zorunda değildir. Expand–Contract tam akışı Release 1 ------- Schema expand ADD full_name Release 2 ------- WRITE old + new READ old Background ---------- Backfill old → new Validation ----------
Release 3 ------- WRITE old + new READ new Release 4 ------- WRITE new READ new Observation window ------------------ Release 5 ------- DROP old Bu birkaç deployment gerektirebilir. Ama amaç deployment sayısını azaltmak değil: migration riskini azaltmaktır. “Tek deployment daha kolay değil mi?” Evet. Ayrıca daha risklidir. Zero-downtime sistemlerde deployment sayısının artması doğal olabilir. İşlem: Schema + Code + Data + Cleanup tek anda yapılmak yerine birbirinden ayrılır. Bu aynı zamanda rollback’i kolaylaştırır.
Additive değişiklikler neden daha güvenli?
Genellikle:
ADD işlemleri: DROP RENAME TYPE CHANGE işlemlerine göre daha kolay backward-compatible tasarlanır. Örneğin:
- ADD COLUMN phone TEXT;
eski kodu çoğu zaman etkilemez. Ama:
- DROP COLUMN phone;
eski application hâlâ alanı kullanıyorsa anında kırıcıdır. Bu nedenle migration planında önce: expand sonra: destructive cleanup gelmelidir.
Index eklemek de downtime yaratabilir mi?
Evet. PostgreSQL’de normal:
- ON orders(user_id);
işlemi index build boyunca tabloya yapılan write’ları bloklayabilir. Büyük production tablosunda index oluşturma saatler sürebileceği için bu ciddi etki yaratabilir. PostgreSQL bunun için:
- ON orders(user_id);
seçeneğini sunar.
CONCURRENTLY kullanıldığında insert, update ve delete işlemleri devam edebilir. (PostgreSQL)
CREATE INDEX CONCURRENTLY tamamen risksiz mi?
Hayır. PostgreSQL dokümantasyonu önemli dezavantajları açıkça belirtiyor: Standart index build’e göre daha fazla iş yapar. Daha uzun sürebilir. CPU/I/O yükü yaratabilir. Aynı tabloda aynı anda yalnızca bir concurrent index build çalışabilir. Transaction block içinde çalıştırılamaz. Başarısız olursa INVALID index bırakabilir. (PostgreSQL) Dolayısıyla:
değildir. Ama production write availability açısından güçlü bir araçtır. Migration tool kullanıyorsanız önemli bir nokta Bazı migration sistemleri SQL komutlarını transaction içinde çalıştırabilir. PostgreSQL:
komutunun transaction block içinde çalışmasına izin vermez. (PostgreSQL) Bu nedenle ORM tarafından otomatik üretilen migration:
kör biçimde production’a gönderilmemelidir. Generated SQL mutlaka review edilmelidir.
ORM migration dosyasına neden bakmalısınız?
ORM tarafında:
add index çok masum görünebilir. Generated SQL:
olabilir. Veya: rename işlemi ORM tarafından: DROP old ADD new şeklinde üretilebilir. Prisma dokümantasyonu da generated migration SQL’inin gerektiğinde manuel olarak özelleştirilmesini ve örneğin veri kaybına yol açabilecek drop/create yerine uygun rename işleminin kullanılmasını dokümante ediyor. (Prisma) Kural: ORM migration output’u source code gibi review edilmelidir.
Foreign key veya CHECK constraint eklemek downtime yaratabilir mi?
Büyük tabloda constraint eklemek mevcut verinin doğrulanmasını gerektirebilir. Örneğin:
ADD CONSTRAINT valid_amount
- CHECK (amount >= 0);
Database mevcut bütün kayıtları kontrol edebilir. Büyük tabloda bu scan uzun sürebilir.
PostgreSQL NOT VALID burada ne sağlıyor?
PostgreSQL:
ADD CONSTRAINT valid_amount CHECK (amount >= 0)
- NOT VALID;
ile constraint’i önce mevcut bütün kayıtları taramadan eklemeye izin verir. Yeni insert/update işlemleri constraint’e uymak zorundadır. Daha sonra:
- VALIDATE CONSTRAINT valid_amount;
ile geçmiş veriler kontrol edilebilir. PostgreSQL bu yaklaşımın ana amacının constraint eklerken concurrent update’ler üzerindeki etkiyi azaltmak olduğunu belirtiyor. Validation işlemi daha düşük seviyeli SHARE UPDATE EXCLUSIVE lock kullanır ve normal concurrent update’leri bloklamak zorunda değildir. (PostgreSQL) Foreign key için de kullanılabilir Örneğin:
ADD CONSTRAINT orders_customer_fk FOREIGN KEY (customer_id) REFERENCES customers(id)
- NOT VALID;
Sonra:
- VALIDATE CONSTRAINT orders_customer_fk;
PostgreSQL dokümantasyonu foreign key eklemenin etkisini azaltmak için bu modeli doğrudan örnekliyor. (PostgreSQL)
NOT NULL constraint büyük tabloda nasıl güvenli eklenebilir?
Diyelim yeni column:
customer_type önce nullable eklendi. Backfill tamamlandı. Doğrudan:
- SET NOT NULL;
mevcut kayıtların taranmasını gerektirebilir. PostgreSQL’in güncel dokümantasyonu geçerli bir CHECK constraint mevcutsa ve bu constraint NULL olamayacağını kanıtlıyorsa SET NOT NULL sırasında tam table scan’in atlanabildiğini belirtiyor. (PostgreSQL) Pratik pattern:
ADD CONSTRAINT customer_type_not_null CHECK (customer_type IS NOT NULL)
- NOT VALID;
Sonra:
- VALIDATE CONSTRAINT customer_type_not_null;
Ardından:
- SET NOT NULL;
Son olarak geçici check constraint kaldırılabilir. Bu teknik PostgreSQL’e özeldir; başka database engine’lerinde aynı SQL varsayılmamalıdır.
Column type değiştirmek neden riskli?
Örneğin:
- ALTER COLUMN amount TYPE NUMERIC(20,2);
bazı değişikliklerde çok pahalı olabilir. PostgreSQL dokümantasyonu mevcut column type’ın değiştirilmesinin normalde tüm tablo ve index’lerin rewrite edilmesini gerektirebileceğini belirtiyor; bazı binary-compatible istisnalar bulunuyor. (PostgreSQL) Bu nedenle büyük production tablolarında:
yerine expand-contract düşünülebilir. Type migration örneği Eski: amount_cents INTEGER Yeni: amount_decimal NUMERIC Expand
- ADD COLUMN amount_decimal NUMERIC(20,2);
Application WRITE → amount_cents + amount_decimal Backfill amount_decimal = amount_cents / 100 Switch read READ → amount_decimal Contract Daha sonraki release:
- DROP COLUMN amount_cents;
Bu yaklaşım doğrudan full-table type rewrite’ın etkisini azaltabilir.
Default değer eklemek her zaman pahalı mı?
Hayır. PostgreSQL’in güncel davranışında:
- ADD COLUMN status TEXT DEFAULT 'active';
gibi non-volatile default kullanan bir column eklemek için bütün tabloyu fiziksel olarak yeniden yazmak gerekmez; değer metadata üzerinden sunulabilir. Bu nedenle büyük tabloda bile işlem oldukça hızlı olabilir. (PostgreSQL) Ama örneğin volatile default: DEFAULT clock_timestamp gibi durumlar tüm tablo ve index’lerin rewrite edilmesini gerektirebilir. (PostgreSQL) Kural: Migration davranışını tahmin etmeyin; kullandığınız database sürümünün DDL dokümantasyonunu kontrol edin. MySQL’de durum farklı olabilir PostgreSQL tek veritabanı değildir. MySQL 8.4 online DDL için üç temel algoritma kullanıyor: INSTANT Sadece metadata değişebilir. INPLACE Tablonun tamamen kopyalanması gerekmeyebilir; bazı işlemlerde concurrent DML devam edebilir.
Yeni tablo oluşturulur ve veriler kopyalanır; concurrent DML mümkün olmayabilir. (MySQL Geliştirici Bölgesi) MySQL’de güvenli DDL örneği Uygun operasyonlarda:
- ADD COLUMN phone VARCHAR(50),
- ALGORITHM=INSTANT;
kullanılabilir. INSTANT desteklenmiyorsa query fail eder ve beklemediğiniz daha pahalı bir algoritmaya sessizce düşmesini engelleyebilirsiniz. Benzer biçimde uygun INPLACE operasyonlarında:
talep ederek concurrent read/write gerektiğini açıkça belirtebilirsiniz. MySQL, istenen concurrency seviyesi desteklenmiyorsa komutun hata vermesine izin veriyor. Bu, yanlışlıkla blocking DDL çalıştırma riskini azaltabilir. (MySQL Geliştirici Bölgesi)
MySQL INSTANT = hiç lock yok mu?
Hayır. MySQL dokümantasyonu INSTANT işlemlerinde bile kısa süreli exclusive metadata lock alınabileceğini belirtiyor. Bu nedenle: INSTANT kelimesi: 0 ms lock guarantee anlamına gelmez. (MySQL Geliştirici Bölgesi) Bu zero-downtime migration’ın en önemli nüanslarından biridir.
Neden “zero downtime” yerine bazen “online migration” demek daha doğru?
Çünkü:
Metadata lock olabilir. CPU artabilir. Disk I/O artabilir. Query latency geçici yükselebilir. Replication lag oluşabilir. Migration deadlock yaşayabilir. Dolayısıyla teknik olarak: Zero planned downtime veya: online migration daha doğru beklenti oluşturabilir. Ama sektörde zero-downtime migration terimi yaygın olarak kesintisiz migration stratejisini ifade eder.
Migration deployment sırası nasıl olmalı?
En güvenli sıralamalardan biri:
1. Backward-compatible schema migration
↓
2. App deploy
↓
3. Data backfill
↓
4. Verification
↓
5. App cutover
↓
6. Observation
↓
7. Destructive cleanup
Bu sırada her aşama ayrı rollback alanı sağlar.
Schema migration uygulamadan önce mi, sonra mı çalışmalı?
Değişikliğe bağlıdır. Expand aşamasında genellikle: Schema first ↓ App second mantıklıdır. Çünkü yeni app: new_column bekliyorsa database’de önce column bulunmalıdır. Contract aşamasında ise: App stops using old schema ↓ Old schema removed later olmalıdır. Basit formül: ADD önce
DROP sonra
Migration ve deployment aynı CI job’da olabilir mi?
Olabilir. Ama aynı job içinde bulunmaları: tek atomik işlem oldukları anlamına gelmez. Örneğin: migration başarılı ↓ app deploy başarısız olabilir. Bu nedenle migration her aşamada eski application sürümüyle uyumlu olmalıdır.
Rollback neden database migration’da zor?
Application rollback:
v2 → v1 çoğu zaman kolaydır. Database: DROP COLUMN sonrası: rollback ile kaybolan veriyi otomatik geri getiremezsiniz. Bu nedenle destructive migration için klasik: up down modeline aşırı güvenmek risklidir. En iyi rollback bazen: eski schema’yı henüz silmemek olabilir.
“Down migration var” neden güvenlik garantisi değil?
Örneğin migration:
- DROP COLUMN customer_notes;
çalıştırdı. Down migration:
- ADD COLUMN customer_notes TEXT;
olabilir. Column geri geldi. Ama içindeki veri: ❌ geri gelmedi. Bu nedenle: Schema rollback ile data rollback aynı şey değildir.
Database backup varsa migration güvenli midir?
Backup önemlidir. Ama: Production database → backup restore çoğu sistemde:
- zaman,
- veri kaybı,
- restore operasyonu,
yeni kullanıcı işlemlerinin kaybı anlamına gelebilir. Backup: migration design’ın yerine geçmez. Son savunma katmanıdır.
Migration öncesi production kopyasında test yapılmalı mı?
Özellikle büyük migration’larda çok değerlidir. Development database: 500 row iken production: 200 milyon row olabilir. Migration testinde şu metrikler ölçülebilir: DDL süresi Lock süresi CPU I/O Disk gereksinimi WAL/binlog artışı Replication lag PostgreSQL dokümantasyonu table/index rebuild işlemlerinin büyük tablolarda uzun sürebileceğini ve geçici olarak iki katına kadar disk alanına ihtiyaç duyabileceğini belirtiyor. (PostgreSQL)
Migration timeout ve lock timeout kullanılmalı mı?
Kritik production DDL’de değerlendirilebilir. Örneğin migration’ın: 20 dakika lock bekleyip sonra production’ı kilitlemesi yerine: Kısa lock alamıyorsan fail et yaklaşımı daha güvenli olabilir. Database’e göre: lock_timeout statement_timeout DDL lock options kullanılabilir. Bunların değerleri uygulamanın trafik profiline göre belirlenmelidir. Uzun transaction’lara dikkat Backfill:
↓ 20 milyon UPDATE ↓
şeklinde çalışırsa büyük transaction oluşabilir. Riskler:
- uzun lock lifecycle,
- replication etkisi,
- rollback maliyeti,
- vacuum/history retention,
WAL/binlog hacmi. Bu nedenle batch migration genellikle daha yönetilebilir olur.
Backfill rate limit uygulanmalı mı?
Evet. Örneğin: Batch: 5.000 row Pause: 250 ms gibi kontrollü processing yapılabilir. Ayrıca: DB CPU > %70 → yavaşla gibi adaptive backfill tasarlanabilir. Ama her proje için gerekli değildir.
Queue + worker büyük backfill için kullanılabilir mi?
Evet. Örneğin: Migration controller ↓ ID ranges ↓ Queue ↓ Workers kullanılabilir. Ancak 100 worker birden açmak database’i aşırı yükleyebilir. makaledeki prensip burada tekrar geçerlidir: Worker concurrency downstream database kapasitesiyle sınırlanmalıdır.
Dual write’ın kendi riski yok mu?
Var. Örneğin: write old ✅ write new ❌ olabilir. İki field aynı database transaction içindeyse risk daha kolay yönetilir. Farklı datastore’lara dual write yapılıyorsa distributed consistency problemi oluşur. Bu makaledeki basit expand-contract modeli aynı database içindeki schema migration için düşünülmelidir.
Dual read gerekli olabilir mi?
Bazen. Örneğin transition sırasında:
kullanılabilir. Bu: new data varsa → new yoksa → old modelidir. Backfill devam ederken faydalıdır. Ama fallback sonsuza kadar tutulursa migration hiçbir zaman tamamlanmaz. Bu yüzden telemetry ile eski path kullanımının sıfıra indiği doğrulanmalıdır. Feature flag migration’da yardımcı olabilir Örneğin:
ile başlayıp: false ↓ %5 users ↓ %50 ↓ true geçiş yapılabilir. Sorun oluşursa: flag off ile eski read path’e dönülebilir. Database schema hâlâ her iki alanı desteklediği için rollback kolaylaşır.
Migration telemetry’sinde ne tutulmalı?
Özellikle büyük geçişlerde:
Backfill progress Rows migrated Rows remaining Error count Batch duration Database CPU Replication lag Old-column reads Old-column writes izlenebilir. Son iki metrik contract kararında çok değerlidir.
Eski kolonun gerçekten kullanılmadığını nasıl anlarsınız?
Sadece:
“Kodda aradım, bulamadım.” yeterli olmayabilir. Eski erişim:
- legacy worker,
- BI query,
- cron,
- data pipeline,
başka service üzerinden gelebilir. Database query log, application telemetry veya repository search birlikte kullanılabilir.
Schema version bağımlılığı azaltılabilir mi?
Microservice veya çok client’lı sistemlerde önemlidir. Her servis: DB schema = tam olarak version 42 beklerse deployment coordination zorlaşır. Daha güçlü prensip: Her release kendinden önceki ve sonraki schema transition aşamasıyla uyumlu olmalı. Bu N / N+1 compatibility modeli gibi düşünülebilir. Zero-Downtime Migration Karar Matrisi Değişiklik Risk Önerilen yaklaşım Nullable column ekleme Düşük Direkt/additive Non-volatile default column Düşük/Orta DB sürüm davranışını doğrula Index ekleme Orta/Yüksek Online/concurrent index Foreign key Orta NOT VALID + validation gibi online yöntem NOT NULL Orta/Yüksek Backfill → validate → constraint Column rename Yüksek Expand-contract Column type change Yüksek Yeni column + backfill Column drop Yüksek Kullanım sıfırlandıktan sonra contract Büyük table rewrite Çok yüksek Ayrı plan / online DDL / shadow table Primary key değişimi Çok yüksek Özel migration projesi Bu tablo InoviqLab değerlendirmesidir. Kesin davranış database engine ve sürüme göre doğrulanmalıdır. Production Migration Kontrol Listesi Planlama Migration’ın additive mi destructive mi olduğu belirlendi. Etkilenen tablo boyutu biliniyor. Row sayısı biliniyor. Database engine ve sürümü doğrulandı. DDL lock davranışı dokümantasyondan kontrol edildi. Table rewrite gerekip gerekmediği kontrol edildi. Gerekli disk alanı değerlendirildi. Compatibility Eski application yeni schema ile çalışabiliyor. Yeni application transition schema ile çalışabiliyor. Rolling deployment senaryosu test edildi. Worker/cron/legacy servisler kontrol edildi. Destructive değişiklik ilk release’te yapılmıyor. Migration Migration SQL’i manuel review edildi. ORM tarafından generated SQL incelendi. Index online/concurrent oluşturulabiliyor mu kontrol edildi. Constraint validation stratejisi belirlendi. Lock timeout değerlendirildi. Migration staging’de çalıştırıldı. Backfill Backfill idempotent. Batch boyutu tanımlı. Progress izleniyor. Pause/rate limit mümkün. Retry stratejisi var. İşlem tekrar çalıştırılabilir. Validation Eksik kayıt sayısı kontrol ediliyor. Old/new veri karşılaştırması yapılıyor. Constraint validation tamamlandı. Application error rate kontrol ediliyor. Replication lag kontrol edildi. Cutover Read path kontrollü geçirildi. Feature flag varsa kullanıldı. Eski alana write sona erdi. Eski application instance kalmadı. Eski worker kalmadı. Contract Old schema kullanımı sıfırlandı. Cleanup ayrı migration olarak hazırlandı. Backup doğrulandı. Destructive migration onaylandı. Cleanup sonrası monitoring yapıldı.
Hangi migration’lar ayrı proje gibi ele alınmalı?
Özellikle:
Primary key değiştirme Büyük tablo type dönüşümü Partition yapısını değiştirme Çok büyük foreign-key graph Multi-terabyte backfill Sharding değişimi Database engine değiştirme basit ORM migration’ından daha kapsamlı plan gerektirebilir.
Zero-downtime migration her sistem için gerekli mi?
Hayır. Bir internal sistem: 02:00–02:05 bakım yapabiliyor ve bunun business maliyeti yoksa: 5 dakikalık maintenance window çok daha basit ve düşük riskli olabilir. Zero-downtime mimari de mühendislik maliyeti yaratır. Bu nedenle amaç: “Downtime teknik olarak kötü.”
- değil,
“Downtime’ın business maliyeti bu ek karmaşıklığa değer mi?” sorusunu cevaplamaktır. InoviqLab teknik değerlendirmesi Bu bölüm InoviqLab değerlendirmesidir; PostgreSQL, MySQL veya Prisma’nın normative dokümantasyonundan ayrıdır. Database migration’larında en sık gördüğümüz temel hata: Schema migration ile application release’i aynı şey sanmak. Gerçekte production değişikliği çoğu zaman: Schema + Application + Data + Traffic + Background workers arasındaki koordinasyondur. En güçlü prensip: önce genişlet, sonra daralt Riskli düşünce: Eski schema'yı kaldır ↓ Yeni schema'yı kullan Daha güvenli: Yeni schema'yı ekle ↓ İki sürümü uyumlu tut ↓ Veriyi taşı ↓ Yeni schema'yı kullan ↓ Eski schema'yı kaldır Bu yaklaşım yalnızca database için değil, API contract migration için de güçlü bir modeldir. İkinci prensip: rollback’i migration’dan önce tasarla Migration tamamlandıktan sonra: “Sorun çıkarsa ne yaparız?” diye düşünmek geç olabilir. Özellikle destructive değişikliklerde rollback: eski uygulamaya dön kadar basit olmayabilir. Bu nedenle migration planının parçası: Rollback Path olmalıdır. Üçüncü prensip: Data migration ile schema migration’ı ayır Örneğin: ADD COLUMN hızlı olabilir. Ama: 200 milyon row backfill saatler sürebilir. İkisini tek transaction veya tek deployment aşamasına koymak gereksiz risk yaratır. Dördüncü prensip: “Online DDL” performans etkisi yok demek değildir
Ama PostgreSQL bunun:
- daha fazla iş yaptığını,
- daha uzun sürdüğünü,
ek CPU/I/O yükü oluşturabildiğini belirtiyor. (PostgreSQL) MySQL online DDL de table-copy yöntemine göre concurrency avantajı sağlayabilir; buna rağmen metadata locking ve operasyon tipine bağlı rebuild maliyetleri devam edebilir. (MySQL Geliştirici Bölgesi) Dolayısıyla: Online ≠ ücretsiz. Beşinci prensip: Migration kodu da production kodudur Şu SQL:
bir defa çalışacak diye daha az önemli değildir. Tam tersine:
- production data’ya dokunur,
- geri dönüşü zor olabilir,
lock yaratabilir. Bu nedenle migration dosyası: Code Review + Staging Test + Monitoring gerektirir.
Sonuç
Zero-downtime database migration’ın özü özel bir SQL komutu değildir. Asıl prensip: Eski ve yeni application sürümlerinin transition döneminde aynı database schema ile birlikte çalışabilmesini sağlamak. Çoğu değişiklik için güvenli model: EXPAND ↓ DEPLOY ↓ DUAL WRITE ↓ BACKFILL ↓ VALIDATE ↓ CUTOVER ↓ OBSERVE ↓ CONTRACT şeklindedir. PostgreSQL tarafında:
- CREATE INDEX CONCURRENTLY,
- NOT VALID,
- VALIDATE CONSTRAINT,
metadata-only column additions gibi mekanizmalar production etkisini azaltabilir. (PostgreSQL) MySQL 8.4 tarafında:
- ALGORITHM=INSTANT,
- ALGORITHM=INPLACE,
gibi online DDL özellikleri birçok değişikliğin concurrent application trafiği sürerken yapılabilmesine yardımcı olabilir. (MySQL Geliştirici Bölgesi) Ancak nihai kural database’den bağımsızdır: Destructive schema değişikliğini, bütün eski uygulama sürümlerinin artık ona ihtiyaç duymadığını doğrulamadan yapmayın.
Kaynaklar
- PostgreSQL — Güncel ALTER TABLE dokümantasyonu; lock seviyeleri, NOT VALID, VALIDATE CONSTRAINT, table rewrite ve constraint davranışları. (PostgreSQL)
- PostgreSQL — CREATE INDEX CONCURRENTLY; write availability, performans maliyeti, invalid index ve transaction kısıtları. (PostgreSQL)
- PostgreSQL — Column type değişimlerinde ve volatile default’larda table/index rewrite davranışı. (PostgreSQL)
- MySQL 8.4 — ALTER TABLE; INSTANT, INPLACE, COPY ve LOCK seçenekleri. (MySQL Geliştirici Bölgesi)
- MySQL 8.4 — Online DDL operasyonları ve concurrent DML desteği. (MySQL Geliştirici Bölgesi)
- Prisma — Production data migration için expand-and-contract pattern. (Prisma)
- Prisma — Migration SQL özelleştirme ve kesintisiz schema evolution örnekleri. (Prisma)