Perintah DDL dan Dampak Penguncian
| Operasi | Aman? | Catatan |
|---|---|---|
ADD COLUMN tanpa default | ✅ Instan | Hanya mengubah katalog |
ADD COLUMN ... DEFAULT | ✅ Instan | Sejak PostgreSQL 11 |
DROP COLUMN | ✅ Instan | Ruang kembali setelah VACUUM |
ALTER TYPE (perbesar varchar) | ✅ Instan | Hanya bila memperbesar |
ALTER TYPE (ubah tipe) | ❌ Menulis ulang | Mengunci tabel penuh |
SET NOT NULL | ⚠️ Memindai penuh | Bisa dipercepat, lihat di bawah |
ADD FOREIGN KEY | ⚠️ Memindai | Pakai NOT VALID |
CREATE INDEX | ❌ Mengunci tulis | Pakai CONCURRENTLY |
Ini jebakan yang sangat merusak dan sering tidak disadari. Bahkan DDL yang biasanya instan pun harus menunggu kunci. Bila ada kueri panjang yang sedang berjalan, DDL Anda mengantre — dan semua kueri baru mengantre di belakangnya, termasuk SELECT sederhana. Satu ALTER TABLE yang tampak sepele bisa menghentikan seluruh aplikasi.
-- SELALU pasang lock_timeout sebelum DDL SET lock_timeout = '3s'; ALTER TABLE pesanan ADD COLUMN catatan text; -- Bila tidak bisa mendapat kunci dalam 3 detik, gagal saja -- lalu coba lagi nanti — jauh lebih baik daripada memblokir semuanya.
Menambah Constraint dengan Aman
-- ❌ Memindai seluruh tabel sambil mengunci
ALTER TABLE pesanan ADD CONSTRAINT ck_total CHECK (total >= 0);
-- ✅ Dua langkah
ALTER TABLE pesanan ADD CONSTRAINT ck_total
CHECK (total >= 0) NOT VALID; -- instan, berlaku untuk data BARU
ALTER TABLE pesanan VALIDATE CONSTRAINT ck_total; -- pindai tanpa mengunci tulis
-- SET NOT NULL tanpa pemindaian panjang (PostgreSQL 12+)
ALTER TABLE pesanan ADD CONSTRAINT ck_notnull
CHECK (pengguna_id IS NOT NULL) NOT VALID;
ALTER TABLE pesanan VALIDATE CONSTRAINT ck_notnull;
ALTER TABLE pesanan ALTER COLUMN pengguna_id SET NOT NULL; -- instan
ALTER TABLE pesanan DROP CONSTRAINT ck_notnull;
Pola Expand-Contract
Tujuan: mengganti nama kolom "nama" menjadi "nama_lengkap" RILIS 1 — EXPAND ALTER TABLE pengguna ADD COLUMN nama_lengkap text; CREATE TRIGGER sinkron ... -- tulis ke keduanya Aplikasi: menulis ke KEDUANYA, membaca dari "nama" RILIS 2 — ISI DATA UPDATE pengguna SET nama_lengkap = nama WHERE nama_lengkap IS NULL; -- dalam potongan kecil! RILIS 3 — PINDAH BACA Aplikasi: menulis ke keduanya, membaca dari "nama_lengkap" RILIS 4 — CONTRACT Aplikasi: hanya memakai "nama_lengkap" DROP TRIGGER sinkron; ALTER TABLE pengguna DROP COLUMN nama;
Selama rilis bergulir, versi lama dan baru aplikasi berjalan bersamaan terhadap basis data yang sama. Mengganti nama kolom dalam satu langkah akan langsung merusak versi lama yang masih melayani trafik. Aturan yang bisa dipegang: setiap perubahan skema harus kompatibel dengan versi aplikasi sebelumnya dan sesudahnya.
Memperbarui Data dalam Jumlah Besar
-- ✅ Potong-potong dengan jeda
DO $$
DECLARE
terpengaruh int;
BEGIN
LOOP
UPDATE pesanan SET wilayah = 'jakarta'
WHERE id IN (
SELECT id FROM pesanan
WHERE wilayah IS NULL
LIMIT 5000
FOR UPDATE SKIP LOCKED
);
GET DIAGNOSTICS terpengaruh = ROW_COUNT;
EXIT WHEN terpengaruh = 0;
COMMIT;
PERFORM pg_sleep(0.1); -- beri ruang untuk autovacuum & replika
END LOOP;
END $$;
Daftar Periksa Migrasi
| Periksa | Kenapa |
|---|---|
lock_timeout disetel | Mencegah antrean kunci melumpuhkan aplikasi |
| Setiap migrasi punya prosedur mundur | Rilis bisa dibatalkan |
| Diuji pada salinan data produksi | Durasi pada 10 baris tidak mewakili 10 juta baris |
Indeks dibuat CONCURRENTLY | Tidak memblokir penulisan |
| Pembaruan data dipecah | Menjaga WAL, kunci, dan lag replika tetap terkendali |
| Kompatibel dengan versi aplikasi sebelumnya | Rilis bergulir menjalankan dua versi bersamaan |
Alat migrasi seperti Flyway, Liquibase, golang-migrate, atau Alembic menangani pengurutan dan pencatatan versi. Untuk menangkap DDL berbahaya sebelum sampai produksi, squawk dapat dijalankan di pipeline CI untuk memeriksa berkas migrasi Anda dan memperingatkan operasi yang mengunci.