Pelajari cara sistem yang dibangun AI menangani perubahan skema dengan aman: versi, rollout kompatibel mundur, migrasi data, pengujian, observability, dan strategi rollback.

Sebuah skema pada dasarnya adalah kesepakatan bersama tentang bentuk data dan apa arti setiap field. Dalam sistem yang dibangun oleh AI, kesepakatan itu muncul di lebih banyak tempat daripada sekadar tabel basis data—dan ia berubah lebih sering daripada yang tim duga.
Anda akan menemukan skema setidaknya di empat lapisan umum:
Jika dua bagian sistem saling bertukar data, ada skema—meskipun tidak ada yang menuliskannya.
Kode yang dihasilkan AI dapat mempercepat pengembangan secara drastis, tapi juga meningkatkan churn:
id vs userId) muncul ketika beberapa generasi atau refactor terjadi di tim berbeda.Hasilnya adalah “contract drift” yang lebih sering antara producer dan consumer.
Jika Anda menggunakan alur kerja vibe-coding (misalnya menghasilkan handler, lapisan akses DB, dan integrasi lewat chat), penting untuk menanamkan disiplin skema ke alur itu sejak hari pertama. Platform seperti Koder.ai membantu tim bergerak cepat dengan menghasilkan aplikasi React/Go/PostgreSQL dan Flutter dari antarmuka chat—tetapi semakin cepat Anda mengirim, semakin penting untuk memberi versi pada antarmuka, memvalidasi payload, dan menggulirkan perubahan secara terencana.
Post ini berfokus pada cara praktis menjaga produksi tetap stabil sambil tetap iterasi cepat: mempertahankan kompatibilitas mundur, menggulirkan perubahan dengan aman, dan memigrasi data tanpa kejutan.
Kami tidak akan mendalami pemodelan teori berat, metode formal, atau fitur vendor-spesifik. Penekanan ada pada pola yang bisa diterapkan di berbagai stack—baik sistem Anda ditulis tangan, dibantu AI, atau sebagian besar dihasilkan AI.
Kode hasil AI cenderung membuat perubahan skema terasa “normal”—bukan karena tim ceroboh, tetapi karena input ke sistem berubah lebih sering. Ketika perilaku aplikasi sebagian dikendalikan oleh prompt, versi model, dan kode penghubung yang dihasilkan, bentuk data lebih rentan untuk bergeser seiring waktu.
Beberapa pola berulang menyebabkan churn skema:
risk_score, explanation, source_url) atau memecah satu konsep menjadi banyak (mis. address jadi street, city, postal_code).Kode yang dihasilkan AI sering “berfungsi” dengan cepat, tapi bisa mengenkode asumsi yang rapuh:
Code generation mendorong iterasi cepat: Anda menggenerate ulang handler, parser, dan lapisan akses DB saat kebutuhan berubah. Kecepatan itu berguna, tetapi juga memudahkan untuk mengirim perubahan antarmuka kecil berulang kali—kadang tanpa disadari.
Mindset yang lebih aman adalah memperlakukan setiap skema sebagai kontrak: tabel basis data, payload API, event, bahkan respons terstruktur LLM. Jika sebuah consumer bergantung padanya, versi-kan, validasi, dan ubah secara disengaja.
Perubahan skema tidak semuanya setara. Pertanyaan paling berguna pertama adalah: apakah consumer lama akan tetap bekerja tanpa perubahan? Jika ya, biasanya additif. Jika tidak, itu breaking—dan perlu rencana rollout yang dikoordinasikan.
Perubahan additif memperluas apa yang sudah ada tanpa merubah makna yang ada.
Contoh umum di basis data:
preferred_language).Contoh non-basis data:
Additif hanya “aman” jika consumer lama toleran: mereka harus mengabaikan field yang tidak dikenal dan tidak mengharuskan field baru.
Perubahan breaking mengubah atau menghapus sesuatu yang sudah diandalkan oleh consumer.
Contoh breaking di basis data:
Contoh breaking non-basis data:
Sebelum merge, dokumentasikan:
Catatan dampak singkat ini memaksa kejelasan—terutama ketika kode yang dihasilkan AI memperkenalkan perubahan skema secara implisit.
Versioning adalah cara memberi tahu sistem lain (dan Anda di masa depan) “ini berubah, dan begini tingkat risikonya.” Tujuannya bukan sekadar administrasi—melainkan mencegah kerusakan silent saat klien, layanan, atau pipeline data update pada kecepatan berbeda.
Pikirkan dalam istilah major / minor / patch, meskipun Anda tidak selalu mempublikasikan 1.2.3 secara literal:
Aturan sederhana yang menyelamatkan tim: jangan pernah mengubah makna field yang sudah ada secara diam-diam. Jika status="active" dulu berarti “pelanggan yang membayar,” jangan repurpose menjadi “akun ada.” Tambahkan field baru atau versi baru.
Biasanya ada dua opsi praktis:
1) Endpoint berversi (mis. /api/v1/orders dan /api/v2/orders):
Bagus ketika perubahan benar-benar breaking atau luas. Jelas, tapi bisa menimbulkan duplikasi dan pemeliharaan jangka panjang jika Anda mempertahankan banyak versi.
2) Field berversi / evolusi additif (mis. tambahkan new_field, pertahankan old_field):
Bagus ketika Anda bisa membuat perubahan secara additif. Klien lama mengabaikan yang tak dipahami; klien baru membaca field baru. Seiring waktu, deprecate dan hapus field lama dengan rencana eksplisit.
Untuk stream, queue, dan webhook, consumer sering berada di luar kontrol deployment Anda. Sebuah schema registry (atau katalog skema terpusat dengan pengecekan kompatibilitas) membantu menegakkan aturan seperti “hanya perubahan additif yang diizinkan” dan membuat jelas producer mana serta consumer mana yang mengandalkan versi tertentu.
Cara paling aman mengirim perubahan skema—terutama saat Anda punya banyak layanan, job, dan komponen yang digenerate AI—adalah pola expand → backfill → switch → contract. Ini meminimalkan downtime dan menghindari deployment “semua-atau-tidak” di mana satu consumer tertinggal dapat merusak produksi.
1) Expand: Perkenalkan skema baru secara backward-compatible. Pembaca dan penulis lama seharusnya tetap bekerja tanpa perubahan.
2) Backfill: Isi field baru untuk data historis (atau proses ulang pesan) sehingga sistem jadi konsisten.
3) Switch: Update penulis dan pembaca untuk memakai field/format baru. Bisa dilakukan bertahap (canary, rollout persen) karena skema mendukung keduanya.
4) Contract: Hapus field/format lama hanya setelah yakin tidak ada yang bergantung padanya.
Rollout dua fase (expand → switch) dan tiga fase (expand → backfill → switch) mengurangi downtime karena menghindari kopling ketat: penulis bisa pindah dulu, pembaca bisa pindah kemudian, dan sebaliknya.
Misalkan Anda ingin menambahkan customer_tier.
customer_tier sebagai nullable dengan default NULL.customer_tier, dan update pembaca untuk memprioritaskannya.Perlakukan setiap skema sebagai kontrak antara producer (writer) dan consumer (reader). Dalam sistem yang dibangun AI, ini mudah terlewat karena jalur kode baru muncul cepat. Buat rollout eksplisit: dokumentasikan versi mana yang menulis apa, layanan mana yang bisa membaca keduanya, dan tanggal “kontrak” ketika field lama boleh dihapus.
Migrasi basis data adalah “manual instruksi” untuk memindahkan data dan struktur produksi dari satu keadaan aman ke keadaan berikutnya. Dalam sistem yang dibangun AI, mereka lebih penting karena kode yang digenerate bisa mengasumsikan kolom ada, mengganti nama field secara tidak konsisten, atau mengubah constraint tanpa mempertimbangkan baris yang ada.
Berkas migrasi (dimasukkan ke source control) adalah langkah eksplisit seperti “tambah kolom X,” “buat indeks Y,” atau “salin data dari A ke B.” Mereka dapat diaudit, ditinjau, dan dapat dijalankan ulang di staging dan produksi.
Auto-migrations (dihasilkan oleh ORM/framework) nyaman untuk pengembangan awal dan prototyping, tetapi bisa menghasilkan operasi berisiko (menghapus kolom, membangun kembali tabel) atau mengubah urutan perubahan dengan cara yang tidak Anda inginkan.
Aturan praktis: gunakan auto-migrations untuk membuat draf perubahan, lalu konversi ke berkas migrasi yang ditinjau untuk apapun yang menyentuh produksi.
Buat migrasi idempoten bila memungkinkan: menjalankannya ulang tidak boleh merusak data atau gagal di tengah jalan. Prefer “create if not exists,” tambahkan kolom baru sebagai nullable dulu, dan lindungi transformasi data dengan pemeriksaan.
Juga jaga urutan jelas. Setiap environment (lokal, CI, staging, prod) harus menerapkan rangkaian migrasi yang sama. Jangan “memperbaiki” produksi dengan SQL manual kecuali Anda menangkapnya dalam migrasi setelahnya.
Beberapa perubahan skema bisa memblokir penulisan (atau bahkan pembacaan) jika mengunci tabel besar. Cara tingkat tinggi untuk mengurangi risiko:
Untuk basis data multi-tenant, jalankan migrasi dalam loop yang terkontrol per tenant, dengan pelacakan progres dan retry aman. Untuk shard, perlakukan setiap shard seperti sistem produksi terpisah: jalankan migrasi shard-per-shard, verifikasi kesehatan, lalu lanjutkan. Ini membatasi blast radius dan membuat rollback bisa dilakukan.
Sebuah backfill adalah ketika Anda mengisi field baru (atau nilai yang dikoreksi) untuk record yang sudah ada. Reprocessing adalah ketika Anda memproses kembali data historis lewat pipeline—biasanya karena aturan bisnis berubah, bug diperbaiki, atau format output/model diubah.
Keduanya umum setelah perubahan skema: mudah untuk mulai menulis bentuk baru untuk “data baru,” tapi sistem produksi juga bergantung pada data kemarin yang konsisten.
Backfill online (di produksi, bertahap). Jalankan job terkontrol yang memperbarui record dalam batch kecil saat sistem tetap live. Lebih aman untuk layanan kritikal karena Anda bisa mengatur throttle, pause, dan resume.
Backfill batch (offline atau job terjadwal). Proses potongan besar saat jendela trafik rendah. Lebih sederhana dari sisi operasional, tapi bisa menciptakan lonjakan beban DB dan butuh waktu lebih lama untuk pulih dari kesalahan.
Lazy backfill saat baca. Ketika record lama dibaca, aplikasi menghitung/mengisi field yang hilang dan menulisnya kembali. Ini menyebarkan biaya seiring waktu dan menghindari job besar, tetapi membuat pembacaan pertama lebih lambat dan bisa membiarkan data “lama” tidak terkonversi dalam waktu lama.
Dalam praktiknya, tim sering menggabungkan: lazy backfill untuk long-tail record, plus job online untuk data yang paling sering diakses.
Validasi harus eksplisit dan terukur:
Juga validasi efek downstream: dashboard, indeks pencarian, cache, dan export yang bergantung pada field yang diperbarui.
Backfill menukar kecepatan (selesai cepat) dengan risiko dan biaya (beban, compute, dan overhead operasional). Tetapkan kriteria penerimaan di muka: apa artinya “selesai”, runtime yang diharapkan, tingkat error maksimum yang diizinkan, dan tindakan jika validasi gagal (pause, retry, atau rollback).
Skema tidak hanya hidup di basis data. Setiap kali satu sistem mengirim data ke sistem lain—topik Kafka, antrean SQS/RabbitMQ, payload webhook, bahkan “event” yang ditulis ke object storage—Anda telah membuat kontrak. Producer dan consumer bergerak secara independen, jadi kontrak ini cenderung lebih sering rusak dibanding tabel internal sebuah aplikasi.
Untuk stream event dan payload webhook, pilih perubahan yang bisa diabaikan oleh consumer lama dan dapat diadopsi consumer baru.
Aturan praktis: tambahkan field, jangan hapus atau ganti nama. Jika harus mendeprikasikan sesuatu, tetap kirim untuk sementara dan dokumentasikan sebagai deprecated.
Contoh: perluas event OrderCreated dengan menambahkan field opsional.
{
"event_type": "OrderCreated",
"order_id": "o_123",
"created_at": "2025-12-01T10:00:00Z",
"currency": "USD",
"discount_code": "WELCOME10"
}
Consumer lama membaca order_id dan created_at dan mengabaikan sisanya.
Daripada producer menebak apa yang bisa merusak pihak lain, consumer mempublikasikan apa yang mereka andalkan (field, tipe, aturan wajib/opsional). Producer lalu memvalidasi perubahan terhadap ekspektasi itu sebelum dikirim. Ini sangat berguna di codebase yang dihasilkan AI, di mana model bisa “membantu” mengganti nama field atau mengubah tipe.
Buat parser toleran:
Saat perlu perubahan breaking, gunakan tipe event baru atau nama berversi (mis. OrderCreated.v2) dan jalankan keduanya paralel sampai semua consumer bermigrasi.
Saat Anda menambahkan LLM ke sistem, output-nya cepat menjadi skema de facto—meskipun tidak ada yang menulis spesifikasi formal. Kode downstream mulai mengasumsikan “akan ada field summary,” “baris pertama adalah judul,” atau “bullet dipisah dengan dash.” Asumsi-asumsi itu mengeras, dan sedikit pergeseran perilaku model dapat merusak mereka seperti mengganti nama kolom.
Daripada mem-parsing “teks cantik,” minta output terstruktur (biasanya JSON) dan validasi sebelum masuk ke sisa sistem. Anggap ini sebagai perpindahan dari “usaha terbaik” ke kontrak.
Pendekatan praktis:
Ini terutama penting ketika respons LLM memberi makan pipeline data, otomasi, atau konten yang terlihat pengguna.
Bahkan dengan prompt yang sama, output bisa bergeser: field mungkin dihilangkan, kunci ekstra muncul, dan tipe bisa berubah ("42" vs 42, array vs string). Perlakukan ini sebagai event evolusi skema.
Mitigasi yang efektif:
Sebuah prompt adalah antarmuka. Jika Anda mengeditnya, beri versi. Simpan prompt_v1, prompt_v2, dan gulirkan bertahap (feature flag, canary, atau toggle per-tenant). Uji dengan evaluation set tetap sebelum mempromosikan perubahan, dan jalankan versi lama sampai consumer downstream beradaptasi. Untuk lebih lanjut tentang mekanik rollout yang aman, kaitkan pendekatan Anda ke /blog/safe-rollouts-expand-contract.
Perubahan skema biasanya gagal dengan cara yang membosankan dan mahal: kolom baru hilang di satu environment, consumer masih mengharapkan field lama, atau migrasi berjalan baik pada data kosong tapi time out di produksi. Pengujian mengubah “kejutan” itu menjadi pekerjaan yang dapat diprediksi dan diperbaiki.
Unit test melindungi logika lokal: fungsi mapping, serializer/deserializer, validator, dan query builder. Jika field diganti nama atau tipe berubah, unit test harus gagal dekat dengan kode yang perlu diperbarui.
Integration test memastikan aplikasi Anda tetap bekerja dengan dependensi nyata: mesin basis data yang sebenarnya, tool migrasi yang nyata, dan format pesan yang nyata. Di sini Anda menangkap isu seperti “model ORM berubah tapi migrasi tidak” atau “nama indeks baru konflik.”
End-to-end test mensimulasikan hasil pengguna atau workflow antar layanan: buat data, migrasi, baca kembali lewat API, dan verifikasi consumer downstream masih berperilaku benar.
Evolusi skema sering rusak di batas: API service-to-service, stream, queue, dan webhook. Tambahkan contract test yang berjalan di kedua sisi:
Uji migrasi seperti Anda akan menerapkannya:
Simpan seperangkat fixture kecil yang mewakili:
Fixture ini membuat regresi jelas, terutama ketika kode yang dihasilkan AI secara halus mengubah nama field, optionality, atau format.
Perubahan skema jarang gagal dengan keras tepat saat Anda deploy. Lebih sering, kerusakan muncul sebagai kenaikan bertahap parsing error, peringatan “field tak dikenal”, data hilang, atau job background tertinggal. Observability yang baik mengubah sinyal lemah itu menjadi umpan balik yang dapat ditindaklanjuti saat Anda masih bisa menghentikan rollout.
Mulai dari dasar (kesehatan app), lalu tambahkan sinyal khusus skema:
Kuncinya adalah membandingkan sebelum vs sesudah dan memotong menurut versi klien, versi skema, dan segmen trafik (canary vs stable).
Buat dua tampilan dashboard:
Dashboard perilaku aplikasi
Dashboard migrasi dan job background
Jika Anda menjalankan rollout expand/contract, sertakan panel yang menunjukkan baca/tulis dipisah menurut skema lama vs baru sehingga Anda bisa melihat kapan aman melanjutkan ke fase berikutnya.
Page untuk isu yang mengindikasikan data hilang atau terbaca salah:
Hindari alert bising pada 500 mentah tanpa konteks; kaitkan alert ke rollout skema menggunakan tag seperti versi skema dan endpoint.
Selama transisi, sertakan dan log:
X-Schema-Version, metadata pesan)Detail ini membuat pertanyaan “kenapa payload ini gagal?” bisa dijawab dalam hitungan menit, bukan hari—terutama saat layanan berbeda (atau versi model berbeda) live bersamaan.
Perubahan skema gagal dalam dua cara: perubahan itu sendiri salah, atau sistem di sekitarnya berperilaku berbeda dari yang diharapkan (terutama saat kode yang dihasilkan AI memperkenalkan asumsi halus). Bagaimanapun, setiap migrasi perlu cerita rollback sebelum dikirim—bahkan jika cerita itu eksplisit “tidak ada rollback.”
Memilih “tidak ada rollback” bisa valid ketika perubahan tidak dapat dibalik (mis. menghapus kolom, menulis ulang identifier, atau deduplikasi yang destruktif). Tapi “tidak ada rollback” bukan tanpa rencana; itu keputusan yang menggeser rencana ke arah perbaikan maju, restore, dan containment.
Feature flag / config gate: Bungkus pembaca, penulis, dan field API baru di balik flag sehingga Anda bisa mematikan perilaku baru tanpa redeploy. Ini sangat berguna ketika kode yang dihasilkan AI benar secara sintaksis tapi salah secara semantik.
Matikan dual-write: Jika Anda menulis ke skema lama dan baru selama rollout expand/contract, sediakan kill switch. Mematikan jalur tulis baru menghentikan divergensi lebih lanjut saat investigasi.
Revert pembaca (bukan hanya penulis): Banyak insiden terjadi karena consumer mulai membaca field atau tabel baru terlalu cepat. Permudah layanan untuk kembali ke versi skema sebelumnya, atau mengabaikan field baru.
Beberapa migrasi tidak bisa dibatalkan bersih:
Untuk ini, rencanakan restore dari backup, replay dari event, atau recompute dari input mentah—dan verifikasi Anda masih punya input tersebut.
Manajemen perubahan yang baik membuat rollback jarang—dan membuat recovery membosankan saat terjadi.
Jika tim Anda iterasi cepat dengan pengembangan dibantu AI, berguna untuk memadukan praktik-praktik ini dengan tooling yang mendukung eksperimen aman. Misalnya, Koder.ai menyertakan planning mode untuk desain perubahan di muka dan snapshot/rollback untuk pemulihan cepat ketika perubahan yang dihasilkan secara tidak sengaja menggeser sebuah kontrak. Digunakan bersama, code generation yang cepat dan evolusi skema yang disiplin memungkinkan Anda bergerak lebih cepat tanpa memperlakukan produksi sebagai lingkungan uji.