---
title: "Canlıda Sıfır Kesintiyle Şema Değiştirmek"
description: "Production'da kesinti yaratmadan migration almak: geri uyumlu adımlar, çok aşamalı kolon değişiklikleri ve tablo kilitlenmesini önleme."
url: https://sade.dev/tr/notes/zero-downtime-veritabani-gocleri/
lang: tr
author: "Muhammet Şafak"
published: 2026-08-29
updated: 2026-09-06
section: Note
tags: ["postgresql","database","migrations","production"]
summary: "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

> 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` — `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:

```sql
-- 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`:

```sql
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](/tr/journal/ileride-lazim-olur-kodunun-faturasi) 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.

## Sık sorulanlar

**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

- [ALTER TABLE: NOT VALID constraints, VALIDATE CONSTRAINT and ADD COLUMN with a DEFAULT](https://www.postgresql.org/docs/current/sql-altertable.html) — PostgreSQL
- [Explicit Locking: table-level lock modes](https://www.postgresql.org/docs/current/explicit-locking.html) — PostgreSQL
- [CREATE INDEX: building indexes concurrently](https://www.postgresql.org/docs/current/sql-createindex.html) — PostgreSQL
- [Client Connection Defaults: lock_timeout](https://www.postgresql.org/docs/current/runtime-config-client.html) — PostgreSQL
