2026-09-04veri4 dk

Veritabanı şeması değişikliğini sıfır kesintiyle yapmak

ALTER TABLE'ın aldığı kilit, milisaniyelik bir komutu kırk saniyelik kesintiye çevirebiliyor. Genişlet-daralt yöntemiyle şemayı bakım penceresi açmadan değiştirmenin adımları.

Şema değişikliği üretimi durduruyorsa sorun genellikle sorgunun süresi değil, aldığı kilit. ALTER TABLE çoğu biçiminde ACCESS EXCLUSIVE kilidi ister. Bu kilit, tabloyu okuyan en basit SELECT ile bile çakışır ve kilit kuyruğu FIFO çalışır: önünde uzun süren bir sorgu varsa ALTER bekler, ALTER beklerken arkasına dizilen bütün sorgular da bekler. Milisaniyelik olması gereken komut, uygulamayı kırk saniye durdurur.

İlk savunma migration'ı zaman aşımına bağlamaktır:

SET lock_timeout = '3s';
SET statement_timeout = '60s';
ALTER TABLE siparis ADD COLUMN kanal text;

Kilit üç saniyede alınamazsa komut hata verir, kuyruk büyümez. Migration çalıştırıcınız bu hatayı yakalayıp birkaç kez yeniden denemeli. Denenmeden geçilen adım, gece yarısı bakım penceresi demektir.

Genişlet ve daralt

Kesintisiz şema değişikliğinin tamamı tek bir fikirden çıkar: şema ile kod aynı anda değişemez. Yeni sürüm yayına alınırken eski sürüm hâlâ istek karşılar, dolayısıyla şemanın bir süre her iki sürümle de uyumlu olması gerekir.

Bu yüzden değişiklik beş ayrı yayına bölünür:

  1. Yeni yapıyı ekle (eskisine dokunmadan)
  2. Her iki yere de yaz
  3. Eski kayıtları parça parça geri doldur
  4. Okumayı yeni yapıya çevir
  5. Eski yapıyı sil

Adımlar arasında en az bir yayın döngüsü bekleyin. Aceleyle birleştirilen adımlar, geri alınamayan adımlardır.

Kolon eklemek

PostgreSQL 11'den itibaren sabit bir DEFAULT ile kolon eklemek tabloyu yeniden yazmaz; değer katalogda tutulur. Ama varsayılan uçucuysa (now(), random(), gen_random_uuid()) bütün tablo yeniden yazılır ve kilit süresi tablo boyutuyla büyür.

NOT NULL kısıtını doğrudan koymak da tam tarama yaptırır. Sıra şöyle:

ALTER TABLE siparis ADD COLUMN kanal text;

ALTER TABLE siparis
  ADD CONSTRAINT siparis_kanal_nn CHECK (kanal IS NOT NULL) NOT VALID;

-- geri doldurma buraya

ALTER TABLE siparis VALIDATE CONSTRAINT siparis_kanal_nn;
ALTER TABLE siparis ALTER COLUMN kanal SET NOT NULL;
ALTER TABLE siparis DROP CONSTRAINT siparis_kanal_nn;

NOT VALID kısıt yalnızca yeni satırları denetler ve anlık alınır. VALIDATE CONSTRAINT tabloyu tarar ama SHARE UPDATE EXCLUSIVE kilidiyle çalışır, yani yazmayı engellemez. PostgreSQL 12 ve sonrasında doğrulanmış bir CHECK varken SET NOT NULL ikinci bir tam tarama yapmaz.

Kolon adı değiştirmek diye bir şey yok

ALTER TABLE ... RENAME COLUMN anlık bir işlem, ama sorun kilit değil kod. Yeniden adlandırdığınız anda eski sürüm eski adı sorgular ve hata alır. Çevrimiçi ortamda ad değiştirme yoktur; yeni kolon açar, iki yere yazar, okumayı çevirir, eskisini silersiniz. Aynısı tip değişikliği için de geçerli: int sütunu bigint yapmak tabloyu yeniden yazar, bunun yerine yeni sütun açmak daha güvenlidir.

Geri doldurmayı tek UPDATE ile yapmayın

Milyonlarca satırı tek işlemde güncellemek uzun süren bir yazma işlemi, şişen WAL ve autovacuum'un tutunamadığı bir tablo demektir. Anahtar sırasına göre parçalayın:

UPDATE siparis
SET kanal = 'web'
WHERE id > $1 AND id <= $1 + 5000
  AND kanal IS NULL;

Döngüyü uygulama tarafında kurun, her turdan sonra kısa bir bekleme koyun ve son işlenen anahtarı bir yere yazın ki yarıda kalan iş baştan başlamasın. OFFSET kullanmayın; her turda tabloyu baştan tarar.

Her UPDATE satırın yeni bir sürümünü ürettiği için tablo şişer. Uzun süren geri doldurmalarda autovacuum'a nefes aldırmak, işi hızlandırmaktan daha önemli.

İndeks ve yabancı anahtar

CREATE INDEX CONCURRENTLY idx_siparis_kanal ON siparis (kanal);

CONCURRENTLY yazmayı engellemez, karşılığında tabloyu iki kez tarar ve işlem bloğu içinde çalıştırılamaz — migration aracınız her adımı tek transaction'a sarıyorsa bu adımı dışarı almanız gerekir. Yarıda kesilirse geride geçersiz bir indeks bırakır; sessizce durur, sorgular indeksi kullanmaz:

SELECT indexrelid::regclass FROM pg_index WHERE NOT indisvalid;

Geçersiz indeksi DROP INDEX CONCURRENTLY ile atıp yeniden kurun. Yabancı anahtar da aynı mantıkla iki adımda eklenir: önce NOT VALID, sonra VALIDATE CONSTRAINT.

Silme adımı unutulan adımdır

Çift yazmayı açtıktan sonra kimse geri dönüp kapatmıyor. Aylar sonra kodda iki kolonu birden dolduran, hangisinin doğru olduğunu kimsenin bilmediği yerler kalıyor. Beşinci adımı ilk gün kayda geçirin; migration'ı yazarken temizlik işini de açın.

En riskli adım da bu sonuncusu. Eski kolonu silmek geri alınamaz, bu yüzden onu bir yayın geciktirmek ucuz bir sigortadır. Yedekten dönmek bir geri alma planı sayılmaz.

MySQL tarafı

MySQL'in yerleşik online DDL desteği birçok değişikliği kilitsiz yapar, ama hangi ALTER'ın gerçekten çevrimiçi olduğu sürüme ve işlemin türüne göre değişir; belgedeki tablodan kontrol etmeden varsaymayın. Desteklenmeyen durumlar için gh-ost ve pt-online-schema-change gölge tablo kurup değişikliği oraya uygular, sonra tabloları takas eder. Yaklaşım aynı: değişikliği küçük parçalara bölmek ve her an geri dönebilecek durumda olmak.

Kısa kontrol listesi

Migration'ı yayına almadan önce: lock_timeout ayarlı mı, adım tek başına geri alınabilir mi, geri doldurma parçalı ve devam ettirilebilir mi, indeks CONCURRENTLY mi, eski yapıyı silmek için açılmış bir kayıt var mı. Beşi de evetse bakım penceresine ihtiyacınız yok.