โ† Semua pembelajaran / Astro Nol โ†’ Portal Berita
Fase 2 ยท MySQL dengan Kysely

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.

Sumber asli kysely.dev Resmi Rangkuman ~9 menit baca

Intisari

  • Migrasi Kysely adalah berkas TypeScript dengan up dan down. 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 TABLE di 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.

PerubahanAman?Catatan
Menambah kolom nullableYaCI3 mengabaikannya
Menambah kolom NOT NULL ber-DEFAULTYaINSERT lama tetap jalan
Menambah kolom NOT NULL tanpa defaultTidakINSERT dari CI3 langsung gagal
Menambah indexYaTapi lihat catatan waktu di bawah
Menghapus kolomTidakCI3 mungkin masih SELECT * dan menulisnya
Mengganti nama kolomTidakButuh tiga deploy โ€” lihat di bawah
Memperlebar VARCHARYaAman satu arah
Mempersempit tipeTidakData lama bisa tidak muat
Menambah UNIQUEHati-hatiGagal 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.

OperasiBisa LOCK=NONE?
Menambah kolomYa
Menambah index sekunderYa
Menghapus indexYa
Mengubah tipe kolomSering tidak โ€” menyalin seluruh tabel
Menambah kolom PRIMARY KEYTidak
Mengubah charset tabelTidak โ€” 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.