Lewati ke isi

06 β€” Strategi Migrasi: Big-Bang Cutover

Dibaca oleh: tim migrasi (DB + backend). Tujuan: memindahkan data 10 tahun dari MariaDB lama ke PostgreSQL baru sekali jalan pada jendela downtime terjadwal, dengan aman & bisa diaudit.

Keputusan pemilik: big-bang cutover. Big-bang itu sah, tetapi untuk data akuntansi 10 tahun ia hanya aman jika dilatih berkali-kali (dry-run) dan direkonsiliasi 100% sebelum hari-H. Dokumen ini menjadikan itu wajib.

Aturan emas: Tidak ada rekonsiliasi 100%, tidak ada cutover. Data akuntansi tidak mengenal "nanti diperbaiki sambil jalan".


1. Gambaran Fase

FASE 0  Persiapan & inventaris        (paham total dulu)
FASE 1  Bangun skema + logika baru    (Dokumen 03 & 05 jadi skrip nyata)
FASE 2  Bangun pipeline ETL           (lama β†’ baru, dengan aturan transformasi)
FASE 3  Dry-run berulang + rekonsiliasi (latihan sampai 100% cocok)
FASE 4  Pembekuan & Cutover (Hari-H)  (eksekusi + verifikasi + buka sistem baru)
FASE 5  Stabilisasi & rollback-ready  (jaring pengaman pasca-cutover)

2. FASE 0 β€” Persiapan & Inventaris

Selesaikan Tindak Lanjut Dokumen 01 Β§7 dulu. Tanpa ini, estimasi & migrasi buta.

Checklist: - [~] Inventaris Β±150 prosedur: data awal read-only tersedia di database/etl/output/routines.csv (154 routine); masih perlu klasifikasi dipakai / tidak / duplikat. - [~] Inventaris view (Dokumen 01 Β§5): data awal read-only tersedia di database/etl/output/views.csv (63 view); masih perlu klasifikasi mana dipakai vs mati. Konfirmasi tabel sumber (brg/ktk/t/d/s) memang tunggal β€” bukan konsolidasi tabel kembar. - [ ] Petakan semua arti kolom cek (angka 7/56/66/...). - [ ] Daftar laporan aktif (dari 63 view + prosedur periodic*/omset*/neraca*). - [ ] Tetapkan tanggal pisah buku (cut-off akuntansi) & jendela downtime (idealnya saat tutup buku bulanan, beban transaksi minimum). - [ ] Siapkan server PostgreSQL (uji & produksi), backup MariaDB penuh terbukti bisa di-restore.

Keluaran FASE 0: Kamus Data (Dokumen 02) lengkap + daftar fitur yang harus tetap jalan.


3. FASE 1 β€” Bangun Skema & Logika Baru

  • [ ] DDL Dokumen 03 β†’ skrip migrasi bernomor (V001__skema_sql, dst.).
  • [ ] Logika Dokumen 05 β†’ fungsi/trigger PL/pgSQL, tiap fungsi COMMENT ON.
  • [ ] Bangun di database erp: schema pusat/logika/logs + template tenant (Dokumen 04).
  • [ ] Unit test logika dengan data buatan kecil: 1 PB β†’ cek lapisan FIFO; 1 PJ β†’ cek HPP, stok, jurnal imbang; RJ/RB; PL (pemindahan); OS (opname).
  • [x] Sediakan utilitas stok: logika.bangun_ulang_stok() β€” idempoten, untuk dipakai di akhir ETL. Util jurnal massal ditunda sampai sub-fungsi jurnal tidak lagi stub, supaya tidak menghapus jurnal hasil ETL.

4. FASE 2 β€” Pipeline ETL (Extract β†’ Transform β†’ Load)

Alat

  • Ekstraksi/skema: pgloader baik untuk pemindahan kasar MySQLβ†’PG, tetapi data ini butuh transformasi (rename, ganti tipe data, pemetaan ID). Rekomendasi:
  • pgloader hanya untuk staging tabel (salin apa adanya ke skema lama_raw). View tidak ikut (tidak menyimpan data) β€” definisinya ditulis ulang terpisah.
  • Transformasi nyata pakai SQL INSERT ... SELECT dari lama_raw β†’ skema baru. Ini bisa di-review, dites, diulang.
MariaDB  ──pgloader──►  PG skema `lama_raw` (mentah, apa adanya)
                                β”‚  SQL transformasi (di Git, bernomor)
                                β”œβ”€β”€β–Ί `pusat.wilayah_*` untuk referensi global
                                β–Ό
                        PG schema `tenant_<kode>` untuk data tenant (Dokumen 03/04)

Aturan transformasi (wajib didokumentasikan per tabel)

Aturan Contoh
Rename tabel/kolom brg→barang, kdtrans→jenis_kode (pakai Kamus Dokumen 02)
tinyint(1) β†’ boolean nostok 1/0 β†’ tanpa_stok true/false
Kode akun decimal→int 110.01 × 100 → 11001 (normalisasi di staging stg_rekening.kode)
PK d string β†’ identity buang idtrans.idbarang; petakan refid lama β†’ ref_detail_id baru via tabel pemetaan
ID lama β†’ ID baru buat tabel map_transaksi(old_id, new_id) & map_detail(...) agar relasi tetap nyambung
Migrasi tabel, bukan view hanya BASE TABLE di-ETL; semua VIEW di-skip lalu definisinya di-port terpisah ke PostgreSQL
Referensi global wilayah_* dimuat sekali ke pusat.wilayah_*, bukan per schema tenant
Skip tabel kerja temp_*/*_draft/*_temp tidak dimigrasi sebagai histori
Bersihkan data kotor baris yatim (FK menggantung), tanggal invalid β†’ laporkan & putuskan (perbaiki / kecualikan)
Buang temp_*/*_draft/*lite tidak dimigrasi (data kerja, bukan histori)

Pemetaan ID adalah bagian paling rawan. Karena PK lama dibuat manual dan PK baru IDENTITY, simpan tabel map_* (old→new) dan terjemahkan semua FK lewat tabel itu. Jangan memaksakan ID lama ke IDENTITY.

Urutan muat (hormati dependensi FK)

0. referensi pusat         : wilayah_provinsi/kabupaten/kecamatan/desa
1. master tanpa dependensi : akun, klien, lokasi, divisi, satuan,
                             barang_golongan/kategori/jenis/merk
2. master berdependensi    : barang, kontak, jenis_transaksi, termin, pengguna
3. transaksi (header)      : transaksi            β†’ isi map_transaksi
4. transaksi_detail        : pakai map_transaksi  β†’ isi map_detail
5. turunan                 : fifo_lapisan, fifo_konsumsi, jurnal, kas
6. cache                   : stok  (lebih baik: bangun ulang via fungsi, lihat Β§5)

Strategi logika saat muat (penting)

Karena trigger-first, memuat 850rb detail dengan trigger aktif = sangat lambat & bisa salah urut FIFO. Pendekatan:

  1. Nonaktifkan trigger saat memuat data historis (ALTER TABLE ... DISABLE TRIGGER USER).
  2. Muat data apa adanya (termasuk hpp, mutasi_stok hasil hitungan lama β€” histori dipercaya apa adanya, jangan dihitung ulang dari nol untuk masa lalu).
  3. Bangun ulang turunan secara terkontrol di akhir:
  4. logika.bangun_ulang_stok() β†’ isi stok dari SUM(mutasi_stok).
  5. Rekonstruksi fifo_lapisan/fifo_konsumsi dari histori (urut tanggal) atau migrasi tabel fifo lama apa adanya lalu validasi (lihat keputusan Β§6).
  6. Aktifkan kembali trigger β†’ mulai hari-H semua transaksi BARU lewat logika baru.

Keputusan yang harus diambil pemilik+DBE di FASE 0: untuk histori, migrasi fifo lama apa adanya (lebih aman, angka HPP historis tak berubah) vs rekonstruksi FIFO (lebih bersih tapi bisa menggeser HPP lama). Default rekomendasi: migrasi apa adanya untuk masa lalu; logika FIFO baru hanya berlaku untuk transaksi sejak cutover.


5. FASE 3 β€” Dry-Run Berulang + Rekonsiliasi

Jalankan seluruh pipeline di server uji, berulang sampai lolos 100%. Setiap dry-run dicatat (tanggal, durasi, temuan, perbaikan).

Wajib cocok (rekonsiliasi) β€” lama vs baru

Pemeriksaan Cara
Jumlah baris per entitas COUNT(*) transaksi, detail, jurnal, barang, kontak β€” sama (kecuali yang sengaja dibuang, terdokumentasi)
Saldo per akun SUM(debitβˆ’kredit) per akun = sama
Total piutang & hutang per kontak = sama
Stok akhir per barang/lokasi = sama (toleransi 0)
Total HPP per periode = sama (atau selisih terjelaskan oleh keputusan FIFO Β§4)
Neraca & Rugi/Laba per periode nilai akhir = sama
Jurnal imbang setiap transaksi: SUM(debit)=SUM(kredit) (di baru harus 0 pelanggaran)
Spot-check dokumen ambil 30 nota acak (PJ/PB/RJ/PL/OS) β†’ bandingkan baris per baris

Sediakan skrip rekonsiliasi otomatis yang mengeluarkan satu laporan "LULUS/GAGAL" + daftar selisih. Cutover hanya jika LULUS total.

Ukur durasi

Catat berapa lama pipeline berjalan di volume nyata (β‰ˆ196rb/852rb/590rb baris). Durasi ini menentukan panjang jendela downtime hari-H. Tambah margin 2Γ—.


6. FASE 4 β€” Hari-H (Cutover)

Runbook (jam-J). Setiap langkah punya penanggung jawab & checkbox:

T-1 hari : pengumuman downtime ke semua user; freeze fitur baru; backup penuh terjadwal
T0       : 1. Tutup akses semua aplikasi (Delphi/Flutter/Web) ke sistem lama
           2. Backup final MariaDB (verifikasi backup bisa dibaca)
           3. Ekstrak data final β†’ jalankan pipeline ETL ke PostgreSQL
           4. Jalankan bangun_ulang_stok() + verifikasi jurnal imbang
           5. Jalankan SKRIP REKONSILIASI otomatis
           6. GERBANG KEPUTUSAN:
                 - LULUS 100%  β†’ lanjut langkah 7
                 - GAGAL       β†’ JALANKAN ROLLBACK (Β§7), batalkan cutover
           7. Arahkan backend/aplikasi ke PostgreSQL (ganti koneksi/env)
           8. Uji asap di produksi: login β†’ pilih tenant β†’ buat 1 transaksi
              uji tiap jenis kritis (PJ/PB/RJ/PL/OS) β†’ cek stok, HPP, jurnal β†’ hapus
           9. Buka akses user (mulai bertahap: tim internal dulu, lalu semua)
T+0..72j : pemantauan ketat (lihat Β§8)

Aturan: langkah 6 adalah gerbang tak bisa ditawar. Pewenang go/no-go ditetapkan di muka (pemilik atau yang ditunjuk).


7. Rencana Rollback (wajib ada sebelum hari-H)

Big-bang tanpa rollback = judi. Karena selama cutover sistem lama hanya dibekukan (read-only), tidak dihapus, rollback = kembali memakainya.

Pemicu rollback : rekonsiliasi GAGAL, atau cacat fatal ditemukan < T+X jam
Langkah:
  1. Tutup akses ke sistem baru
  2. Buka kembali akses aplikasi ke MariaDB lama (yang masih utuh & beku)
  3. Karena belum ada transaksi baru di sistem lama selama beku β†’ tak ada data
     yang perlu direkonsiliasi balik
  4. Umumkan ke user; jadwalkan dry-run/cutover ulang setelah akar masalah dibereskan

Syarat agar rollback aman: selama jendela cutover, TIDAK ADA transaksi nyata di sistem baru sebelum gerbang LULUS dilewati. Itu sebabnya uji asap (langkah 8) memakai transaksi dummy yang langsung dihapus.

Batas waktu rollback: tetapkan "titik tak bisa balik" (mis. T+24 jam setelah user mulai transaksi nyata di sistem baru). Lewat itu, perbaikan = maju (fix-forward), bukan mundur. Komunikasikan ini ke pemilik di muka.


8. FASE 5 β€” Stabilisasi

  • Pantau log_error, performa query, dan jurnal tidak imbang (harus 0) 72 jam.
  • Sediakan view tenant v_jurnal_tidak_imbang & v_stok_negatif sebagai alarm harian.
  • Catat semua keluhan user β†’ triase: bug migrasi vs bug fitur vs salah paham.
  • Backup PostgreSQL harian + uji restore mingguan sejak hari-1.
  • Setelah 2–4 minggu stabil & rekonsiliasi periodik aman β†’ arsipkan MariaDB lama (jangan dihapus; simpan sebagai arsip read-only minimal 1 tahun untuk audit).

9. Risiko Spesifik Big-Bang & Mitigasi

Risiko Mitigasi
Jendela downtime tak cukup (ETL kelamaan) Ukur durasi di FASE 3, margin 2Γ—, optimalkan muat (COPY, index dibuat setelah muat)
Rekonsiliasi gagal di hari-H Sudah dilatih berkali-kali; gerbang go/no-go + rollback siap
Fitur tersembunyi hilang (prosedur tak ter-port) Lampiran Dokumen 05 Β§9 wajib penuh sebelum FASE 4
Data 10 tahun kotor Dibersihkan & didokumentasikan di FASE 2; keputusan baris bermasalah dicatat
Pemetaan ID/relasi putus Tabel map_* + uji integritas FK 0 yatim sebelum LULUS
HPP historis bergeser Keputusan "migrasi FIFO apa adanya untuk masa lalu" (Β§4)

Lampiran A β€” Perintah Bantu Inventaris (READ-ONLY, aman)

Jalankan di koneksi read-only ke MariaDB lama. Untuk memahami, bukan mengubah.

Daftar trigger:

SELECT EVENT_OBJECT_TABLE, ACTION_TIMING, EVENT_MANIPULATION, TRIGGER_NAME
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA='u1566482_sparepart'
ORDER BY EVENT_OBJECT_TABLE, ACTION_TIMING;

Daftar prosedur/fungsi + ukuran (untuk prioritas port):

SELECT ROUTINE_TYPE, ROUTINE_NAME, CHAR_LENGTH(ROUTINE_DEFINITION) AS panjang
FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA='u1566482_sparepart'
ORDER BY panjang DESC;

Pisahkan TABEL vs VIEW (klarifikasi: yang di-ETL hanya BASE TABLE):

SELECT TABLE_TYPE, COUNT(*) FROM information_schema.TABLES
WHERE TABLE_SCHEMA='u1566482_sparepart' GROUP BY TABLE_TYPE;
-- Hasil acuan: BASE TABLE = 120, VIEW = 63

Tabel dasar terbesar (prioritas ETL & uji performa):

SELECT TABLE_NAME, TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA='u1566482_sparepart' AND TABLE_TYPE='BASE TABLE'
ORDER BY TABLE_ROWS DESC LIMIT 30;

Daftar view + definisinya (untuk port ke PostgreSQL, bukan ETL):

SELECT TABLE_NAME FROM information_schema.VIEWS
WHERE TABLE_SCHEMA='u1566482_sparepart' ORDER BY TABLE_NAME;
-- lalu: SHOW CREATE VIEW <nama>;  β†’ tulis ulang ke sintaks PostgreSQL bila masih dipakai

Cek jurnal tidak imbang di sistem LAMA (baseline kualitas data):

SELECT idtrans, SUM(debit) d, SUM(kredit) k
FROM j GROUP BY idtrans HAVING ROUND(SUM(debit),2) <> ROUND(SUM(kredit),2);

Hasil baseline ini penting: jika sistem lama sendiri sudah punya jurnal tak imbang, putuskan di FASE 0 apakah diperbaiki dulu atau dimigrasi apa adanya dengan catatan. Jangan sampai sistem baru "disalahkan" atas data lama yang sudah bermasalah.