İçeriğe geç
Muhammet Şafak
en
Güncelleme 2 dk okuma

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.

Canlıda Sıfır Kesintiyle Şema Değiştirmek — kapak görseli

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 — CONCURRENTLY olmadan tabloyu yazmaya kapatır.
  • Tabloyu yeniden yazan ALTER COLUMN ... TYPE değişiklikleri.
  • Tüm tabloyu tarayan NOT NULL, CHECK veya 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:

  1. Genişlet. Yeni kolonu ekle. Kod hem eskiye hem yeniye yazsın, hâlâ eskiden okusun.
  2. Geçiş. Eski veriyi yeni kolona batch’ler hâlinde taşı. Kod artık yeniden okusun.
  3. 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.

  1. Genişlet

    Yeni kolonu ekle. Kod hem eskiye hem yeniye yazsın, okumayı hâlâ eskiden yapsın.

  2. Geçiş

    Eski veriyi yeni kolona batch’ler hâlinde taşı; taşıma bitince kod artık yeniden okusun.

  3. 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.

Sık sorulanlar

2 soru

  • Hangi şema değişiklikleri yazmayı değil, okumayı da bloklar?

    ACCESS EXCLUSIVE lock alanlar: tablo yeniden yazımı ya da NOT NULL veya CHECK eklemesi. Düz bir SELECT'i yalnızca bu lock modu engeller. CONCURRENTLY olmadan çalışan CREATE INDEX daha zayıf bir lock alır; tabloyu yazmaya kapatır ama okumaya açık bırakır.

  • Kısıtı neden önce NOT VALID ile eklemek, sonra ayrı adımda doğrulamak gerekir?

    NOT VALID ile eklenen kısıt tabloyu taramadan yeni satırlar için hemen geçerli olur, yani anında commit eder. Ayrı adımdaki VALIDATE CONSTRAINT ise tabloyu yalnızca SHARE UPDATE EXCLUSIVE lock ile tarar ve yazmaları engellemez.

Kaynaklar

İlgili

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi