Canlıda Sıfır Kesintiyle Şema Değiştirmek
Production'da kesinti yaratmadan migration almak: geri uyumlu adımlar, çok aşamalı kolon değişiklikleri ve tablo kilitlenmesini önleme.
Kısaca
Tehlikeli olan şema değişikliği değil, onu tek adımda yapmak. Okumaları da kuyruğa sokan yalnızca ACCESS EXCLUSIVE lock alan işlemlerdir ve 50 milyon satırlık bir tabloda o kuyruğun adı kesintidir. Değişikliği expand-contract adımlarına bölün: kısıtı NOT VALID ile ekleyip VALIDATE'i ayrı adımda çalıştırın, indeksleri CONCURRENTLY kurun ve migration oturumuna kısa bir lock_timeout verin.
Bir deploy, 50 milyon satırlık bir tabloda tek bir ALTER TABLE çalıştırdı ve tabloyu dört dakika kilitledi. Dört dakika boyunca o tabloya değen her istek bekledi; site fiilen çöktü. Migration’ın kendisi doğruydu — sorun, onun tek adımda yapılmasıydı.
Tehlikeli olan şema değişikliği değildir. Tehlikeli olan, şema değişikliğini tek seferde, geri uyumluluğu düşünmeden yapmaktır.
Lock’u kim alır?
Her şema değişikliği aynı maliyette değildir. Modern PostgreSQL’de sabit DEFAULT ile kolon eklemek bir metadata işlemidir — hızlıdır. Asıl tehlike, tabloyu uzun süre kilitleyen işlemlerdedir:
CREATE INDEX—CONCURRENTLYolmadan tabloyu yazmaya kapatır.- Tabloyu yeniden yazan
ALTER COLUMN ... TYPEdeğişiklikleri. - Tüm tabloyu tarayan
NOT NULL,CHECKveya foreign key eklemeleri.
Bu işlemler güçlü bir lock alır; ACCESS EXCLUSIVE alanlar — tablo yeniden yazımı ya da NOT NULL/CHECK eklemesi — tabloya değen her sorguyu, okuma dahil, kuyruğa sokar. Geri kalanı yalnızca yazmaları bloklar. Tablo büyükse kuyruk uzar.
Tehlikeli olan tek adımdır
Çözüm migration yapmamak değil; her tehlikeli migration’ı, her biri tek başına güvenli ve geri uyumlu olan küçük adımlara bölmektir. Bunun adı expand-contract (genişlet–daralt) kalıbıdır.
Bir kolonu yeniden adlandırmak istediğinizi düşünün. Tek adımda RENAME COLUMN, eski kolonu okuyan çalışan kodu anında kırar. Bunun yerine üç deploy:
- Genişlet. Yeni kolonu ekle. Kod hem eskiye hem yeniye yazsın, hâlâ eskiden okusun.
- Geçiş. Eski veriyi yeni kolona batch’ler hâlinde taşı. Kod artık yeniden okusun.
- Daralt. Eski kolonu sil.
Her adım, hem bir önceki sürümdeki kod hem de yeni kod ile çalışır. Hiçbir an, çalışan kod ile şema uyumsuz değildir.
Güvenli reçeteler
Sık yapılan değişikliklerin kesintisiz hâlleri:
Yeni NOT NULL kolon. Tek adımda NOT NULL eklemek tabloyu tarar. Bölün:
-- 1. Önce nullable ekle
ALTER TABLE orders ADD COLUMN status text;
-- 2. Mevcut satırları batch'ler hâlinde doldur (uygulama tarafında)
-- 3. Kısıtı önce doğrulamadan ekle, sonra ayrı adımda doğrula
ALTER TABLE orders ADD CONSTRAINT orders_status_not_null
CHECK (status IS NOT NULL) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT orders_status_not_null;
NOT VALID ile eklenen kısıt yeni satırlar için hemen geçerlidir ama mevcut satırları taramaz; VALIDATE ise tabloyu yalnızca SHARE UPDATE EXCLUSIVE lock ile tarar — yazmaları engellemez.
İndeks. Her zaman CONCURRENTLY:
CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);
Foreign key. Aynı iki adımlı kalıp: önce ADD CONSTRAINT ... NOT VALID, sonra VALIDATE CONSTRAINT.
lock_timeout. Bir migration, uzun süren bir sorgunun arkasında lock için beklerken kendisi de arkasındaki her şeyi bloklar. Bunu önlemek için migration oturumuna kısa bir lock_timeout verin — lock hemen alınamıyorsa migration beklemek yerine başarısız olsun, siz tekrar deneyin.
Kod ve şema birlikte uyumlu olmalı
Expand-contract’ın özü tek bir kuralda: her ara adımda, hem çalışmakta olan eski kod hem de yeni kod mevcut şema ile çalışabilmeli.
Veritabanı şeması, ileride lazım olur yazısında “tek yönlü kapı” dediğim şeydir — geri almak pahalıdır. Bu yüzden değişikliği büyük ve geri dönülmez tek bir adım olarak değil, her biri ayrı ayrı geri alınabilir küçük adımlar olarak tasarlayın.
Sıfır kesintili migration bir araç değil, bir disiplindir: her şema değişikliğini, çalışan kodun hiç fark etmeyeceği kadar küçük adımlara bölmek.
Tehlikeli olan değişikliğin kendisi değil, onu tek nefeste yapmaktır.
Adımlar
Expand–contract ile bir kolonu kesintisiz değiştirmek
Production'da kesinti yaratmadan migration almak: geri uyumlu adımlar, çok aşamalı kolon değişiklikleri ve tablo kilitlenmesini önleme.
-
Genişlet
Yeni kolonu ekle. Kod hem eskiye hem yeniye yazsın, okumayı hâlâ eskiden yapsın.
-
Geçiş
Eski veriyi yeni kolona batch’ler hâlinde taşı; taşıma bitince kod artık yeniden okusun.
-
Daralt
Eski kolonu sil. Her adım hem bir önceki sürümün kodu hem yeni kod ile çalışır; hiçbir an şema ile çalışan kod uyumsuz değildir.