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
INSERT
,UPDATE
, danDELETE
diarahkan ke port 3307 dan masuk ke node utama (misalnyadb1
) 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!