Pelajari prompt Claude Code untuk migrasi PostgreSQL yang aman: pola expand–contract, backfill, dan rencana rollback, serta apa yang harus diverifikasi di staging sebelum rilis.

Perubahan skema PostgreSQL tampak sederhana sampai berhadapan dengan trafik nyata dan data nyata. Bagian yang berisiko biasanya bukan SQL itu sendiri. Yang berbahaya adalah ketika kode aplikasi, keadaan basis data, dan waktu deployment tidak lagi sinkron.
Sebagian besar kegagalan bersifat praktis dan menyakitkan: sebuah deploy rusak karena kode lama mengakses kolom baru, sebuah migrasi mengunci tabel panas dan timeout melonjak, atau perubahan “cepat” diam-diam menghapus atau menulis ulang data. Bahkan ketika tidak ada yang crash, Anda bisa mengirim bug halus seperti default yang salah, konstraint yang rusak, atau indeks yang tidak selesai dibangun.
Migrasi yang dihasilkan AI menambah lapisan risiko lain. Alat dapat menghasilkan SQL yang valid tetapi tidak aman untuk beban kerja Anda, volume data Anda, atau proses rilis Anda. Mereka juga bisa menebak nama tabel, melewatkan lock yang berjalan lama, atau meremehkan rollback karena down migration sulit. Jika Anda menggunakan Claude Code untuk migrasi, Anda membutuhkan pembatas dan konteks konkret.
Ketika posting ini mengatakan suatu perubahan “aman”, itu berarti tiga hal:
Tujuannya agar migrasi menjadi pekerjaan rutin: dapat diprediksi, dapat dites, dan membosankan.
Mulailah dengan beberapa aturan yang tidak boleh dilanggar. Mereka menjaga model tetap fokus dan mencegah Anda mengirim perubahan yang hanya bekerja di laptop Anda.
Pisahkan pekerjaan menjadi langkah kecil. Perubahan skema, backfill data, perubahan aplikasi, dan langkah pembersihan adalah risiko berbeda. Menggabungkannya membuatnya lebih sulit melihat apa yang rusak dan lebih sulit melakukan rollback.
Utamakan perubahan aditif sebelum yang destruktif. Menambahkan kolom, indeks, atau tabel biasanya berisiko rendah. Mengganti nama atau menghapus objek adalah tempat terjadinya outage. Lakukan bagian yang aman dulu, pindahkan aplikasi, lalu hapus yang lama hanya ketika Anda yakin tidak digunakan.
Buat aplikasi toleran terhadap kedua bentuk untuk sementara. Kode sebaiknya bisa membaca kolom lama atau baru selama rollout. Ini menghindari perlombaan di mana beberapa server menjalankan kode baru sementara basis data masih lama (atau sebaliknya).
Perlakukan migrasi seperti kode produksi, bukan skrip cepat. Bahkan jika Anda membangun dengan platform seperti Koder.ai (backend Go dengan PostgreSQL, plus klien React atau Flutter), basis data dibagi oleh semuanya. Kesalahan mahal.
Jika Anda ingin satu set aturan ringkas untuk diletakkan di atas setiap permintaan SQL, gunakan hal seperti:
Contoh praktis: daripada mengganti nama kolom yang menjadi dependensi aplikasi Anda, tambahkan kolom baru, backfill pelan-pelan, deploy kode yang membaca baru lalu lama, dan baru nanti hapus kolom lama.
Claude bisa menulis SQL yang cukup baik dari permintaan yang samar, tapi migrasi aman membutuhkan konteks. Perlakukan prompt Anda seperti brief desain kecil: tunjukkan apa yang ada, jelaskan apa yang tidak boleh rusak, dan definisikan apa arti “aman” untuk rollout Anda.
Mulai dengan menempelkan hanya fakta basis data yang penting. Sertakan definisi tabel plus indeks dan konstraint relevan (primary key, unique constraint, foreign key, check, trigger). Jika tabel terkait dilibatkan, sertakan cuplikan itu juga. Cuplikan kecil dan akurat mencegah model menebak nama atau melewatkan konstraint penting.
Tambahkan skala dunia nyata. Jumlah baris, ukuran tabel, laju tulis, dan puncak trafik harus mengubah rencana. “200M rows dan 1k writes/sec” berbeda dari “20k rows dan sebagian besar read.” Sertakan juga versi Postgres Anda dan bagaimana migrasi dijalankan dalam sistem Anda (transaksi tunggal vs beberapa langkah).
Jelaskan bagaimana aplikasi menggunakan data: bacaan penting, penulisan, dan job latar. Contoh: “API membaca berdasarkan email”, “worker mengupdate status”, atau “laporan memindai berdasarkan created_at.” Ini menentukan apakah Anda perlu expand/contract, feature flags, dan seberapa aman sebuah backfill.
Terakhir, eksplisitkan konstraint dan deliverable. Struktur sederhana bekerja baik:
Meminta SQL sekaligus rencana run memaksa model memikirkan urutan, risiko, dan apa yang harus diperiksa sebelum Anda kirim.
Pola migrasi expand/contract mengubah database PostgreSQL tanpa merusak aplikasi saat perubahan berlangsung. Alih-alih satu switch berisiko, Anda membuat database mendukung bentuk lama dan baru untuk jangka waktu tertentu.
Bayangkan: tambahkan hal baru dengan aman (expand), alihkan trafik dan data secara bertahap, lalu baru hapus bagian lama (contract). Ini sangat berguna untuk pekerjaan berbantuan AI karena memaksa Anda merencanakan tahap tengah yang kompleks.
Alur praktis terlihat seperti ini:
Gunakan pola ini setiap kali pengguna mungkin masih menggunakan kode aplikasi lama saat database berubah. Itu termasuk deployment multi-instance, aplikasi mobile yang update lambat, atau rilis yang migrasinya bisa memakan menit atau jam.
Taktik yang membantu adalah merencanakan dua rilis. Rilis 1 melakukan expand plus compatibility sehingga tidak ada yang rusak jika backfill belum selesai. Rilis 2 melakukan contract hanya setelah Anda mengonfirmasi kode baru dan data baru sudah ada.
Salin template ini dan isi bagian di dalam kurung. Ini memaksa Claude Code menghasilkan SQL yang dapat Anda jalankan, cek untuk membuktikan berhasil, dan rencana rollback yang bisa Anda ikuti.
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
Dua baris ekstra yang membantu dalam praktik:
RISK: blocks writes, plus kapan menjalankannya (off-peak vs anytime).Perubahan skema kecil masih bisa menyakiti jika mengambil lock panjang, menulis ulang tabel besar, atau gagal di tengah. Saat menggunakan Claude Code untuk migrasi, minta SQL yang menghindari rewrite dan menjaga aplikasi bekerja sementara database mengejar.
Menambahkan kolom nullable biasanya aman. Menambahkan kolom dengan default non-null bisa berisiko pada versi Postgres lama karena bisa me-rewrite seluruh tabel.
Pendekatan yang lebih aman adalah perubahan dua langkah: tambahkan kolom sebagai NULL tanpa default, backfill dalam batch, lalu set default untuk baris baru dan tambahkan NOT NULL setelah data bersih.
Jika Anda harus menegakkan default segera, minta penjelasan tentang perilaku lock untuk versi Postgres Anda dan rencana fallback jika waktu eksekusi lebih lama dari perkiraan.
Untuk indeks pada tabel besar, minta CREATE INDEX CONCURRENTLY agar baca dan tulis tetap berjalan. Juga minta catatan bahwa itu tidak bisa dijalankan di dalam blok transaksi, yang berarti tool migrasi Anda perlu langkah non-transaksional.
Untuk foreign key, jalan yang lebih aman biasanya menambahkan konstraint sebagai NOT VALID dulu, lalu memvalidasi kemudian. Ini membuat perubahan awal lebih cepat sambil tetap menegakkan FK untuk penulisan baru.
Saat memperketat konstraint (NOT NULL, UNIQUE, CHECK), minta "bersihkan dulu, terapkan nanti." Migrasi harus mendeteksi baris buruk, memperbaikinya, lalu baru mengaktifkan aturan yang lebih ketat.
Jika Anda ingin checklist singkat untuk ditempel di prompt, buat singkat:
Backfill adalah tempat kebanyakan rasa sakit migrasi muncul, bukan ALTER TABLE. Prompt paling aman memperlakukan backfill seperti job terkontrol: dapat diukur, dapat dilanjutkan, dan ramah produksi.
Mulai dengan cek penerimaan yang mudah dijalankan dan sulit diperdebatkan: jumlah baris yang diharapkan, target tingkat null, dan beberapa spot check (mis. bandingkan nilai lama vs baru untuk 20 ID acak).
Lalu minta rencana batching. Batch menjaga lock singkat dan mengurangi kejutan. Permintaan yang bagus menentukan:
Wajibkan idempotensi karena backfill bisa gagal di tengah. SQL harus aman dijalankan ulang tanpa menggandakan atau merusak data. Pola umum: "update hanya dimana kolom baru IS NULL" atau aturan deterministik di mana input yang sama selalu menghasilkan output yang sama.
Juga jelaskan bagaimana aplikasi tetap benar saat backfill berjalan. Jika penulisan baru terus masuk, Anda butuh jembatan: dual-write di kode aplikasi, trigger sementara, atau logika read-fallback (baca baru jika ada, kalau tidak baca lama). Sebutkan pendekatan mana yang bisa Anda deploy dengan aman.
Terakhir, bangun kemampuan pause dan resume ke dalam desain. Minta pelacakan progres dan checkpoint, seperti tabel kecil yang menyimpan ID terakhir yang diproses dan query yang melaporkan progres (baris diupdate, ID terakhir, waktu mulai).
Contoh: Anda menambahkan users.full_name yang diturunkan dari first_name dan last_name. Backfill aman hanya mengubah baris di mana full_name IS NULL, berjalan dalam rentang ID, menyimpan ID terakhir yang diupdate, dan menjaga signup baru tetap benar lewat dual-write sampai perpindahan selesai.
Rencana rollback bukan hanya "tulis down migration." Ini dua masalah: membatalkan perubahan skema dan menangani data apa pun yang berubah saat versi baru hidup. Rollback skema sering mungkin. Rollback data sering tidak mungkin kecuali Anda merencanakannya.
Jadilah eksplisit tentang apa arti rollback untuk perubahan Anda. Jika Anda menghapus kolom atau menulis ulang nilai di tempat, minta jawaban realistis seperti: "Rollback mengembalikan kompatibilitas aplikasi, tetapi data asli tidak bisa dipulihkan tanpa snapshot." Kejujuran semacam itu yang menjaga Anda aman.
Minta trigger rollback yang jelas sehingga tidak ada perselisihan saat insiden. Contoh:
Wajibkan paket rollback lengkap, bukan hanya SQL: down migration SQL (hanya jika aman), langkah aplikasi untuk tetap kompatibel, dan cara menghentikan job latar.
Polanya biasanya cukup:
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.
Sebelum mengirim, ambil "safety snapshot" ringan agar Anda dapat membandingkan sebelum dan sesudah:
Juga jelaskan kapan tidak perlu rollback. Jika Anda hanya menambahkan kolom nullable dan aplikasi dual-writing, perbaikan maju (hotfix kode, jeda backfill, lalu lanjut) sering lebih aman daripada revert dan menimbulkan lebih banyak drift.
AI bisa menulis SQL dengan cepat, tapi tidak melihat basis data produksi Anda. Sebagian besar kegagalan terjadi saat prompt samar dan model mengisi celah.
Perangkap umum adalah melewatkan skema saat ini. Jika Anda tidak menempelkan definisi tabel, indeks, dan konstraint, SQL mungkin menargetkan kolom yang tidak ada atau melewatkan aturan unik yang membuat backfill menjadi operasi lambat dan berat lock.
Kesalahan lain adalah mengirim expand, backfill, dan contract dalam satu deploy. Itu menghapus jalur keluar Anda. Jika backfill berjalan lama atau error di tengah, Anda terjebak dengan aplikasi yang mengharapkan keadaan akhir.
Masalah yang sering muncul:
Contoh konkret: "mengganti nama kolom dan mengupdate aplikasi." Jika rencana yang dihasilkan mengganti nama dan backfill dalam satu transaksi, backfill lambat dapat menahan lock dan merusak lalu lintas produksi. Prompt yang lebih aman memaksa batch kecil, timeout eksplisit, dan query verifikasi sebelum menghapus jalur lama.
Staging adalah tempat Anda menemukan masalah yang tak pernah muncul di basis data dev kecil: lock panjang, null tak terduga, indeks yang hilang, dan jalur kode terlupakan.
Pertama, periksa bahwa skema sesuai rencana setelah migrasi: kolom, tipe, default, konstraint, dan indeks. Sekilas tidak cukup. Satu indeks yang hilang bisa mengubah backfill aman menjadi bencana.
Lalu jalankan migrasi terhadap dataset realistis. Idealnya itu salinan produksi terbaru dengan field sensitif dimasking. Jika tidak bisa, setidaknya cocokkan volume produksi dan hotspot (tabel besar, row lebar, tabel dengan banyak indeks). Catat waktu tiap langkah supaya tahu ekspektasi di produksi.
Checklist singkat untuk staging:
Akhirnya, uji alur pengguna nyata, bukan hanya SQL. Buat, update, dan baca record yang disentuh oleh perubahan. Jika expand/contract direncanakan, pastikan kedua skema bekerja sampai pembersihan akhir.
Bayangkan Anda punya kolom users.name yang menyimpan nama lengkap seperti "Ada Lovelace." Anda ingin first_name dan last_name, tapi tidak boleh merusak signup, profil, atau layar admin saat perubahan berjalan.
Mulai dengan langkah expand yang aman meski tidak ada perubahan kode yang dideploy: tambahkan kolom nullable, pertahankan kolom lama, dan hindari lock panjang.
ALTER TABLE users ADD COLUMN first_name text;
ALTER TABLE users ADD COLUMN last_name text;
Lalu update perilaku aplikasi agar mendukung kedua skema. Di Rilis 1, aplikasi harus membaca dari kolom baru bila ada, fallback ke name bila null, dan menulis ke kedua tempat sehingga data baru tetap konsisten.
Selanjutnya backfill. Jalankan job batch yang mengupdate potongan kecil baris per run, mencatat progres, dan bisa dijeda dengan aman. Misalnya: update users dimana first_name null dalam urutan ID naik, 1.000 per batch, dan log berapa baris berubah.
Sebelum memperketat aturan, validasi di staging:
first_name dan last_name dan tetap mengisi namename yang adausers tidak terasa lebih lambatRilis 2 memindahkan baca hanya ke kolom baru. Hanya setelah itu Anda menambahkan konstraint (mis. SET NOT NULL) dan menghapus name, idealnya di deploy terpisah nanti.
Untuk rollback, buat sederhana. Aplikasi tetap membaca name selama transisi, dan backfill dapat dihentikan. Jika harus rollback Rilis 2, alihkan baca kembali ke name dan biarkan kolom baru tetap ada sampai stabil.
Perlakukan setiap perubahan seperti runbook kecil. Tujuannya bukan prompt sempurna. Tujuannya rutinitas yang memaksa detail benar: skema, konstraint, rencana run, dan rollback.
Standarkan apa yang harus disertakan setiap permintaan migrasi:
Putuskan siapa yang memegang tiap langkah sebelum menjalankan SQL. Pembagian sederhana mencegah "semua mengira orang lain melakukannya": developer memegang prompt dan kode migrasi, ops memegang penjadwalan produksi dan monitoring, QA memverifikasi perilaku staging dan edge case, dan satu orang memutus go/no-go terakhir.
Jika Anda membangun aplikasi lewat chat, membantu untuk menguraikan urutan sebelum menghasilkan SQL. Untuk tim yang memakai Koder.ai, Planning Mode adalah tempat yang wajar untuk menulis urutan itu, dan snapshot plus rollback dapat mengurangi dampak bila ada yang tak terduga saat rollout.
Setelah dikirim, jadwalkan pembersihan contract segera selagi konteks masih segar, supaya kolom lama dan kode kompatibilitas sementara tidak menggantung berbulan-bulan.
Perubahan skema berisiko ketika kode aplikasi, keadaan basis data, dan jadwal deployment tidak lagi sinkron.
Mode kegagalan umum:
Gunakan pendekatan expand/contract:
Model dapat menghasilkan SQL yang valid tetapi tidak aman untuk beban kerja Anda.
Risiko khusus AI:
Anggap keluaran AI sebagai draf: minta rencana run, pemeriksaan, dan langkah rollback.
Cantumkan hanya fakta yang bergantung pada migrasi:
CREATE TABLE relevan (plus indeks, FK, UNIQUE/CHECK, trigger)Aturan default: pisahkan.
Pembagian praktis:
Menggabungkan semuanya membuat kegagalan lebih sulit didiagnosis dan di-rollback.
Preferensi pola:
ADD COLUMN ... NULL tanpa default (cepat)NOT NULL hanya setelah verifikasiMenambahkan default non-null langsung bisa berisiko pada beberapa versi karena mungkin me-rewrite seluruh tabel. Jika perlu default segera, minta catatan tentang perilaku lock dan fallback yang lebih aman.
Minta:
CREATE INDEX CONCURRENTLY untuk tabel besar/sibukUntuk verifikasi, sertakan cek cepat bahwa indeks ada dan dipakai (mis. bandingkan EXPLAIN sebelum/sesudah di staging).
Gunakan NOT VALID dulu, lalu validasi kemudian:
NOT VALID agar langkah awal kurang menggangguVALIDATE CONSTRAINT di langkah terpisah saat Anda bisa memantauIni tetap menegakkan FK untuk penulisan baru sambil memberi kontrol kapan validasi mahal dilakukan.
Backfill yang baik itu bertahap, idempoten, dan bisa dilanjutkan.
Persyaratan praktis:
WHERE new_col IS NULL)Tujuan rollback default: pulihkan kompatibilitas aplikasi dengan cepat, meskipun data tidak sepenuhnya kembali.
Rencana rollback yang dapat dipakai harus mencakup:
Seringkali rollback paling aman adalah mengalihkan baca kembali ke field lama sambil meninggalkan kolom baru.
Ini menjaga agar versi aplikasi lama dan baru tetap bekerja selama rollout.
Ini mencegah tebakan dan memaksa urutan yang benar.
Ini membuat backfill tahan terhadap lalu lintas nyata.