L
Git • Literally Web ID

Manajemen Migrasi Skema Zero-Downtime Menggunakan Git dan Pipeline CI/CD

⏱️ 18 menit baca • 📅 14 Oct 2026 • 👁️ 1 views
Manajemen Migrasi Skema Zero-Downtime Menggunakan Git dan Pipeline CI/CD

1. Konteks Industri & Analisis Akar Masalah

Di era modern, ekspektasi pengguna terhadap aplikasi web sangatlah tinggi: layanan harus tersedia 24/7 tanpa ada maintenance window. Namun, evolusi produk menuntut perubahan struktur basis data secara berkala. Tantangan terbesar bagi seorang Principal Web Architect dan DevOps Engineer adalah mengeksekusi migrasi skema database relasional (seperti MySQL/MariaDB) di lingkungan produksi berskala besar tanpa memicu downtime, degradasi performa, atau inkonsistensi data antara aplikasi dan basis data.

Secara tradisional, perubahan skema database dilakukan melalui prosedur manual yang rentan human error selama jam-jam sepi (pukul 02:00 dini hari). Pendekatan ini memiliki bottleneck yang parah: perintah SQL seperti ALTER TABLE pada tabel dengan jutaan baris akan mengunci tabel (table lock), memblokir seluruh query Read/Write, menghabiskan memori RAM, membebani CPU, dan pada akhirnya menyebabkan kegagalan koneksi (connection timeout) pada aplikasi backend berbasis PHP-FPM, Node.js, atau Go.

Perbandingan kondisi Sebelum vs Sesudah penerapan Zero-Downtime Migration:

Aspek Sebelum (Tradisional/Manual) Sesudah (Git + CI/CD + Backward Compatibility)
Ketersediaan Layanan Downtime berkisar dari beberapa detik hingga jam. 100% uptime (Zero-Downtime).
Metode Deployment Eksekusi script SQL langsung di server produksi. Otomatisasi pipeline CI/CD dengan strategi Expand & Contract (Phase-based).
Risiko Kegagalan Tinggi, rollback manual rumit dan memakan waktu. Rendah, otomatisasi rollback dan skema backward compatible.
Dampak Performa Lonjakan beban CPU/I/O, deadlock, tabel terkunci. Terdistribusi, minim dampak pada query transaksional aktif.

2. Konsep Inti & Deep Dive Arsitektur

Untuk mencapai zero-downtime, kita tidak bisa mengubah tabel secara langsung dalam satu langkah instan. Kita harus mengadopsi pola Expand and Contract Pattern (atau dikenal sebagai Parallel Run). Pola ini memecah perubahan skema menjadi beberapa fase rilis terisolasi yang masing-masing tetap kompatibel dengan kode aplikasi lama maupun baru.

Berikut adalah ilustrasi alur arsitektur eksekusi migrasi skema berbasis Git dan pipeline CI/CD:

[ Git Repository: main branch ]
             │
             ▼ (Trigger Pipeline CI/CD)
┌────────────────────────────────────────┐
│           CI/CD Runner                 │
├────────────────────────────────────────┤
│ 1. Lint & Test Code                    │
│ 2. Phase 1: Expand (Add new columns)   │
│ 3. Phase 2: Dual Write (App update)    │
│ 4. Phase 3: Data Backfill / Migration  │
│ 5. Phase 4: Contract (Drop old schema) │
└────────────────────────────────────────┘
             │
             ▼ (Zero-Downtime Deployment)
┌────────────────────────────────────────┐
│ Load Balancer (Cloudflare / Nginx)     │
├────────────────────────────────────────┤
│ Web Nodes (Go/Node/PHP-FPM)            │
├────────────────────────────────────────┤
│ Database Cluster (MySQL / MariaDB)     │
└────────────────────────────────────────┘

Alur Eksekusi Migrasi 4 Fase:

  1. Fase Expand (Perluasan): Tambahkan kolom atau tabel baru yang bersifat opsional (NULL atau memiliki default value yang aman). Kode aplikasi lama tidak akan terganggu karena mengabaikan kolom baru tersebut.
  2. Fase Dual Write (Penulisan Ganda): Rilis versi aplikasi baru yang menulis data ke kolom lama sekaligus kolom baru secara bersamaan. Pembacaan (read) masih mengandalkan kolom lama.
  3. Fase Backfill (Migrasi Data Historis): Jalankan proses latar belakang (menggunakan queue worker atau script batching) untuk menyalin data lama ke struktur baru tanpa mengunci tabel secara penuh.
  4. Fase Contract (Penyusutan): Setelah seluruh aplikasi menggunakan struktur baru dan data historis telah disinkronkan, rilis versi kode final yang menghapus ketergantungan pada kolom lama, lalu hapus kolom lama tersebut.

3. Perbandingan Komparatif / Analisis Pendekatan

Dalam membangun pipeline migrasi, terdapat beberapa pendekatan eksekusi database migration yang sering dipertimbangkan oleh arsitek infrastruktur:

Fitur / Metrik Direct ALTER TABLE (Naif) Online DDL (pt-online-schema-change) Expand & Contract via CI/CD
Locking Type Exclusive Metadata Lock / Table Lock Minimal (hanya saat trigger dibuat/dihapus) Tidak ada lock struktural yang memblokir
Konsumsi CPU/RAM Rendah saat eksekusi, tapi melonjak tajam jika gagal Sedang (menyalin baris per baris ke tabel bayangan) Rendah hingga Sedang (terdistribusi dalam siklus rilis)
Kompleksitas Kode Sangat Rendah Sedang Tinggi (membutuhkan beberapa kali rilis)
Keamanan Data Buruk (risiko korupsi data tinggi) Baik Sangat Baik (dapat di-rollback dengan mudah)
Cocok untuk Tabel > 10M Baris Tidak Disarankan Sangat Direkomendasikan Sangat Direkomendasikan

[!WARNING] Jangan pernah menjalankan perintah ALTER TABLE table_name ADD COLUMN secara langsung pada tabel produksi yang memiliki jutaan baris tanpa menggunakan alat bantu Online DDL atau pola Expand & Contract. Tindakan ini akan menghentikan seluruh transaksi Write aplikasi secara instan.


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum mengonfigurasi pipeline CI/CD dan repositori Git, pastikan lingkungan infrastruktur Anda memenuhi spesifikasi teknis berikut:

  • Database Engine: MySQL 8.0+ atau MariaDB 10.5+ (mendukung Instant Add Column untuk beberapa operasi skema).
  • Git Repository: GitHub / GitLab dengan proteksi branch (main / production).
  • CI/CD Runner: GitLab Runner atau GitHub Actions Runner yang memiliki akses jaringan privat ke database produksi.
  • Web Stack: Nginx + PHP-FPM / Go Binary / Node.js PM2 di balik Reverse Proxy dengan arsitektur rolling update.
  • Dependensi CLI: git, make, jq, migrator tool (seperti golang-migrate, flyway, atau ORM migration bawaan bahasa pemrograman).

Verifikasi koneksi database dan hak akses (privileges) user migrasi:

-- Pastikan user migrasi memiliki hak akses yang cukup namun tetap dibatasi
GRANT ALTER, CREATE, DROP, INDEX, REFERENCES ON production_db.* TO 'migration_user'@'10.0..%';
FLUSH PRIVILEGES;

5. Panduan Implementasi Langkah demi Langkah

Langkah 1: Konfigurasi Skema Backward Compatible (Contoh: Mengubah kolom name menjadi first_name dan last_name)

Alih-alih langsung menghapus kolom name, kita buat kolom baru terlebih dahulu.

-- File migrasi: 000001_expand_user_names.up.sql
ALTER TABLE users 
ADD COLUMN first_name VARCHAR(100) DEFAULT '' AFTER id,
ADD COLUMN last_name VARCHAR(100) DEFAULT '' AFTER first_name;

Langkah 2: Implementasi Dual Write di Aplikasi Backend (Go / Node.js)

Berikut adalah contoh implementasi kode aplikasi di backend Go yang menulis ke kolom lama dan baru secara bersamaan selama masa transisi:

package repository

import (
	"context"
	"database/sql"
)

type UserRepository struct {
db *sql.DB
}

func NewUserRepository(db *sql.DB) *UserRepository {
	return &UserRepository{db: db}
}

// UpdateUser menangani dual-write pada fase transisi skema
func (r *UserRepository) UpdateUser(ctx context.Context, id int64, firstName, lastName string) error {
	// Kolom lama 'name' dipertahankan untuk kompatibilitas mundur dengan aplikasi versi lama
	legacyName := firstName + " " + lastName

	query := `
		UPDATE users 
		SET name = ?, first_name = ?, last_name = ? 
		WHERE id = ?
	`
	
	_, err := r.db.ExecContext(ctx, query, legacyName, firstName, lastName, id)
	return err
}

Langkah 3: Konfigurasi Pipeline CI/CD (Contoh: GitHub Actions untuk Migrasi Otomatis)

Buat file .github/workflows/deploy-migration.yml di repository Git Anda:

name: Production Zero-Downtime Migration & Deploy

on:
  push:
    branches:
      - main

jobs:
  db-migration:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Set up Go Migration Tool
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Run Database Migrations (Expand Phase)
        env:
          DB_HOST: ${{ secrets.PROD_DB_HOST }}
          DB_PORT: ${{ secrets.PROD_DB_PORT }}
          DB_USER: ${{ secrets.PROD_DB_USER }}
          DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
          DB_NAME: ${{ secrets.PROD_DB_NAME }}
        run: |
          go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
          migrate -path ./db/migrations -database "mysql://${DB_USER}:${DB_PASSWORD}@tcp(${DB_HOST}:${DB_PORT})/${DB_NAME}" up

  deploy-web:
    needs: db-migration
    runs-on: ubuntu-latest
    steps:
      - name: Trigger Rolling Update via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.PROD_WEB_HOST }}
          username: ${{ secrets.PROD_SSH_USER }}
          key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/production-app
            git pull origin main
            go build -o app main.go
            systemctl restart app-backend
            nginx -s reload

6. Bedah Kode & Penjelasan Detail Baris Kunci

  • DEFAULT '' pada ALTER TABLE: Memastikan bahwa baris data yang sudah ada tidak menghasilkan error NULL violation saat kolom baru ditambahkan, menjaga integritas data tanpa mengunci tabel secara eksklusif dalam waktu lama.
  • Dual-Write di Layer Aplikasi (UpdateUser): Baris kode yang menulis ke name, first_name, dan last_name sekaligus adalah kunci dari backward compatibility. Jika terjadi rollback mendadak pada aplikasi, data tetap valid di kolom lama.
  • Pemisahan Job db-migration dan deploy-web di CI/CD: Memastikan bahwa struktur database telah diperbarui dan siap menerima beban sebelum instance aplikasi baru di-deploy ke server produksi.

7. Skenario Produksi & Studi Kasus Dunia Nyata

Studi Kasus: Sebuah platform e-commerce dengan 50.000 requests per second (RPS) harus memigrasikan tabel orders yang berisi 80 juta baris untuk mengubah tipe data kolom total_amount dari INT ke DECIMAL(10,2) guna mendukung presisi mata uang.

Solusi Arsitektural:

  1. Expand: Tambahkan kolom baru total_amount_decimal DECIMAL(10,2).
  2. Backfill Batching: Jalankan script Go berkecepatan tinggi yang membaca data dalam potongan kecil (chunk sebanyak 1.000 baris per iterasi) dengan jeda time.Sleep(50 * time.Millisecond) di antara setiap batch untuk menghindari lonjakan penggunaan I/O disk dan menjaga replication lag pada MySQL Master-Replica tetap di bawah 0.5 detik.
  3. Switch & Contract: Setelah sinkronisasi selesai, arahkan pembacaan ke kolom baru, lalu hapus kolom lama pada siklus rilis berikutnya.

8. Common Pitfalls, Anti-Patterns & Solusi

  • Anti-Pattern 1: Menggabungkan ALTER TABLE dan Deploy Aplikasi dalam satu commit instan.
    • Solusi: Pecah menjadi minimal dua atau tiga PR/MR terpisah (Expand -> Update App -> Contract).
  • Anti-Pattern 2: Menggunakan RENAME COLUMN secara langsung saat aplikasi lama masih berjalan.
    • Solusi: Jangan pernah mengubah nama kolom secara langsung. Gunakan teknik Add new -> Dual write -> Copy -> Drop old.
  • Pitfall 3: Mengabaikan Index pada Kolom Baru.
    • Solusi: Selalu buat indeks secara asinkron atau pastikan penambahan indeks dilakukan setelah data terisi penuh agar tidak memblokir operasi Write.

9. Benchmark, Metrik Kinerja & Tuning Performa

Untuk menguji ketahanan sistem migrasi terhadap beban tinggi, gunakan tool load testing seperti k6 atau wrk.

Contoh script pengujian k6 untuk memonitor latensi API selama proses migrasi berjalan:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 1000 }, // Ramp up ke 1000 virtual users
    { duration: '3m', target: 1000 }, // Sustained load
    { duration: '1m', target: 0 },    // Ramp down
  ],
};

export default function () {
  const res = http.get('https://api.production-domain.com/users/1');
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 200ms': (r) => r.timings.duration < 200,
  });
  sleep(0.1);
}

Interpretasi Metrik: Pastikan p99 latency tetap di bawah batas aman (misal < 200ms) dan tingkat error rate tetap di angka 0.00% selama eksekusi pipeline CI/CD.


10. Checklist Penerapan Produksi & Kesimpulan

Sebelum merilis migrasi skema database ke lingkungan produksi, pastikan Anda telah memeriksa daftar perintah dan kondisi berikut:

  • Skema database dirancang dengan prinsip Backward Compatibility (Expand & Contract).
  • Tidak ada perintah ALTER TABLE destruktif langsung pada tabel berukuran besar.
  • Pipeline CI/CD memisahkan tahap migrasi database dan rolling deploy aplikasi.
  • Skrip backfill data historis dirancang dengan mekanisme chunking dan penundaan (rate-limiting).
  • Backup database penuh (full snapshot) telah diambil sebelum pipeline dijalankan.
  • Monitoring metrik CPU, I/O, dan replication lag aktif selama proses rilis.

Dengan menerapkan strategi terstruktur ini, tim engineering Anda dapat melakukan pembaruan skema database yang kompleks secara rutin tanpa mengorbankan ketersediaan layanan dan kenyamanan pengguna akhir.

🏷️ Tags: #Backend Architecture #Devops #Database Migration #Ci/Cd #Git