Claude Code ile PostgreSQL migrasyonlarında expand-contract desenini, backfill, rollback planları ve release öncesi staging'de doğrulanması gerekenleri öğrenin.

Bir PostgreSQL şema değişikliği gerçek trafik ve gerçek veriyle karşılaşana kadar basit görünür. Riskli olan kısmı genellikle SQL'in kendisi değildir. Asıl problem, uygulama kodu, veritabanı durumu ve dağıtım zamanlaması eşleşmeyi bıraktığında başlar.
Çoğu başarısızlık pratik ve acı vericidir: bir deploy eski kodun yeni bir kolona dokunması yüzünden bozulur, bir migration sıcak bir tabloyu kilitler ve timeout'lar patlar, ya da bir “hızlı” değişiklik sessizce veriyi siler veya yeniden yazar. Hiçbir şey çökmediğinde bile yanlış varsayılanlar, bozuk kısıtlar veya tamamlanmamış indeksler gibi ince hatalar gönderebilirsiniz.
AI tarafından üretilen migration'lar ayrıca ayrı bir risk katmanı getirir. Araçlar geçerli SQL üretebilir ama iş yükünüz, veri hacminiz veya yayın süreciniz için güvensiz olabilir. Tablo isimlerini tahmin edebilir, uzun süren kilitleri atlayabilir veya geri alma konusunda belirsiz davranabilirler çünkü down migration’lar zordur. Eğer migration’lar için Claude Code kullanıyorsanız, koruyucu kurallara ve somut bağlama ihtiyacınız var.
Bu yazı bir değişiklikten "güvenli" söz ederken üç şeyi kasteder:
Amaç, migration'ları rutin hale getirmek: öngörülebilir, test edilebilir ve sıkıcı.
Modeli odaklı tutmak ve sadece laptopunuzda çalışan bir değişikliği göndermemeniz için birkaç tartışılmaz kuralla başlayın.
İşi küçük adımlara bölün. Bir şema değişikliği, bir veri backfill'i, bir uygulama değişikliği ve bir temizlik adımı farklı risklerdir. Bunları birleştirmek neyin bozulduğunu görmeyi ve geri almayı zorlaştırır.
Yıkıcı olanlardan önce ekleyici değişiklikleri tercih edin. Bir sütun, indeks veya tablo eklemek genelde düşük risklidir. Yeniden adlandırma veya nesne silme kesintilerin olduğu yerdir. Önce güvenli kısmı yapın, uygulamayı taşıyın, ve eski şeyi yalnızca kullanılmadığından emin olduktan sonra kaldırın.
Uygulamanın bir süre iki biçimi de tolere etmesini sağlayın. Kod, rollout sırasında ya eski sütunu ya da yeni sütunu okuyabilmeli. Bu, bazı sunucular yeni kodu çalıştırırken veritabanının hâlâ eski olduğu (veya tersine) yaygın yarış durumunu önler.
Migration'ları hızlı bir betik değil, production kodu gibi ele alın. Koder.ai (Go backend ile PostgreSQL, ayrıca React veya Flutter istemciler) gibi bir platformla inşa ediyor olsanız bile, veritabanı her şey tarafından paylaşılır. Hatalar pahalıdır.
SQL isteğinin başına koymak için kompakt bir kural seti isterseniz şöyle bir şey kullanın:
Pratik bir örnek: uygulamanızın bağlı olduğu bir kolonu yeniden adlandırmak yerine yeni kolonu ekleyin, yavaşça backfill edin, yeni sonra eskiyi kaldırın.
Claude belirsiz bir isteğe göre makul SQL yazabilir, ama güvenli migration'lar bağlam ister. Prompt'unuzu küçük bir tasarım brifi gibi ele alın: ne olduğunu gösterin, hangi şeyin bozulmaması gerektiğini açıklayın ve rollout için "güvenli"nin ne anlama geldiğini tanımlayın.
Sadece önemli veritabanı gerçeklerini yapıştırarak başlayın. Tablo tanımını ve ilgili indeksleri ve kısıtları (primary key, unique, foreign key, check constraint, trigger) ekleyin. İlgili tablolar varsa, onların snippet'lerini de koyun. Küçük, doğru bir kesit modelin isimleri tahmin etmesini veya önemli bir kısıtı kaçırmasını önler.
Gerçek dünya ölçeğini ekleyin. Satır sayıları, tablo boyutu, yazma hızı ve zirve trafik planı değişikliği etkiler. "200M satır ve 1k yazma/sn" ile "20k satır ve çoğunlukla okuma" çok farklıdır. Ayrıca Postgres sürümünüzü ve migration'ların sisteminizde nasıl çalıştığını (tek transaction mı yoksa birden fazla adım mı) belirtin.
Uygulamanın veriyi nasıl kullandığını açıklayın: önemli okuma, yazma ve background job'lar. Örnekler: "API email ile okur", "workerlar durum günceller" veya "raporlar created_at'e göre tarar". Bu, expand/contract, feature flag ve backfill güvenliğinin nasıl olması gerektiğini belirler.
Son olarak, kısıtlar ve teslimatlar konusunda açık olun. Basit bir yapı iyi çalışır:
Hem SQL hem de run planı istemek modeli sıralama, risk ve kontrol edilecekler hakkında düşünmeye zorlar.
Expand/contract migration deseni PostgreSQL veritabanını değiştirirken uygulamanın kırılmamasını sağlar. Tek riskli anahtar yerine, veritabanının eski ve yeni biçimleri bir süre desteklemesini sağlarsınız.
Bunu şu şekilde düşünün: önce yeni şeyleri güvenle ekleyin (expand), trafiği ve veriyi kademeli taşıyın, ve sadece sonra eski parçaları kaldırın (contract). Bu, AI destekli çalışmalarda özellikle faydalıdır çünkü karmaşık süreç için plan yapmanızı zorlar.
Pratik bir akış şöyle görünür:
Bu deseni, kullanıcılar veritabanını değiştirirken hala eski uygulama sürümünde olabilirlerse her zaman kullanın. Çok örnekli dağıtımlar, yavaş güncellenen mobil uygulamalar veya migration'ın dakikalar/ saatler sürebileceği herhangi bir yayın için uygundur.
Yararlı bir taktik iki sürüm planlamaktır. Sürüm 1 expand + compatibility yapar, böylece backfill eksik olsa bile bir şey kırılmaz. Sürüm 2 yalnızca contract yapar, yeni kod ve verinin yerinde olduğundan emin olduktan sonra.
Bu şablonu kopyalayıp köşeli parantezleri doldurun. Claude Code'u çalıştırılabilir SQL, doğrulama ve gerçekçi bir rollback planı üretmeye zorlar.
You are helping me plan a PostgreSQL expand-contract migration.
Context
- App: [what the feature does, who uses it]
- Database: PostgreSQL [version if known]
- Table sizes: [rough row counts], write rate: [low/medium/high]
- Zero/near-zero downtime required: [yes/no]
Goal
- Change: [describe the schema change]
- Current schema (relevant parts):
[paste CREATE TABLE or \d output]
- How the app will change (expand phase and contract phase):
- Expand: [new columns/indexes/triggers, dual-write, read preference]
- Contract: [when/how we stop writing old fields and remove them]
Hard safety requirements
- Prefer lock-safe operations. Avoid full table rewrites on large tables when possible.
- If any step can block writes, call it out explicitly and suggest alternatives.
- Use small, reversible steps. No “big bang” changes.
Deliverables
1) UP migration SQL (expand)
- Use clear comments.
- If you propose indexes, tell me if they should be created CONCURRENTLY.
- If you propose constraints, tell me whether to add them NOT VALID then VALIDATE.
2) Verification queries
- Queries to confirm the new schema exists.
- Queries to confirm data is being written to both old and new structures (if dual-write).
- Queries to estimate whether the change caused bloat/slow queries/locks.
3) Rollback plan (realistic)
- DOWN migration SQL (only if it is truly safe).
- If down is not safe, write a rollback runbook:
- how to stop the app change
- how to switch reads back
- what data might be lost or need re-backfill
4) Runbook notes
- Exact order of operations (including app deploy steps).
- What to monitor during the run (errors, latency, deadlocks, lock waits).
- “Stop/continue” checkpoints.
Output format
- Separate sections titled: UP.sql, VERIFY.sql, DOWN.sql (or ROLLBACK.md), RUNBOOK.md
İki ek satır pratikte yardımcı olur:
RISK: blocks writes olarak etiketle ve ne zaman çalıştırılacağını söyle (off-peak vs anytime).Küçük şema değişiklikleri bile uzun kilitler, büyük tablo yeniden yazımları veya yarıda başarısız olma riskiyle zarar verebilir. Claude Code kullandığınızda, yeniden yazımlardan kaçınan ve uygulamanız çalışırken veritabanının yetişmesini sağlayan SQL isteyin.
Nullable bir sütun eklemek genelde güvenlidir. Non-null ve varsayılan bir değer eklemek bazı eski Postgres sürümlerinde tabloyu yeniden yazdırabileceği için risklidir.
Daha güvenli yaklaşım iki aşamalıdır: sütunu önce NULL ve varsayılan olmadan ekleyin, partiler halinde backfill yapın, sonra yeni satırlar için varsayılanı ayarlayın ve veri temizse NOT NULL yapın.
Hemen bir varsayılan gerekiyorsa, Postgres sürümünüz için kilit davranışı hakkında açıklama ve beklenenden uzun sürerse bir fallback planı isteyin.
Büyük tablolarda indeksler için CREATE INDEX CONCURRENTLY isteyin ki okuma ve yazma akmaya devam etsin. Ayrıca bunun bir transaction bloğu içinde çalıştırılamayacağını (non-transactional adım gerektiğini) belirtin.
Foreign key'ler için daha güvenli yol genellikle önce NOT VALID olarak eklemek, sonra doğrulamaktır. Bu başlangıçtaki değişikliği hızlı tutar ama yeni yazımları yine de kısıtlar.
Daha katı kısıtlar (NOT NULL, UNIQUE, CHECK) eklerken "önce temizle, sonra zorla" isteyin. Migration kötü satırları tespit etmeli, bunları düzeltmeli ve yalnızca sonra daha katı kuralı etkinleştirmelidir.
Kısa bir kontrol listesi gerektiğinde:
Backfill'ler çoğu migration acısını gösterir, ALTER TABLE değil. En güvenli prompt'lar backfill'leri kontrollü işler gibi ele alır: ölçülebilir, yeniden başlatılabilir ve production'a nazik.
Başlangıç olarak kabul kriterleri ekleyin: beklenen satır sayıları, hedef null oranı ve birkaç spot kontrol (örneğin 20 rastgele ID için eski ve yeni değerleri karşılaştırma).
Sonra bir batching planı isteyin. Partiler kilitleri kısa tutar ve sürprizleri azaltır. İyi bir istek şunu belirtir:
Idempotency isteyin çünkü backfill'ler yarıda başarısız olur. SQL tekrar çalıştırılabilir olmalı, çoğaltma veya veri bozulması yapmamalı. Tipik desenler UPDATE ... WHERE new_col IS NULL veya deterministik kurallar kullanmaktır.
Uygulamanın backfill çalışırken doğru kalmasını nasıl sağlayacağınızı da yazın. Yeni yazımlar devam ediyorsa köprü gerekir: uygulamada dual-write, geçici trigger veya read-fallback mantığı. Hangi yaklaşımı güvenle dağıtabileceğinizi söyleyin.
Ayrıca durdurma ve yeniden başlatma tasarlayın. İlerlemeyi izleyen küçük bir tablo (son işlenen ID) ve ilerlemeyi raporlayan bir sorgu (güncellenen satırlar, son ID, başlama zamanı) isteyin.
Örnek: users.full_name ekliyorsunuz ve bunu first_name + last_name'den türetiyorsunuz. Güvenli backfill yalnızca full_name IS NULL olanları günceller, ID aralıklarında çalışır, son işlenen ID'yi kaydeder ve yeni kayıtlar için dual-write tutar.
Rollback planı sadece "down migration yaz" demek değildir. İki problem vardır: şema değişikliğini geri almak ve yeni sürüm yayındayken değişen veriyi ele almak. Şema rollback genelde mümkündür. Veri rollback genelde değil, eğer önceden plan yapmadıysanız.
Rollback'un ne anlama geldiğini açıkça belirtin. Eğer bir sütunu siliyorsanız veya verileri yerinde yeniden yazıyorsanız, gerçekçi bir yanıt isteyin: "Rollback uygulama uyumluluğunu geri sağlar, ancak orijinal veri bir snapshot olmadan kurtarılamaz." Bu dürüstlük sizi güvende tutar.
Net rollback tetikleyicileri isteyin ki bir olay sırasında tartışma çıkmasın. Örnekler:
Tüm rollback paketini isteyin: down migration SQL (yalnızca güvenliyse), uygulama adımlarıyla uyumluluk için gerekli kod/konfigürasyon değişiklikleri ve arka plan işlerini durdurma talimatları.
Bu prompt kalıbı genelde yeterlidir:
Produce a rollback plan for this migration.
Include: down migration SQL, app config/code switches needed for compatibility, and the exact order of steps.
State what can be rolled back (schema) vs what cannot (data) and what evidence we need before deciding.
Include rollback triggers with thresholds.
Göndermeden önce hafif bir "güvenlik snapshot'u" alın ki karşılaştırma yapabilesiniz:
Ayrıca ne zaman geri dönmemeniz gerektiğini belirtin. Eğer yalnızca nullable bir sütun eklediyseniz ve uygulama dual-write yapıyorsa, ileri yönde bir düzeltme (hotfix kodu, backfill'i durdurup yeniden başlatma) genelde geri almaktan daha güvenlidir.
AI hızlı SQL yazabilir, ama production veritabanınızı göremez. Çoğu hata prompt belirsiz olduğunda ve model boşlukları doldurduğunda ortaya çıkar.
Yaygın tuzak, mevcut şemayı atlamaktır. Tablo tanımını, indeksleri ve kısıtları yapıştırmazsanız, SQL olmayan kolonlara hedeflenebilir veya uniqueness kuralını kaçırıp backfill'i kilit-ağır bir işe dönüştürebilir.
Başka bir hata expand, backfill ve contract'ı tek deploy'ta paketlemektir. Bu kaçış yolunuzu kaldırır. Backfill uzun sürerse veya yarıda hata verirse, uygulama final durumu bekliyorsa sıkışırsınız.
En sık görülen sorunlar:
Somut örnek: "bir kolonu yeniden adlandır ve uygulamayı güncelle." Eğer üretilen plan yeniden adlandırma ve backfill'i tek bir transaction'da yapıyorsa, yavaş bir backfill kilit tutar ve canlı trafiği bozar. Daha güvenli bir prompt küçük partiler, açık zaman aşımı ve kaldırmadan önce doğrulama sorguları zorunlu kılmalıdır.
Staging, küçük dev veritabanında hiç ortaya çıkmayan sorunları bulduğunuz yerdir: uzun kilitler, sürpriz null'lar, eksik indeksler ve unutulan kod yolları.
Önce migration sonrası şemanın planla eşleştiğini kontrol edin: sütunlar, tipler, varsayılanlar, kısıtlar ve indeksler. Hızlı bir göz atma yeterli değildir. Bir eksik indeks güvenli backfill'i yavaş bir felakete çevirebilir.
Ardından migration'ı gerçekçi bir veri kümesine karşı çalıştırın. İdeali production'un yakın tarihli bir kopyasıdır (gizli alanlar maskelerek). Bunu yapamıyorsanız, en azından production hacmini ve hotspot'ları (büyük tablolar, geniş satırlar, yoğun indeksli tablolar) eşleştirin. Her adım için zaman kaydı alın ki production'da ne bekleyeceğinizi bilin.
Kısa bir staging kontrol listesi:
Son olarak, sadece SQL değil gerçek kullanıcı akışlarını test edin. Oluşturma, güncelleme ve okuma işlemlerini çalıştırın. Eğer expand/contract planı varsa, her iki şemanın final temizliğe kadar çalıştığını doğrulayın.
Diyelim users.name sütununda "Ada Lovelace" gibi tam isimler saklıyorsunuz. first_name ve last_name istiyorsunuz ama signup, profil veya admin ekranlarını değişiklik sırasında bozmak istemiyorsunuz.
Önce hiçbir kod değişikliği olmasa bile güvenli olacak bir expand adımıyla başlayın: nullable sütunları ekleyin, eski sütunu tutun ve uzun kilitlerden kaçının.
ALTER TABLE users ADD COLUMN first_name text;
ALTER TABLE users ADD COLUMN last_name text;
Sonra uygulama davranışını her iki şemayı da destekleyecek şekilde güncelleyin. Sürüm 1'de uygulama yeni sütunları varsa onlardan okur, yoksa name'e düşer ve yazma sırasında her iki yere de yazar ki yeni veriler tutarlı kalsın.
Ardından backfill gelir. Her çalıştırmada küçük bir parça güncelleyen, ilerlemeyi kaydeden ve güvenle durdurulup yeniden başlatılabilen bir batch işi çalıştırın. Örneğin: first_name null olanları ID sırasına göre 1.000'erlik bloklarla güncelleyin ve kaç satır değiştiğini loglayın.
Kuralları sıkılaştırmadan önce staging'de doğrulayın:
first_name ve last_name dolduruyor ve yine name de set ediliyorname var olsa bile doğru görüntüleniyorusers üzerindeki temel sorgular belirgin şekilde yavaşlamadıSürüm 2 okumaları yalnızca yeni sütunlara çevirir. Ancak bunun ardından kısıtları (ör. SET NOT NULL) eklemeli ve name'i kaldırmalısınız; bunlar ayrı bir deploy olmalı.
Rollback için sıkıcı kalın. Geçiş sırasında uygulama name okumaya devam etsin ve backfill durdurulabilir olsun. Sürüm 2'yi geri almak gerekirse, okumaları tekrar name'e çevirin ve yeni sütunları olduğu gibi bırakın.
Her değişikliği küçük bir runbook gibi ele alın. Amaç mükemmel prompt değil; doğru detayları zorlayan bir rutin: şema, kısıtlar, çalışma planı ve rollback.
Her migration isteğinin standart olarak içermesini zorunlu kılın:
Her adımın sahibini dağıtın ki "herkes başkası yaptı sanıyordu" durumu olmasın: geliştiriciler prompt ve migration kodundan sorumlu olsun, ops production zamanlaması ve izlemeden sorumlu olsun, QA staging davranışı ve kenar durumları doğrulasın, ve bir kişi son go/no-go kararı versin.
Chat üzerinden uygulama inşa ediyorsanız, herhangi bir SQL üretmeden önce diziyi planlamak yardımcı olur. Koder.ai kullanan ekipler için Planning Mode bu sırayı yazmak için doğal bir yer olabilir ve snapshotlar ile rollback blast radius'u azaltabilir.
Gönderdikten sonra, bağlam tazeyken contract cleanup'i hemen planlayın ki eski sütunlar ve geçici uyumluluk kodu aylarca kalmasın.
A schema change is risky when app code, database state, and deployment timing stop matching.
Common failure modes:
Use an expand/contract approach:
This keeps both old and new app versions working during rollout.
Because the model can generate SQL that is valid but unsafe for your workload.
Typical AI-specific risks:
Treat AI output as a draft and require a run plan, checks, and rollback steps.
Include only the facts the migration depends on:
CREATE TABLE snippets (plus indexes, FKs, UNIQUE/CHECK constraints, triggers)Default rule: separate them.
A practical split:
Bundling everything makes failures harder to diagnose and roll back.
Prefer this pattern:
ADD COLUMN ... NULL with no default (fast)NOT NULL only after verificationAdding a non-null default can be risky on some versions because it may rewrite the whole table. If you need an immediate default, ask for lock/runtime notes and a safer fallback.
Ask for:
CREATE INDEX CONCURRENTLY for large/hot tablesFor verification, include a quick check that the index exists and is used (for example, compare an EXPLAIN plan before/after in staging).
Use NOT VALID first, then validate later:
NOT VALID so the initial step is less disruptiveVALIDATE CONSTRAINT in a separate step when you can watch itThis still enforces the FK for new writes, while letting you control when the expensive validation happens.
A good backfill is batched, idempotent, and restartable.
Practical requirements:
WHERE new_col IS NULL)Default rollback goal: restore app compatibility fast, even if data isn’t perfectly undone.
A workable rollback plan should include:
Often the safest rollback is switching reads back to the old field while leaving new columns in place.
This prevents guessing and forces the right ordering.
This makes backfills survivable under real traffic.