karawaci.kode

2026-08-04 · 10 min

Drizzle Kit vs Prisma Migrate: Studi Kasus Live Migration 500K Rows

Di production klien Jakarta saya ada tabel transactions dengan 500K rows yang terus tumbuh sekitar 8K rows per hari. Suatu sprint, tim perlu menambah tiga kolom baru, mengubah satu tipe kolom, dan membuat dua index baru. Sebelum sprint itu saya sudah curiga bahwa cara kami menjalankan migration perlu berubah — dan memang benar, percobaan pertama dengan Prisma Migrate hampir menghasilkan downtime 2 menit di jam sibuk.

Konteks: kenapa ini penting

Migration tool yang “berhasil di development” tidak otomatis aman di production. Dua operasi yang kelihatan sepele — menambah kolom NOT NULL dan membuat index — bisa mengambil exclusive lock di PostgreSQL dan memblokir seluruh query masuk selama proses berjalan. Di tabel kecil ini tidak terasa; di tabel 500K rows dengan traffic aktif, ini bisa berarti error atau timeout untuk pengguna nyata.

Drizzle Kit dan Prisma Migrate adalah dua tool migrasi paling populer di ekosistem Node.js/TypeScript. Keduanya punya pendekatan yang sangat berbeda, dan perbedaan itu terasa paling nyata justru di skenario production yang kritis.

Cara kerja masing-masing

Prisma Migrate bekerja secara deklaratif penuh: Anda edit schema.prisma, jalankan prisma migrate dev, dan Prisma men-diff schema lama dengan schema baru lalu meng-generate SQL migration. Saat deploy, prisma migrate deploy menjalankan semua migration yang belum dieksekusi. SQL yang di-generate tidak dimaksudkan untuk diedit manual — idealnya Anda percayakan semuanya ke Prisma.

Drizzle Kit bekerja lebih eksplisit: schema didefinisikan sebagai TypeScript biasa, drizzle-kit generate membuat file SQL migration di folder drizzle/, dan Anda bisa — bahkan dianjurkan — membaca dan mengedit SQL itu sebelum menjalankan drizzle-kit migrate. File SQL-nya adalah plain SQL, bukan format proprietary.

Perbedaan filosofi ini menentukan segalanya.

Kasus nyata: tiga perubahan, satu masalah besar

Perubahan yang dibutuhkan sprint itu:

  1. Tambah kolom settled_at TIMESTAMPTZ NOT NULL
  2. Ubah kolom amount dari NUMERIC(12,2) ke NUMERIC(15,4)
  3. Buat index pada (merchant_id, created_at) untuk query dashboard

Dengan Prisma Migrate, saya jalankan prisma migrate dev --name add-settled-at-index-merchant:

-- Prisma-generated migration (disederhanakan)
ALTER TABLE "transactions"
  ADD COLUMN "settled_at" TIMESTAMP(3) NOT NULL,
  ALTER COLUMN "amount" TYPE DECIMAL(15,4);

CREATE INDEX "transactions_merchant_id_created_at_idx"
  ON "transactions"("merchant_id", "created_at");

SQL ini valid, tapi ada tiga masalah production:

  1. ADD COLUMN ... NOT NULL tanpa DEFAULT — di PostgreSQL ini menyebabkan table rewrite untuk tabel non-empty. Di PostgreSQL 11+ ada optimasi untuk DEFAULT volatile, tapi kolom timestamp tanpa default tetap bermasalah.
  2. ALTER COLUMN ... TYPE dengan perubahan presisi — juga butuh table rewrite.
  3. CREATE INDEX tanpa CONCURRENTLY — mengambil ACCESS EXCLUSIVE lock, memblokir semua operasi lain sampai index selesai dibuat.

Dengan tabel 500K rows, simulasi di staging menunjukkan lock duration sekitar 90 detik. Di production jam sibuk, ini tidak acceptable.

Solusi dengan Drizzle Kit

Dengan Drizzle Kit, saya punya kendali penuh atas SQL. Pertama, generate migration seperti biasa:

drizzle-kit generate

File yang di-generate di drizzle/0023_add_settled_at.sql kira-kira sama dengan yang Prisma buat. Tapi sekarang saya bisa edit file itu langsung sebelum dijalankan.

Saya pecah jadi dua file migration:

drizzle/0023_add_settled_at_nullable.sql — dijalankan deploy pertama:

-- Step 1: tambah kolom NULLABLE dulu
ALTER TABLE "transactions"
  ADD COLUMN "settled_at" TIMESTAMPTZ;

-- Step 2: ALTER tipe amount — tetap butuh table rewrite,
-- tapi saya jadwalkan di maintenance window 5 menit
ALTER TABLE "transactions"
  ALTER COLUMN "amount" TYPE NUMERIC(15,4);

-- Step 3: buat index CONCURRENTLY — tidak blokir traffic
CREATE INDEX CONCURRENTLY "transactions_merchant_id_created_at_idx"
  ON "transactions"("merchant_id", "created_at");

drizzle/0024_set_settled_at_not_null.sql — dijalankan deploy kedua, setelah aplikasi sudah mengisi settled_at untuk semua row baru dan backfill sudah selesai:

-- Hanya set NOT NULL setelah semua rows terisi
ALTER TABLE "transactions"
  ALTER COLUMN "settled_at" SET NOT NULL;

Konfigurasi Drizzle di project:

// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
  // Jangan pakai verbose di production CI/CD
  verbose: process.env.NODE_ENV === 'development',
  strict: true, // Minta konfirmasi untuk operasi destructive
});

Script migration di package.json:

{
  "scripts": {
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:studio": "drizzle-kit studio"
  }
}

Di CI/CD, yang dijalankan hanya db:migrate — tidak pernah db:generate atau drizzle-kit push.

Prisma Migrate workaround: —create-only

Prisma sebenarnya punya jalan keluar: flag --create-only.

prisma migrate dev --name add-settled-at --create-only

Ini hanya membuat file migration tanpa langsung mengeksekusinya. Anda bisa edit SQL-nya, lalu jalankan:

prisma migrate deploy

Sayangnya ini tidak default dan tidak terdokumentasi dengan menonjol. Sebagian besar tim yang pakai Prisma tidak tahu opsi ini ada sampai sudah kena masalah. Ditambah lagi, setelah file di-edit manual, prisma migrate dev di lokal developer lain bisa komplain karena checksum tidak cocok dengan schema yang diexpect. Anda harus hati-hati mengelola flow ini.

Perbandingan head-to-head

AspekDrizzle KitPrisma Migrate
Edit SQL sebelum jalanNative, langsung edit fileButuh --create-only, rawan konflik
Schema definitionTypeScript biasaDSL .prisma tersendiri
File migrationPlain SQLSQL + metadata JSON
CONCURRENTLY indexTambah manual di SQLTambah manual via --create-only
RollbackManual (tulis SQL balik)Manual (mark resolved)
Kurva belajarLebih rendah untuk yang paham SQLLebih tinggi, abstraksi tebal
EkosistemTumbuh cepatLebih mature, lebih banyak tooling

Trade-off yang jujur

Drizzle Kit memberi kontrol penuh, tapi tanggung jawab ada di tangan Anda. Kalau tim tidak paham PostgreSQL locking behavior, kontrol itu tidak banyak membantu. File SQL yang diedit sembarangan bisa lebih berbahaya dari yang di-generate otomatis.

Prisma Migrate lebih opiniated dan protektif untuk use case standar. Untuk tim yang tidak mau menyentuh SQL dan tabelnya kecil, Prisma lebih aman secara default. Masalah muncul justru saat tabel tumbuh dan tim tidak tahu harus keluar dari alur Prisma yang normal.

Untuk ALTER TABLE yang benar-benar butuh table rewrite (perubahan tipe signifikan), kedua tool memerlukan maintenance window atau pendekatan alternatif seperti shadow table. Tidak ada magic — ini limitasi PostgreSQL, bukan tool-nya.

Verdict

Kalau tabel Anda sudah di atas 100K rows dan traffic tidak bisa berhenti, gunakan Drizzle Kit. Kemampuan mengedit SQL migration secara langsung bukan fitur nice-to-have — ini kebutuhan production. Prisma Migrate bisa dipakai, tapi Anda harus selalu memakai --create-only dan mendidik seluruh tim soal ini, yang menciptakan friction dan potensi human error.

Satu aturan yang saya terapkan di semua project: setiap file migration wajib di-review seperti code review biasa — bukan hanya dilihat schema-nya, tapi SQL-nya dibaca baris per baris untuk operasi apa yang akan terjadi di database. Tool migration hanya alat bantu; keputusan apakah operasi itu aman di production tetap ada di kepala engineer yang menjalankannya.

Untuk project baru dari nol di 2026, saya default ke Drizzle Kit. Untuk project yang sudah pakai Prisma dan tabelnya masih kecil, tidak perlu migrasi — tapi mulailah biasakan --create-only sebelum Anda butuh.

Ditulis oleh Reza Pradipta