MariaDB Galera

Optimasi Aplikasi CodeIgniter 3 pada Infrastruktur MariaDB Galera Cluster dengan HAProxy

Beberapa waktu lalu, saya menangani salah satu klien yang menggunakan aplikasi berbasis CodeIgniter 3. Awalnya, sistem berjalan normal, namun muncul masalah serius saat terjadi lonjakan request—terutama pada operasi POST ke API. Aplikasi mengalami kegagalan, request gagal terselesaikan, dan sebagian besar error mengarah ke database.

Arsitektur Awal

Sebelum kita masuk ke solusi, mari lihat terlebih dahulu kondisi infrastruktur saat saya pertama kali menanganinya:

  • Aplikasi: CodeIgniter 3
  • Database: MariaDB dengan Galera Cluster (3 node, master-master replication)
  • Load Balancer: HAProxy untuk mendistribusikan request database ke semua node (port 3306)
  • Aplikasi Web/API: Mengakses database melalui satu endpoint (IP HAProxy) di port 3306 untuk semua query, baik read maupun write

Masalah mulai muncul ketika sistem menerima concurrent request POST dalam jumlah besar. Gejala yang saya amati:

  • Terjadi lock pada beberapa query
  • Respons API menjadi lambat
  • Sebagian besar transaksi gagal, menyebabkan error 500 di sisi aplikasi

Diagnosis Awal

Masalah utama berasal dari cara Galera Cluster bekerja. Meskipun secara teori semua node dapat menerima write karena sifatnya master-master, Galera tidak optimal saat concurrent write sangat tinggi. Galera harus menjaga konsistensi di semua node secara real-time, dan ini berakibat pada deadlock dan bottleneck ketika write query dilakukan secara bersamaan ke beberapa node.

HAProxy yang tidak membedakan antara query read dan write justru memperburuk situasi, karena semua query dibagi rata ke semua node, tanpa memedulikan jenis operasinya.

Solusi: Split Read/Write Query dengan HAProxy

Untuk mengatasi ini, saya menerapkan strategi Read-Write Splitting yang cukup sederhana namun efektif:

1. Konfigurasi HAProxy Split Port

Saya membuat dua konfigurasi frontend/backend di HAProxy:

  • Port 3306: Untuk query READ, load-balanced ke seluruh node Galera
  • Port 3307: Untuk query WRITE, diarahkan ke satu node utama saja (dengan mekanisme failover)
# Untuk Read (Load-balanced)
frontend mysql_read
    bind *:3306
    default_backend galera_read

backend galera_read
    balance roundrobin
    server db1 10.0.0.1:3306 check
    server db2 10.0.0.2:3306 check
    server db3 10.0.0.3:3306 check

# Untuk Write (Failover)
frontend mysql_write
    bind *:3307
    default_backend galera_write

backend galera_write
    balance first
    server db1 10.0.0.1:3306 check
    server db2 10.0.0.2:3306 check backup
    server db3 10.0.0.3:3306 check backup

Dengan konfigurasi ini:

  • Semua query SELECT diarahkan ke port 3306 dan dibagi ke 3 node
  • Semua query INSERTUPDATE, dan DELETE diarahkan ke port 3307 dan masuk ke node utama (misalnya db1) saja

2. Modifikasi Koneksi Database di CodeIgniter

Selanjutnya, di sisi aplikasi, saya memodifikasi konfigurasi database CodeIgniter agar koneksi dibagi menjadi dua: read (sebagai default) dan write (mengarah ke port khusus).

Contoh .env untuk pengaturan koneksi:

# .env

# READ (default)
DB_HOST=192.168.1.10
DB_PORT=3306
DB_DATABASE=app_db
DB_USERNAME=dbuser
DB_PASSWORD=dbpass

# WRITE
DB_WRITE_HOST=192.168.1.10
DB_WRITE_PORT=3307
DB_WRITE_USERNAME=dbuser
DB_WRITE_PASSWORD=dbpass

Pengaturan di application/config/database.php:

// Koneksi default (READ)
$db['default'] = array(
    'hostname' => getenv('DB_HOST') . ':' . getenv('DB_PORT'),
    'username' => getenv('DB_USERNAME'),
    'password' => getenv('DB_PASSWORD'),
    'database' => getenv('DB_DATABASE'),
    'dbdriver' => 'mysqli',
    'dbprefix' => '',
    'pconnect' => FALSE,
    'db_debug' => (ENVIRONMENT !== 'production'),
    'cache_on' => FALSE,
    'cachedir' => '',
    'char_set' => 'utf8mb4',
    'dbcollat' => 'utf8mb4_general_ci',
    'autoinit' => TRUE,
    'stricton' => FALSE,
);

// Koneksi untuk WRITE
$db['write'] = array(
    'hostname' => getenv('DB_WRITE_HOST') . ':' . getenv('DB_WRITE_PORT'),
    'username' => getenv('DB_WRITE_USERNAME'),
    'password' => getenv('DB_WRITE_PASSWORD'),
    'database' => getenv('DB_DATABASE'),
    'dbdriver' => 'mysqli',
    'dbprefix' => '',
    'pconnect' => FALSE,
    'db_debug' => (ENVIRONMENT !== 'production'),
    'cache_on' => FALSE,
    'cachedir' => '',
    'char_set' => 'utf8mb4',
    'dbcollat' => 'utf8mb4_general_ci',
    'autoinit' => TRUE,
    'stricton' => FALSE,
);

Kemudian saya buat helper sederhana atau extend model agar pemilihan koneksi bisa disesuaikan berdasarkan jenis operasi:

// contoh dalam model atau library
$this->db = $this->load->database('default', TRUE);     // untuk read (default)
$this->db_write = $this->load->database('write', TRUE); // untuk write

// contoh penggunaan
$data = $this->db->get('users')->result();            // read
$this->db_write->insert('users', $data);              // write

Hasil Uji Coba (Stress Test)

Setelah perubahan ini, saya lakukan stress test dengan tool seperti Apache Bench (ab) dan Locust. Hasilnya:

  • Tidak ada lagi lock error saat concurrent POST dilakukan, kecuali saat melewati limit kemampuan server
  • Respons time menurun drastis
  • Konsistensi data tetap terjaga
  • Load pada cluster lebih stabil

Kesimpulan

Strategi split read/write sederhana ini membawa dampak besar terhadap performa aplikasi, terutama pada sistem berbasis Galera Cluster yang cukup sensitif terhadap write-concurrent tinggi. Ini juga menjadi catatan penting bahwa:

Master-Master replication bukan berarti Write-Everywhere.

Selalu lakukan evaluasi arsitektur dan beban aplikasi sebelum menerapkan skema replikasi tertentu. Kombinasi HAProxy dengan port dan policy terpisah bisa jadi solusi elegan untuk menjaga performa tanpa mengubah struktur data atau engine database secara drastis.


Semoga pengalaman ini bermanfaat untuk rekan-rekan yang sedang bergelut dengan aplikasi monolitik atau ingin mengoptimasi CodeIgniter 3 dalam lingkungan production yang kompleks.

Cheers!

Leave a Reply