Migrasi skema tanpa mematikan situs
Selama masa transisi, dua aplikasi membaca skema yang sama. Setiap perubahan skema harus kompatibel ke belakang, atau salah satunya mati.
Intisari
- Migrasi Kysely adalah berkas TypeScript dengan
updandown. Dijalankan lewat skrip sendiri. - Jangan jalankan migrasi di entrypoint container. Dua belas task ECS akan menjalankannya bersamaan.
- Selama transisi, CI3 dan Astro berbagi skema โ tiap perubahan harus kompatibel dua arah.
- Mengganti nama kolom butuh tiga deploy, bukan satu: tambah, tulis-ganda, hapus.
ALTER TABLEdi tabel besar bisa memakan menit dan memicu lonjakan lag replica. Jadwalkan di jam sepi.
Bentuk berkas migrasi
// db/migrations/2026-08-27T10-00-00-tambah-cuplikan.ts
import { Kysely, sql } from "kysely";
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable("artikel")
.addColumn("cuplikan", "text", (col) => col)
.execute();
await db.schema
.createIndex("idx_artikel_daftar")
.on("artikel")
.columns(["status", "kategori_id", "terbit_pada"])
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropIndex("idx_artikel_daftar").on("artikel").execute();
await db.schema.alterTable("artikel").dropColumn("cuplikan").execute();
}
Perhatikan Kysely<any>. Migrasi sengaja tidak memakai tipe skema โ karena skema
sedang berubah, dan tipe yang ada mencerminkan bentuk sebelum atau sesudah, tidak keduanya.
Menjalankannya
// db/migrate.ts
import { promises as fs } from "node:fs";
import path from "node:path";
import { Migrator, FileMigrationProvider } from "kysely";
import { dbTulis } from "../src/lib/db";
const migrator = new Migrator({
db: dbTulis,
provider: new FileMigrationProvider({
fs,
path,
migrationFolder: path.resolve("db/migrations"),
}),
});
const { error, results } = await migrator.migrateToLatest();
for (const r of results ?? []) {
console.log(`${r.status}: ${r.migrationName}`);
}
if (error) {
console.error(error);
process.exit(1);
}
await dbTulis.destroy();
Migrasi berjalan sebagai tugas ECS terpisah, bukan di entrypoint container web. Kalau kamu menaruhnya di entrypoint, dua belas task akan menjalankannya bersamaan saat deploy. Kysely memakai tabel kunci sehingga tidak akan rusak โ tapi sebelas task akan menunggu, gagal health check, dan ECS akan mengira deploy-nya gagal lalu me-rollback. Jalankan sebagai one-off task di pipeline (Fase 10).
Aturan utama selama transisi
Selama Fase 9, CI3 lama dan Astro baru berjalan bersamaan di atas satu database. Tiap perubahan skema harus bisa dijalani keduanya.
| Perubahan | Aman? | Catatan |
|---|---|---|
| Menambah kolom nullable | Ya | CI3 mengabaikannya |
Menambah kolom NOT NULL ber-DEFAULT | Ya | INSERT lama tetap jalan |
Menambah kolom NOT NULL tanpa default | Tidak | INSERT dari CI3 langsung gagal |
| Menambah index | Ya | Tapi lihat catatan waktu di bawah |
| Menghapus kolom | Tidak | CI3 mungkin masih SELECT * dan menulisnya |
| Mengganti nama kolom | Tidak | Butuh tiga deploy โ lihat di bawah |
Memperlebar VARCHAR | Ya | Aman satu arah |
| Mempersempit tipe | Tidak | Data lama bisa tidak muat |
Menambah UNIQUE | Hati-hati | Gagal kalau sudah ada duplikat; periksa dulu |
Ganti nama kolom = tiga deploy
Misalnya artikel.gambar ingin jadi artikel.gambar_sampul.
Deploy 1 โ TAMBAH
ALTER TABLE artikel ADD COLUMN gambar_sampul VARCHAR(255) NULL;
UPDATE artikel SET gambar_sampul = gambar WHERE gambar_sampul IS NULL;
Kode: BACA dari gambar_sampul, fallback ke gambar. TULIS ke keduanya.
Deploy 2 โ TULIS-GANDA SELESAI
Kode: baca dan tulis hanya gambar_sampul.
Tunggu. Beberapa hari. Pastikan tidak ada yang membaca kolom lama โ
buktikan dengan Performance Schema, jangan dengan keyakinan.
Deploy 3 โ HAPUS
ALTER TABLE artikel DROP COLUMN gambar;
Terasa berlebihan untuk satu nama kolom. Tapi selama rolling deploy, kode lama dan baru berjalan bersamaan selama beberapa menit โ dan selama transisi CI3 juga masih ada. Melakukannya dalam satu langkah berarti setiap request yang mengenai task lama akan gagal sampai deploy selesai.
Jujur saja: sering kali jawabannya adalah jangan ganti nama kolomnya. Nama jelek di database
bisa disembunyikan di lapisan src/lib/, tempat kamu memetakan gambar menjadi
gambarSampul di objek domain. Pembaca kodemu melihat nama yang bagus; database menyimpan
nama lamanya. Biaya migrasi tiga tahap jarang sebanding dengan keuntungannya.
ALTER TABLE di tabel besar
-- MySQL 8.0 mendukung DDL online untuk banyak operasi, tapi tidak semua
ALTER TABLE artikel
ADD COLUMN cuplikan TEXT NULL,
ALGORITHM=INPLACE, LOCK=NONE;
LOCK=NONE menyuruh MySQL menolak operasi kalau ia tidak bisa dilakukan tanpa mengunci tabel โ
itu pagar yang bagus. Lebih baik migrasinya gagal cepat daripada tabel artikelmu terkunci lima menit di
jam sibuk.
| Operasi | Bisa LOCK=NONE? |
|---|---|
| Menambah kolom | Ya |
| Menambah index sekunder | Ya |
| Menghapus index | Ya |
| Mengubah tipe kolom | Sering tidak โ menyalin seluruh tabel |
Menambah kolom PRIMARY KEY | Tidak |
| Mengubah charset tabel | Tidak โ dan ini yang paling lama |
Yang terakhir itu relevan untukmu. Tabel CI3 lama sering ber-charset latin1 atau
utf8mb3, yang tidak bisa menyimpan emoji dan sebagian karakter. Mengubahnya ke
utf8mb4 menyalin seluruh tabel dan mengunci โ untuk tabel artikel berukuran gigabita itu
bisa puluhan menit. Rencanakan sebagai pekerjaan tersendiri di jendela pemeliharaan, bukan diselipkan
di deploy biasa. Alat seperti pt-online-schema-change atau gh-ost ada untuk
kasus ini.
Migrasi dan replica
ALTER TABLE besar di writer akan dijalankan ulang di replica โ dan selama itu replica
tertinggal jauh. Kalau kamu sudah memisahkan baca ke replica (materi sebelumnya), pembacaan portalmu akan
menyajikan data basi selama migrasi berjalan. Untuk migrasi besar, alihkan pembacaan ke writer dulu, atau
jalankan di jam paling sepi.
Latihan: tulis satu migrasi yang menambahkan kolom cuplikan TEXT NULL ke tabel
artikel plus index gabungan untuk kueri daftar. Jalankan up, verifikasi dengan
SHOW CREATE TABLE artikel, lalu jalankan down dan verifikasi ia benar-benar
kembali seperti semula. Lalu, di salinan produksi, ukur berapa lama ALTER TABLE itu
berjalan di tabel artikelmu yang sebenarnya โ angkanya akan menentukan apakah kamu butuh jendela
pemeliharaan.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.