Praktik migration

Mengelola perubahan schema berversi yang dapat diulang di setiap lingkungan.

Daftar isi

Topik ini menutup jalur dengan praktik migration, yaitu perubahan skema yang disimpan sebagai berkas berversi dan diterapkan secara berurutan di setiap lingkungan. Migration menjadikan perubahan skema setara perubahan kode, yaitu tersimpan sebagai berkas yang melewati proses tinjauan (review), memiliki riwayat penerapan, dan mampu membangun basis data segar dari nol.

Blok penyiapan dataset#

bash
# basis data kosong sebagai sasaran migration
PGOPTIONS='-c client_min_messages=warning' psql -q -U postgres -d perpustakaan <<'SQL'
DROP TABLE IF EXISTS peminjaman, buku, anggota, kategori, skema_diterapkan CASCADE;
DROP SCHEMA IF EXISTS keuangan CASCADE;
SQL

Basis data contoh dikosongkan agar migration pada topik ini membangunnya dari nol, sebagaimana lingkungan baru yang menerima seluruh rantai migration untuk pertama kalinya.

Berkas migration berversi#

Satu migration berisi satu perubahan yang utuh, dengan nama berkas yang berurutan, dan daftar penerapannya dicatat pada tabel khusus. Dua berkas pertama pada topik ini membentuk skema awal lalu mengubahnya, dan keduanya disertakan sebagai aset di samping topik ini.

sql 001_skema.sql
-- tidak-dijalankan (berkas dieksekusi melalui psql -f pada blok bash)
-- migration 001: skema awal
CREATE TABLE kategori (
    id   SERIAL PRIMARY KEY,
    nama TEXT NOT NULL UNIQUE
);
CREATE TABLE buku (
    id          SERIAL PRIMARY KEY,
    judul       TEXT NOT NULL,
    penulis     TEXT NOT NULL,
    tahun       INTEGER NOT NULL,
    kategori_id INTEGER REFERENCES kategori(id)
);
sql 002_email.sql
-- tidak-dijalankan (berkas dieksekusi melalui psql -f pada blok bash)
-- migration 002: email unik pada kategori
ALTER TABLE kategori ADD COLUMN email TEXT;
ALTER TABLE kategori ADD CONSTRAINT kategori_email_unik UNIQUE (email);

Penomoran berurutan menjadi urutan penerapannya, dan satu-satunya aturan pada isi migration adalah perubahannya utuh serta dapat dijalankan pada lingkungan apa pun yang sudah menerima migration sebelumnya. Blok berikut membentuk tabel pencatat, menerapkan kedua berkas dengan psql, lalu membaca riwayatnya.

bash
# terapkan migration dan baca riwayatnya
psql -U postgres -d perpustakaan -c "CREATE TABLE skema_diterapkan (nomor TEXT PRIMARY KEY, diterapkan DATE NOT NULL DEFAULT CURRENT_DATE);"
psql -U postgres -d perpustakaan -v ON_ERROR_STOP=1 -f 001_skema.sql
psql -U postgres -d perpustakaan -c "INSERT INTO skema_diterapkan (nomor) VALUES ('001');"
psql -U postgres -d perpustakaan -v ON_ERROR_STOP=1 -f 002_email.sql
psql -U postgres -d perpustakaan -c "INSERT INTO skema_diterapkan (nomor) VALUES ('002');"
psql -U postgres -d perpustakaan -c "SELECT nomor FROM skema_diterapkan ORDER BY nomor;"
text Keluaran
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 1
ALTER TABLE
ALTER TABLE
INSERT 0 1
 nomor
-------
 001
 002
(2 rows)

Flag ON_ERROR_STOP menghentikan penerapan pada kegagalan pertama, syarat yang menjadikan kegagalan migration terdeteksi, bukan diabaikan di tengah rantai. Keluarannya memuat status setiap pernyataan, dan pencatat menegaskan bahwa dua migration telah masuk dengan urutannya.

Idempoten dan transaksional#

Migration yang baik bersifat transaksional, karena DDL pada PostgreSQL berjalan di dalam transaksi sebagaimana dibuktikan pada modul definisi data, sehingga kegagalan di tengah berkas membatalkan seluruh perubahannya. Kebiasaan yang menyertai adalah pernyataan idempoten pada migration tertentu, seperti IF EXISTS untuk pembersihan, tanpa meniadakan makna urutannya.

bash
# uji kegagalan migration membatalkan seluruh perubahannya
psql -U postgres -d perpustakaan <<'SQL'
BEGIN;
CREATE TABLE sementara_uji (id INTEGER);
CREATE TABLE sementara_uji (id INTEGER);
ROLLBACK;
SELECT to_regclass('sementara_uji') AS sisa;
SQL
text Keluaran
BEGIN
CREATE TABLE
ERROR:  relation "sementara_uji" already exists
ROLLBACK
 sisa
------

(1 row)

Tabel yang dibuat sebelum kegagalan kembali hilang setelah ROLLBACK, dan sifat inilah yang membuat satu berkas migration menjadi unit yang dapat dipercaya. Alat migration yang mapan, seperti Alembic untuk ekosistem Python yang dipersinggung pada topik ORM, menerapkan pola yang sama dengan pencatatan versi, pembungkus transaksi, dan perintah naik serta turun per migration.

Kesalahan umum#

Kesalahan pertama adalah menyunting migration yang sudah diterapkan, karena lingkungan yang pernah menerapkannya tidak pernah melihat suntingan itu, dan perubahan lanjutan menjadi migration baru. Kesalahan kedua adalah dua lingkungan yang berbeda isi basis datanya, penyebabnya biasanya migration yang diterapkan manual tanpa pencatat, dan pencatatnya menjadi satu-satunya sumber kebenaran.

Kesalahan ketiga adalah migration yang menulis ulang data besar dalam satu berkas, yang menahan tabel sepanjang penulisannya, dan pola yang aman memecah perubahan skema dari pengisian datanya sebagaimana dibahas pada modul ALTER TABLE. Kesalahan keempat adalah migration tanpa jalur kembali, sehingga satu-satunya jawaban atas kegagalan produksi adalah pemulihan penuh, sedangkan pasangan naik-turun per migration memberi pilihan pemulihan yang lebih halus.

Selain perubahan skema, data benih (seed) yang dibutuhkan lingkungan segar menjadi bagian sah rantai migration, misalnya daftar kategori bawaan pada dataset ini, dan pemisahannya ke berkas sendiri menjaga migration skema tetap murni. Lingkungan baru dengan demikian terbangun lengkap dari dua sumber, yaitu seluruh migration skema dan benihnya, tanpa unduhan manual apa pun. Kebiasaan ini menyatukan cara kerja laptop pengembang, lingkungan uji, dan produksi sejak hari pertama.

Ringkasan#

  • Migration berupa berkas berurutan yang diterapkan sekali per lingkungan, dan pencatat versinya menjadi sumber kebenaran.
  • Flag ON_ERROR_STOP menjadikan kegagalan migration berhenti jelas, sementara transaksi membatalkan perubahannya utuh.
  • Migration yang diterapkan tidak pernah disunting, dan perubahan lanjutan menjadi berkas baru di ujung rantainya.
  • Pasangan naik-turun per migration memberi jalur kembali, dan perubahan skema besar dipecah dari pengisian datanya.

Referensi#