Anatomi Schema Drift pada Rolling Deploy Bun SQL
Penggunaan pendekatan type-safe raw SQL pada runtime Bun (seperti pola bun-sqlgen atau SQL code generator lainnya) memberikan keuntungan validasi tipe pada saat compile/build time serta latensi eksekusi minimal melalui Bun.sql. Masalah muncul ketika kode tersebut dideploy ke kluster multi-instance menggunakan strategi rolling deployment.
Ketika migrasi DDL diterapkan langsung pada basis data bersamaan dengan peluncuran pod baru, jendela transisi memunculkan kondisi di mana dua versi aplikasi berjalan bersamaan di database yang sama:
- Instance v1 (Lama) menjalankan query yang mengasumsikan skema lama.
- Instance v2 (Baru) menjalankan query hasil generate yang mengasumsikan skema baru.
Jika perubahan skema bersifat destruktif (misalnya renaming column atau dropping column), PostgreSQL akan mengembalikan runtime error seperti 42703 (undefined_column) atau 42P01 (undefined_table). Instance Bun tidak dapat mengompilasi ulang query SQL runtime yang sudah dibundel ke dalam binary atau modul JavaScript.
// generated/orders.sql.ts
import { sql } from "bun";
export interface OrderRow {
id: string;
lifecycle_state: string; // Hasil rename dari kolom 'status'
}
export async function getOrderById(id: string): Promise<OrderRow | null> {
// Mengalami runtime error 42703 jika pod v2 hidup sebelum migrasi,
// atau query lama crash jika migrasi mendahului deploy pod v1.
const [row] = await sql<OrderRow[]>`
SELECT id, lifecycle_state FROM orders WHERE id = ${id}
`;
return row ?? null;
}Observabilitas: Health Check dan Error Telemetri
Untuk mencegah instance yang mengalami schema drift terus menerima trafik pengguna, pisahkan endpoint /healthz (liveness) dan /readyz (readiness). Endpoint readiness harus memvalidasi konektivitas dasar dan menandai node sebagai tidak sehat jika volume error skema melampaui batas toleransi.
// server.ts
import { sql } from "bun";
let schemaErrorSpike = 0;
const ERROR_THRESHOLD = 5;
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/healthz") {
return new Response("OK", { status: 200 });
}
if (url.pathname === "/readyz") {
if (schemaErrorSpike >= ERROR_THRESHOLD) {
return new Response("Schema Drift Spike Detected", { status: 503 });
}
try {
await sql`SELECT 1`;
return new Response("READY", { status: 200 });
} catch {
return new Response("DB Unreachable", { status: 503 });
}
}
if (url.pathname.startsWith("/orders/")) {
const id = url.pathname.split("/")[2];
try {
const [order] = await sql`SELECT id, lifecycle_state FROM orders WHERE id = ${id}`;
return Response.json(order ?? {});
} catch (err: any) {
// PostgreSQL error code untuk schema mismatch
if (err.code === "42703" || err.code === "42P01") {
schemaErrorSpike++;
console.error(JSON.stringify({
severity: "CRITICAL",
event: "schema_drift",
code: err.code,
message: err.message,
spikeCount: schemaErrorSpike,
timestamp: new Date().toISOString()
}));
}
return new Response("Internal Server Error", { status: 500 });
}
}
return new Response("Not Found", { status: 404 });
}
});Log terstruktur dalam format JSON memastikan log aggregator (seperti Grafana Loki atau Datadog) dapat memicu alert rule seketika saat event schema_drift terdeteksi.
Mekanisme Safe Rollback Otomatis
Jika error spike terdeteksi sesaat setelah rolling deployment dimulai, proses deploy harus segera dihentikan dan dikembalikan ke versi stabil sebelumnya secara terprogram.
#!/usr/bin/env bash
# deploy-monitor.sh: Memantau deployment dan memicu rollback otomatis
set -euo pipefail
DEPLOYMENT_NAME="bun-sql-order-service"
NAMESPACE="production"
THRESHOLD=3
POLL_INTERVAL=5
MAX_CHECKS=12
echo "Deploying $DEPLOYMENT_NAME..."
kubectl rollout status deployment/"$DEPLOYMENT_NAME" -n "$NAMESPACE" --timeout=60s
echo "Memulai observasi telemetri pasca-deploy..."
for ((i=1; i<=MAX_CHECKS; i++)); do
sleep "$POLL_INTERVAL"
DRIFT_COUNT=$(kubectl logs -n "$NAMESPACE" -l app="$DEPLOYMENT_NAME" --tail=100 \
| grep -c '"event":"schema_drift"' || true)
if [ "$DRIFT_COUNT" -ge "$THRESHOLD" ]; then
echo "[DANGER] Terdeteksi $DRIFT_COUNT schema drift error! Menjalankan rollback..."
kubectl rollout undo deployment/"$DEPLOYMENT_NAME" -n "$NAMESPACE"
kubectl rollout status deployment/"$DEPLOYMENT_NAME" -n "$NAMESPACE"
echo "Rollback selesai. Pod dikembalikan ke revisi stabil."
exit 1
fi
done
echo "Deployment diverifikasi stabil tanpa schema drift."Postmortem dan Pencegahan Struktural
1. Pola Migrasi Expand-Contract (Parallel Run)
Jangan pernah mengeksekusi breaking changes secara langsung pada database relasional dalam satu langkah rilis. Gunakan pola tiga fase:
- Expand: Tambahkan kolom baru tanpa menghapus kolom lama. Biarkan kolom baru opsional atau pasang sinkronisasi data sementara (via DB trigger atau dual-write).
- Deploy: Rilis kode Bun SQL baru yang membaca dari kolom baru. Karena kolom lama masih ada, instance v1 lama tetap berjalan normal selama proses pergantian pod.
- Contract: Setelah 100% trafik beralih ke pod baru dan stabil, rilis migrasi terpisah untuk menghapus kolom usang beserta trigger transisi.
2. Audit Sinkronisasi Skema pada Pipeline CI/CD
Cegah ketidaksesuaian kode SQL hasil generate sebelum rilis masuk ke registry. Buat step verifikasi di GitHub Actions atau GitLab CI yang menjalankan database sementara, menerapkan migrasi, menjalankan generator, dan memeriksa integritas git tree.
# Cuplikan step CI
- name: Verify SQL Codegen Sync
run: |
bun run db:migrate:up
bun run sql:generate
git diff --exit-code generated/
# Jika ada perbedaan file, build gagal: developer lupa regenerate query setelah ubah DDLCatatan: Rollback kode aplikasi mudah dilakukan, namun rollback migrasi DDL pada data produksi sering kali berisiko memicu data loss. Jadikan kompatibilitas mundur (backward-compatible DDL) sebagai aturan wajib sebelum merge ke branch release.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!