Panduan ini gratis dan boleh Anda kerjakan sendiri. Kalau tidak sempat, kami bisa menanganinya — remote.
Untuk siapa: admin/IT rumah sakit & klinik yang mengelola server SIMGos, tanpa harus jadi ahli database atau sysadmin.
Isi: cara menyetel 4 layanan yang menentukan cepat-lambatnya SIMGos:
| Layanan | Perannya di SIMGos |
|---|---|
| Apache (httpd) | Pintu depan. Menerima semua permintaan dari browser petugas. |
| PHP-FPM | Dapur. Menjalankan kode SIMGos (webservice). |
| MySQL (mysqld) | Gudang data. Semua data pasien, tindakan, obat, tagihan. |
| Tomcat | Tukang cetak. Membuat PDF laporan/berkas via JasperReports. |
Semua angka dan "jebakan" di panduan ini berasal dari kasus nyata di server yang kita tangani (4 rumah sakit dan 4 klinik pengguna SIMGos di Indonesia — nama fasilitas disamarkan). Bagian Jebakan di akhir adalah yang paling berharga — semuanya pernah bikin sistem down.
💡 Tidak ingin menghitung manual? Gunakan Kalkulator Tuning Server — masukkan spesifikasi server Anda, dapatkan seluruh file konfigurasi siap salin. Semua perhitungan berjalan di browser Anda.
Semua layanan dalam satu server — cari baris RAM Anda, salin angkanya, lalu buka bagian 10.2 untuk file config lengkapnya.
| RAM / vCPU | Pengguna bersamaan | Apache<br>ServerLimit / MaxRequestWorkers | PHP-FPM<br>pm.max_children | MySQL<br>buffer_pool / max_connections | Tomcat<br>-Xmx |
|---|---|---|---|---|---|
| 8 GB / 4 vCPU | < 25 | 4 / 100 | 25 | 2G / 80 | 512m |
| 16 GB / 4 vCPU | < 50 | 6 / 150 | 50 | 5G / 100 | 1G |
| 32 GB / 8 vCPU | < 100 | 8 / 200 | 100 | 10G / 200 | 2G |
| 64 GB / 8–16 vCPU | < 250 | 16 / 400 | 250 | 24G / 300 | 4G |
| 96–128 GB / 8–16 vCPU | 250+ | 32 / 800 | 600 | 48G / 700 | 6G |
Kalau layanan dipisah ke server sendiri:
| Peran server | RAM | Angka kuncinya | Detail |
|---|---|---|---|
| Server aplikasi (httpd+php-fpm+redis) | 8 / 16 / 32 GB | Apache 200/400/800 · fpm 150/350/700 · Redis wajib | 10.4 |
| Server database (mysqld saja) | 16 / 32 / 64 / 128 GB | buffer_pool 10G/20G/40G/80G · max_conn = Σ max_children semua server app + 50 | 10.5 |
| Server laporan (tomcat+jasper) | 8 / 16 / 24 GB | Tomcat Xmx 4G/8G/12G · maxThreads 25/40/50 · request_terminate_timeout 600s | 10.6 |
Tiga aturan yang mengikat semuanya:
Apache MaxRequestWorkers ≈ pengguna bersamaan × 2 (dan HARUS = ServerLimit × 25)
PHP-FPM pm.max_children ≈ MaxRequestWorkers × 0,5
MySQL max_connections = pm.max_children + 50
Baca sekali, lalu boleh dilewati.
| Istilah | Artinya, sederhananya |
|---|---|
| Proses / worker / child | Satu "pegawai" yang mengerjakan satu permintaan. Kalau semua pegawai sibuk, permintaan baru harus antre. |
| Thread | Sama seperti pegawai, tapi lebih ringan — beberapa thread berbagi satu meja (proses). |
| Concurrency | Berapa banyak permintaan yang ditangani pada saat yang sama. Bukan total per hari. |
| Antrean (queue) | Permintaan yang menunggu giliran karena semua pegawai sibuk. Ini yang dirasakan user sebagai "lemot". |
| Timeout | Batas waktu menunggu. Lewat itu, dibatalkan. |
| Cache | Salinan sementara yang disimpan supaya tidak perlu ambil ulang dari sumber lambat. |
| Daemon / service | Program yang jalan terus di latar belakang (httpd, php-fpm, mysqld, tomcat). |
| Restart vs Reload | Reload = ganti setelan tanpa memutus pekerjaan yang sedang jalan (aman jam kerja). Restart = matikan lalu nyalakan; semua yang sedang jalan terputus (lakukan di jam sepi). |
| Drop-in config | File setelan tambahan terpisah (mis. /etc/my.cnf.d/simgos.cnf) yang menimpa setelan bawaan, tanpa mengubah file asli. Lebih aman karena update paket tidak menghapusnya. |
| Istilah | Artinya, sederhananya |
|---|---|
| RAM | Meja kerja server. Cepat tapi terbatas. |
| Swap | Meja cadangan di harddisk. Sangat lambat. Kalau server mulai pakai swap banyak, artinya RAM kurang. |
| RSS | Angka memori yang muncul di top. Menipu — memori yang dipakai bersama-sama dihitung berkali-kali. |
| USS | Memori yang benar-benar milik sendiri satu proses. Ini yang dipakai untuk menghitung "kalau saya tambah 1 pegawai lagi, butuh RAM berapa?" |
| PSS | Di antara RSS dan USS (memori bersama dibagi rata). |
| OOM Killer | Satpam kernel Linux. Kalau RAM habis, dia mematikan paksa proses terbesar — biasanya MySQL. Server "mati sendiri" tanpa jejak error aplikasi. |
Aturan emas: hitung kebutuhan RAM pakai USS, jangan RSS. Kita pernah salah hitung 4× lipat di RS-2 gara-gara ini.
| Istilah | Artinya, sederhananya |
|---|---|
| InnoDB | Mesin penyimpan data MySQL yang dipakai SIMGos. |
| Buffer pool | Ruang di RAM tempat MySQL menyimpan data yang sering dibaca. Setelan MySQL paling berpengaruh. Kalau kekecilan, MySQL bolak-balik baca harddisk = lambat. |
| Connection | Satu saluran telepon antara PHP dan MySQL. Kalau semua saluran terpakai → error Too many connections. |
| Index | Daftar isi buku. Tanpa index, MySQL membaca seluruh tabel dari awal. |
| Slow query log | Catatan query yang lama. Alat diagnosa utama. |
| Stored procedure | Program kecil yang tersimpan di dalam database. SIMGos memakainya banyak untuk laporan (mis. CetakMR2). |
| Trigger | Aksi otomatis yang jalan setiap kali data disimpan/diubah. SIMGos + SATUSEHAT punya puluhan. Beban tersembunyi. |
| Table lock / FLUSH TABLES | Perintah "semua tutup dulu, saya mau kunci sebentar". Kalau ada query yang jalan berjam-jam, perintah ini macet menunggu — dan backup gagal. |
| Istilah | Artinya, sederhananya |
|---|---|
| JVM | Mesin yang menjalankan program Java (Tomcat). |
| Heap | Jatah RAM untuk JVM. -Xms = jatah awal, -Xmx = jatah maksimal. |
| GC (Garbage Collector) | Petugas kebersihan JVM, membuang data yang tak terpakai. |
| SerialGC | Petugas kebersihan satu orang. Kalau sampah menumpuk, dia sibuk terus dan seluruh aplikasi berhenti. Jangan dipakai untuk beban laporan. |
| G1GC | Petugas kebersihan satu tim, bekerja paralel. Ini yang harus dipakai. |
| OOM Heap | Jatah RAM JVM habis. Tomcat seolah "hang" padahal sedang tercekik GC. |
| Governor (Jasper) | Rem darurat JasperReports: batalkan laporan kalau halaman/waktunya keterlaluan. |
| Istilah | Artinya, sederhananya |
|---|---|
| Opcache | Menyimpan hasil "terjemahan" kode PHP supaya tidak diterjemahkan ulang tiap permintaan. Wajib nyala. |
| Session | Kartu identitas login user. Disimpan di file atau di Redis. |
| File lock (flock) | Kunci file. Kalau session disimpan sebagai file, setiap permintaan mengunci file itu — permintaan lain dari user yang sama harus antre satu-satu. Penyebab lemot nomor 1 di SIMGos. |
| Redis | Penyimpan data super cepat di RAM. Dipakai sebagai pengganti file session. |
| pm (process manager) | Cara PHP-FPM mengatur pegawainya: static, dynamic, atau ondemand. |
Backup dulu, selalu. Sebelum menyentuh file config apa pun:
cp -a /etc/my.cnf /etc/my.cnf.bak-$(date +%Y%m%d-%H%M%S)
Kalau tidak ada backup, jangan mulai.
Ubah satu hal, ukur, lanjut. Kalau mengubah 5 hal sekaligus lalu server bermasalah, Anda tidak akan tahu yang mana penyebabnya.
Angka besar ≠ lebih cepat. Ini kesalahan paling sering. pm.max_children = 2156 bawaan installer bukan setelan bagus — itulah yang membuat Klinik-C reboot 7× dalam 19 hari. Batas yang terlalu besar tidak menambah kecepatan, hanya memperdalam antrean sampai RAM habis.
Server lemot biasanya bukan karena kurang tenaga. Dari semua kasus kita, penyebab tersering berurutan:
Jangan menebak, ukur. Bagian 4 menjelaskan caranya.
Catat apa yang Anda ubah, kapan, dan kenapa. Simpan di file di server, mis. /root/tuning-log.txt.
Ini konsep terpenting di seluruh panduan. Keempat layanan adalah rantai — permintaan mengalir dari kiri ke kanan:
Browser → Apache → PHP-FPM → MySQL
↓
Tomcat (khusus cetak laporan)
Aturannya:
Setiap tahap tidak boleh lebih kecil dari tahap sebelumnya, dan tidak ada gunanya lebih besar dari tahap sebelumnya.
Terjemahan praktisnya:
Apache MaxRequestWorkers ≥ PHP-FPM pm.max_children
PHP-FPM pm.max_children < MySQL max_connections (sisakan cadangan ±20%)
Kenapa penting:
pm.max_children lebih besar dari MaxRequestWorkers → sia-sia. Apache secara fisik tidak akan pernah mengirim permintaan sebanyak itu. Di RS-2 nilainya 3184 padahal Apache hanya bisa 800 — 2384 sisanya cuma angka di kertas.max_connections MySQL lebih kecil dari pm.max_children → Anda hanya memindahkan masalah. Dulunya user melihat "lemot", sekarang user melihat error Too many connections. Di RS-2: 800 pegawai PHP vs 500 saluran MySQL.Batas terakhir yang tidak bisa dilanggar: jumlah CPU. Server 4 vCPU tidak akan mengerjakan 800 hal sekaligus. Di atas beberapa ratus permintaan bersamaan, menambah pegawai hanya memperdalam antrean — throughput tidak naik.
nproc # jumlah CPU
free -h # RAM total & terpakai
df -h / # sisa disk
uptime # load average
Membaca load average: angka pertama ≈ rata-rata antrean 1 menit terakhir. Sehat kalau di bawah jumlah CPU. Server 4 CPU dengan load 3.5 = sibuk tapi sehat; load 20 = kewalahan.
Puncak permintaan per menit dari log Apache:
awk '{print $4}' /var/log/httpd/access_log | cut -d: -f1-3 | sort | uniq -c | sort -rn | head -5
Di RS-2 hasilnya 2036 permintaan pada menit tersibuk ≈ 105 permintaan/detik.
Jumlah pegawai PHP yang benar-benar terpakai:
pgrep -c php-fpm
⚠️ Ambil sampel di jam sibuk (09:00–12:00), bukan tengah malam. Kesalahan kita di RS-2: mengukur 70 pegawai jam 01:00 lalu mengira itu representatif.
Cara benar — sampler otomatis. Pasang cron ini seminggu, lalu baca hasilnya:
cat > /etc/cron.d/fpm-sampler <<'EOF'
* * * * * root echo "$(date +\%F\ \%H:\%M) $(pgrep -c php-fpm) $(pgrep -c httpd)" >> /var/log/fpm-peak.log
EOF
Baca puncaknya:
sort -k3 -rn /var/log/fpm-peak.log | head -20
Apakah PHP-FPM pernah kehabisan pegawai:
grep "reached pm.max_children" /var/log/php-fpm/error.log
Ada barisnya = batas terlalu kecil, naikkan. Kosong = batas cukup atau batasnya memang mustahil tercapai (lihat rantai kapasitas).
for p in $(pgrep php-fpm | head -20); do
awk '/^Rss:/{r=$2} /^Pss:/{p=$2} /^Private/{u+=$2} END{print r, p, u}' /proc/$p/smaps_rollup 2>/dev/null
done | awk '{r+=$1;p+=$2;u+=$3;n++} END{printf "rata2 RSS %d MB | PSS %d MB | USS %d MB (n=%d)\n", r/n/1024, p/n/1024, u/n/1024, n}'
Hasil nyata di RS-2: RSS 60 MB, PSS 16 MB, USS 15 MB. Pakai angka USS.
mysql -uroot -e "
SELECT ROUND(SUM(data_length+index_length)/1024/1024/1024,1) AS total_GB
FROM information_schema.tables;"
Angka ini menentukan innodb_buffer_pool_size.
RAM total = OS (1–2 GB)
+ MySQL (buffer_pool × 1,3)
+ PHP-FPM (max_children × USS)
+ Tomcat (Xmx + 1 GB)
+ Apache (± 0,5 GB)
+ Redis (maxmemory)
+ cadangan 15%
Kalau totalnya melebihi RAM fisik → kecilkan buffer pool atau max_children, jangan berharap "biasanya tidak semua terpakai". Server Klinik-C mati justru karena harapan itu.
Perannya: pintu depan + pengerem. Di sinilah antrean harus ditahan.
File: buat drop-in /etc/httpd/conf.d/mpm-event.conf (jangan edit httpd.conf bawaan).
httpd -V | grep "Server MPM"
Harus event. Kalau prefork, tiap koneksi memakan satu proses penuh — boros. Ubah di /etc/httpd/conf.modules.d/00-mpm.conf: aktifkan mod_mpm_event.so, nonaktifkan mod_mpm_prefork.so.
MaxRequestWorkers = ServerLimit × ThreadsPerChild
Ini bukan saran — ini hukum. Apache menghitungnya sendiri. Kalau Anda menulis MaxRequestWorkers lebih besar dari hasil perkalian itu, Apache diam-diam menurunkannya dan menulis peringatan AH00515 di error log setiap kali start.
ThreadsPerChild biarkan 25 (nilai standar, jangan diutak-atik). Jadi cukup atur ServerLimit:
| Kapasitas yang diinginkan | ServerLimit | MaxRequestWorkers |
|---|---|---|
| 100 | 4 | 100 |
| 150 | 6 | 150 |
| 200 | 8 | 200 |
| 400 | 16 | 400 |
| 800 | 32 | 800 |
| 1200 | 48 | 1200 |
Berapa yang saya butuhkan? Ambil puncak permintaan/detik dari [4.2], kalikan waktu rata-rata satu permintaan (detik), lalu kali 3 sebagai cadangan. Contoh RS-2: 105 req/detik × 0,02 detik × 3 ≈ 7. Nilai 800 sudah 8× lipat dari puncak terukur — sangat longgar. Tidak perlu dinaikkan.
# /etc/httpd/conf.d/mpm-event.conf
<IfModule mpm_event_module>
StartServers 2
ServerLimit 8
ThreadsPerChild 25
MaxRequestWorkers 200 # HARUS = ServerLimit × ThreadsPerChild
MinSpareThreads 25
MaxSpareThreads 75
MaxConnectionsPerChild 10000
</IfModule>
Timeout 60
KeepAlive On
KeepAliveTimeout 5
MaxKeepAliveRequests 100
Penjelasan tiap baris:
| Setelan | Artinya | Catatan |
|---|---|---|
StartServers | Jumlah proses saat start | 2 cukup, Apache menambah sendiri |
ServerLimit | Batas keras jumlah proses | Menaikkan ini butuh RESTART penuh, reload tidak cukup |
ThreadsPerChild | Pegawai per proses | Biarkan 25 |
MaxRequestWorkers | Total permintaan bersamaan | Ini "kapasitas" Apache |
MaxConnectionsPerChild | Proses didaur ulang setelah N koneksi | Mencegah kebocoran memori menumpuk |
KeepAliveTimeout 5 | Berapa lama koneksi menganggur dipertahankan | Jangan 15 detik ke atas — koneksi menganggur memakan slot pegawai |
Timeout 60 | Batas menunggu permintaan lambat |
Kalau server laporan terpisah, Apache mem-proxy ke sana. Timeout proxy harus selaras dengan request_terminate_timeout PHP-FPM di server laporan, kalau tidak koneksi menggantung dan menghabiskan pegawai:
# /etc/httpd/conf.d/php.conf — server laporan
ProxySet timeout=600
⚠️ Kecuali endpoint pembayaran/BPJS. Di RS-1, pembayaran-tagihan sengaja dibiarkan timeout=7200 karena operasi BPJS/INACBG memang lama. Memotongnya = transaksi tagihan gagal. Jangan pukul rata semua endpoint.
⚠️ Aturan bawaan /etc/logrotate.d/httpd hanya mencakup /var/log/httpd/*log. Kalau vhost Anda menulis log ke tempat lain (mis. /var/www/html/log/), log itu tidak akan pernah dirotasi. Di RS-2 requests.log tumbuh sampai 258 GB selama 2 tahun.
Cek dulu:
grep -rE "CustomLog|ErrorLog" /etc/httpd/conf.d/*.conf
Kalau ada path di luar /var/log/httpd/, buat aturan sendiri:
# /etc/logrotate.d/simgos-vhost
/var/www/html/log/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
dateext
create 0644 simgos simgos
sharedscripts
postrotate
/usr/bin/systemctl reload httpd > /dev/null 2>&1 || true
endscript
}
Rotasi pertama harus dipaksa (logrotate menganggap file baru "sudah dirotasi hari ini"):
logrotate -f /etc/logrotate.d/simgos-vhost
Kalau log sudah terlanjur raksasa: jangan pakai
copytruncate(menyalin 258 GB). Pakai rename + reload seperti di atas, tunggu Apache melepas file lama (ls -l /proc/<pid>/fd | grep <namafile-lama>sampai kosong), baru kompres. Kompresi di server itu terbatas I/O bukan CPU — pakaiionice -c 2 -n 7(bukan-c 3yang malah melambat jadi 5 MB/detik).
httpd -t # WAJIB: cek sintaks dulu
systemctl reload httpd # ganti setelan tanpa memutus user
Khusus perubahan ServerLimit: perlu systemctl restart httpd (lakukan di jam sepi).
Perannya: dapur. Ini tempat setelan paling sering salah.
File: /etc/php-fpm.d/www.conf, /etc/php.ini, /etc/php.d/10-opcache.ini
| Mode | Cara kerja | Pakai kapan |
|---|---|---|
static | Semua pegawai dibuat di awal, tetap | Server besar khusus, beban rata |
dynamic | Jaga sejumlah pegawai siaga, tambah saat ramai | Beban naik-turun sedang |
ondemand | Buat pegawai hanya saat ada permintaan, bubarkan setelah menganggur | Rekomendasi untuk SIMGos |
Kenapa ondemand: beban SIMGos sangat berpola — ramai 08:00–13:00, sepi malam. ondemand menghemat RAM saat sepi. Kerugiannya hanya sedikit lambat di permintaan pertama, tidak terasa.
⚠️ Di mode ondemand, setelan pm.start_servers, pm.min_spare_servers, pm.max_spare_servers diabaikan sepenuhnya. Boleh dibiarkan ada, tidak berbahaya, tapi jangan berharap berpengaruh.
pm.max_childrenAmbil nilai terkecil dari tiga batas ini:
1. Apache MaxRequestWorkers ← biasanya ini yang menentukan
2. (RAM tersedia untuk PHP) ÷ USS per pegawai
3. jumlah CPU × 25 (batas kewarasan)
Contoh RS-2 (4 vCPU, 96 GB, Apache 800):
Contoh klinik kecil (3 vCPU, 10 GB, Apache 100):
; /etc/php-fpm.d/www.conf
pm = ondemand
pm.max_children = 100
pm.process_idle_timeout = 30s
pm.max_requests = 500
; endpoint pemantauan (dipakai bagian verifikasi)
pm.status_path = /php-fpm-status
ping.path = /php-fpm-ping
; catat permintaan yang lebih lama dari 10 detik — alat diagnosa utama
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm/www-slow.log
| Setelan | Artinya |
|---|---|
pm.max_children | Batas pegawai bersamaan |
pm.process_idle_timeout | Pegawai menganggur dibubarkan setelah sekian detik |
pm.max_requests | Pegawai didaur ulang tiap N permintaan (mencegah kebocoran memori) |
request_slowlog_timeout | Permintaan >N detik dicatat lengkap dengan baris kode penyebabnya |
⚠️ Jangan ada titik koma di akhir nilai. pm.process_idle_timeout = 30s; (dengan ;) pernah lolos di RS-2 — PHP-FPM menganggapnya awal komentar.
request_terminate_timeout — hati-hatiSetelan ini membunuh permintaan yang lewat batas waktu.
600s). Laporan yang tak terkendali harus dibunuh.Kalau perlu, buat pool terpisah untuk laporan/pembayaran dengan timeout berbeda (RS-3 sudah begini: pool www, report, pembayaran).
memory_limit = 256M ; per permintaan, bukan total
max_execution_time = 60
post_max_size = 32M
upload_max_filesize = 32M
max_input_vars = 5000 ; form SIMGos panjang, bawaan 1000 kadang kurang
realpath_cache_size = 4M ; cache lokasi file — signifikan untuk Laminas
realpath_cache_ttl = 600
Tanpa opcache, PHP menerjemahkan ulang ribuan file SIMGos di setiap permintaan. Ini beda kecepatan 2–3×.
; /etc/php.d/10-opcache.ini
zend_extension=opcache
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=192 ; MB — SIMGos besar, 128 sering kurang
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000 ; SIMGos punya ribuan file
opcache.validate_timestamps=1 ; deteksi file berubah (butuh untuk deploy)
opcache.revalidate_freq=2 ; cek tiap 2 detik
opcache.save_comments=1 ; WAJIB 1 — Laminas/Doctrine membaca anotasi
opcache.fast_shutdown=1
⚠️ opcache.save_comments=0 akan merusak Laminas. Jangan "mengoptimalkan" baris ini.
⚠️ Kalau opcache.validate_timestamps=0 (lebih cepat), setiap deploy harus systemctl reload php-fpm. Untuk SIMGos yang sering di-patch, biarkan 1.
php-fpm -t # WAJIB: cek sintaks
systemctl reload php-fpm # aman di jam kerja
Perannya: gudang data. Setelan paling berdampak, dan paling berisiko kalau salah.
File: buat drop-in /etc/my.cnf.d/simgos.cnf.
MySQL versi resmi (bukan bawaan distro) tidak selalu punya !includedir. Kalau tidak ada, file drop-in Anda diabaikan diam-diam.
grep -n "includedir" /etc/my.cnf
Kalau kosong, tambahkan di baris paling akhir /etc/my.cnf:
!includedir /etc/my.cnf.d/
grep -n "max_connections\|innodb_buffer_pool_size" /etc/my.cnf /etc/my.cnf.d/*.cnf
Di RS-2, /etc/my.cnf punya dua baris max_connections: baris 20 = 2048 dan baris 50 = 500. Yang berlaku adalah yang terakhir (500); baris 20 sudah mati sejak lama tanpa ada yang sadar. Kalau ada duplikat, komentari yang lama.
innodb_buffer_pool_size — setelan paling pentingCara memilih:
| Kondisi | Nilai |
|---|---|
| MySQL sendirian di server | 60–70% RAM |
| MySQL bareng httpd+php-fpm+tomcat | 30–40% RAM |
| Ukuran DB lebih kecil dari itu | ukuran DB × 1,2 sudah cukup |
Jangan berlebihan. RS-2 punya buffer pool 48 GB untuk DB berukuran 25 GB — hanya 11% halaman terpakai. Tidak merugikan (alokasi bersifat malas/lazy), tapi mubazir.
Tambahan:
innodb_buffer_pool_instances = 4 ; 1 instance per 1–2 GB pool, maks 8
Cek efektivitasnya (target ≥ 99%):
mysql -uroot -e "
SELECT ROUND(100 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME='Innodb_buffer_pool_reads')
/ (SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME='Innodb_buffer_pool_read_requests') * 100, 2) AS hit_rate_persen;"
Kalau di bawah 95% → pool kurang besar. Kalau 99,9% → sudah cukup, jangan ditambah.
max_connectionsmax_connections ≥ pm.max_children + 50
Cadangan 50 untuk Tomcat, cron (SATUSEHAT, BPJS, aplicares), backup, dan sesi admin.
⚠️ Menaikkan max_connections wajib diikuti menaikkan batas file:
mkdir -p /etc/systemd/system/mysqld.service.d
cat > /etc/systemd/system/mysqld.service.d/limits.conf <<'EOF'
[Service]
LimitNOFILE=32000
EOF
systemctl daemon-reload
MySQL butuh sekitar max_connections + table_open_cache × 2 file terbuka. Bawaan sering hanya 10.000 — kurang untuk max_connections = 1000. Berlaku setelah restart mysqld berikutnya.
Menaikkan max_connections sendiri bisa tanpa restart:
mysql -uroot -e "SET GLOBAL max_connections = 1000;"
Lalu tetap tulis di simgos.cnf supaya bertahan setelah restart.
# /etc/my.cnf.d/simgos.cnf
[mysqld]
# === Memori ===
innodb_buffer_pool_size = 6G
innodb_buffer_pool_instances = 6
innodb_redo_log_capacity = 1G # MySQL 8.0.30+; versi lama: innodb_log_file_size
innodb_log_buffer_size = 64M
# === Disk I/O ===
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 2
innodb_io_capacity = 1000 # HDD: 200 | SSD: 1000-2000 | NVMe: 4000+
innodb_io_capacity_max = 2000
innodb_flush_neighbors = 0 # 0 untuk SSD/NVMe, 1 untuk HDD
# === Koneksi ===
max_connections = 150
thread_cache_size = 16
table_open_cache = 4000
table_definition_cache = 2000
# === Buffer per sesi (kecil! dikalikan jumlah koneksi) ===
sort_buffer_size = 2M
join_buffer_size = 2M
read_buffer_size = 256K
read_rnd_buffer_size = 512K
# === Tabel sementara (laporan SIMGos berat) ===
tmp_table_size = 128M
max_heap_table_size = 128M
# === Keamanan & visibilitas ===
skip_name_resolve = 1
max_allowed_packet = 64M
slow_query_log = 1
slow_query_log_file = /var/log/mysql-slow.log
long_query_time = 2
max_execution_time = 60000 # ms — rem laporan liar (lihat 7.7)
performance_schema = ON
# === Warm-up setelah restart ===
innodb_buffer_pool_dump_at_shutdown = 1
innodb_buffer_pool_load_at_startup = 1
Catatan penting per setelan:
| Setelan | Kenapa |
|---|---|
innodb_flush_log_at_trx_commit = 2 | Lebih cepat menulis. Risikonya: kalau server mati listrik mendadak, transaksi ≤1 detik terakhir bisa hilang. Untuk RS dengan UPS ini wajar. Kalau tidak ada UPS, pakai 1. |
skip_name_resolve = 1 | MySQL berhenti mencari nama host tiap koneksi. Menghilangkan jeda misterius. Syarat: semua grant di MySQL harus pakai IP, bukan hostname. |
sort_buffer_size kecil | Ini dialokasikan per koneksi. sort_buffer_size = 32M × 500 koneksi = 16 GB. Kesalahan klasik. |
tmp_table_size besar | Laporan SIMGos (CetakMR2, RL) membuat tabel sementara besar. Kalau kekecilan, MySQL menulisnya ke disk = lambat. |
buffer_pool_dump/load | Setelah restart, MySQL memuat ulang isi cache. Tanpa ini, 15–30 menit pertama setelah restart terasa lambat. |
⚠️ Jangan menyalakan log_queries_not_using_indexes = ON di produksi. Di server-database RS-1 ini membuat slow log tumbuh jadi 14 GB / 715.000 entri, penuh query sepersekian detik pada tabel kecil — sehingga 27.800 query yang benar-benar lambat (≥10 detik) tenggelam dan tak terlihat.
Yang benar:
slow_query_log = 1
long_query_time = 2
log_queries_not_using_indexes = OFF
log_slow_admin_statements = OFF
Dan rotasi lognya:
# /etc/logrotate.d/mysql-slow
/var/log/mysql-slow.log {
weekly
rotate 4
compress
missingok
notifempty
create 0640 mysql mysql
postrotate
/usr/bin/mysqladmin flush-slow-logs 2>/dev/null || true
endscript
}
max_execution_time — rem laporan liarmax_execution_time = 60000 # 60 detik, dalam milidetik
Hanya berlaku untuk SELECT, jadi aman untuk transaksi tulis.
Kenapa penting: di RS-1, stored procedure medicalrecord.CetakMR2 (cetak Resume Medis) untuk sebagian nomor pendaftaran meledak dan berjalan 10–14 jam. Akibat berantainya:
FLUSH TABLES → macet menunggu1053 Server shutdown in progressTerjadi berulang: 18, 22, 23 Juni dan 12, 14 Juli.
Kalau 60 detik terlalu ketat untuk laporan sah Anda, naikkan ke 300000 (5 menit) — tapi jangan biarkan tak terbatas.
Tambahkan ke perintah xtrabackup di script backup Anda:
--ftwrl-wait-timeout=120 \
--kill-long-queries-timeout=120 \
--kill-long-query-type=all
Artinya: kalau ada query yang menghalangi lebih dari 2 menit, bunuh query itu dan lanjutkan backup. Lebih baik satu laporan gagal dicetak daripada tidak ada backup semalam.
Sebelum menyalahkan "kurang RAM", periksa dulu:
# berapa banyak trigger, per database
mysql -uroot -e "
SELECT TRIGGER_SCHEMA, COUNT(*) FROM information_schema.TRIGGERS
GROUP BY TRIGGER_SCHEMA ORDER BY 2 DESC;"
# event terjadwal & apakah pernah error
mysql -uroot -e "SELECT db,name,status,interval_value,interval_field,last_executed FROM mysql.event;"
grep -i "PROCEDURE" /var/log/mysqld.log | tail -30
⚠️ information_schema.TRIGGERS hanya menampilkan trigger yang boleh dilihat user Anda. User simgos pernah hanya melihat 26 dari 460 trigger yang sebenarnya ada. Jalankan sebagai root.
Kasus nyata di server-database RS-1: 78 trigger kemkes-ihs + 6 trigger di tabel panas pendaftaran.kunjungan menyala setiap kali pendaftaran disimpan. Satu UPDATE ... WHERE NOMOR=<primary key> yang seharusnya instan malah memeriksa 6 juta sampai 110 juta baris — bukan karena index kurang (NOMOR sudah primary key), tapi karena rentetan trigger. Jangan buru-buru menyimpulkan "kurang index"; buktikan dengan EXPLAIN dulu.
mysqld --validate-config # cek sintaks (MySQL 8.0.16+)
systemctl restart mysqld # sebagian besar setelan butuh RESTART
Setelan yang bisa diubah tanpa restart:
mysql -uroot -e "SET GLOBAL max_connections = 1000;"
mysql -uroot -e "SET GLOBAL slow_query_log = ON;"
mysql -uroot -e "SET GLOBAL long_query_time = 2;"
Yang wajib restart: innodb_buffer_pool_size (bisa online di 8.0 tapi berisiko), innodb_redo_log_capacity, innodb_flush_method, skip_name_resolve.
Perannya: menjalankan JavaBridge + JasperReports, membuat PDF.
File: /opt/tomcat/bin/setenv.sh (instalasi manual) atau /etc/sysconfig/tomcat (instalasi paket).
Di server-laporan RS-1, server dengan 23 GB RAM dan 16 core menjalankan Tomcat dengan -Xms512M -Xmx1024M -XX:+UseSerialGC. Akibatnya: seluruh generasi heap penuh 99–100%, JVM tercekik GC, Tomcat tampak hang padahal tidak crash dan tidak kena OOM Killer.
Operator merespons dengan menekan tombol power fisik server — 8 kali dalam sehari (08:18–12:49). Padahal systemctl restart tomcat sudah cukup.
| Server | Setelan |
|---|---|
| Klinik, laporan jarang dipakai | -Xms256m -Xmx512m |
| RS kecil/menengah, Tomcat serumah dengan MySQL | -Xms1G -Xmx2G |
| Server laporan khusus, 16–24 GB RAM | -Xms4G -Xmx12G |
Aturan: Xmx maksimal 50% RAM kalau berbagi server, maksimal 60% kalau server khusus. Sisakan ruang untuk metaspace, thread stack, dan buffer OS.
# /opt/tomcat/bin/setenv.sh
CATALINA_OPTS="$CATALINA_OPTS -Xms4G -Xmx12G"
CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
CATALINA_OPTS="$CATALINA_OPTS -XX:MaxMetaspaceSize=512m"
CATALINA_OPTS="$CATALINA_OPTS -Djava.awt.headless=true"
# Kalau kehabisan memori: buang jejak forensik lalu MATI BERSIH
CATALINA_OPTS="$CATALINA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
CATALINA_OPTS="$CATALINA_OPTS -XX:HeapDumpPath=/opt/tomcat/logs"
CATALINA_OPTS="$CATALINA_OPTS -XX:+ExitOnOutOfMemoryError"
# Rem darurat JasperReports — batalkan laporan yang keterlaluan
CATALINA_OPTS="$CATALINA_OPTS -Dnet.sf.jasperreports.governor.max.pages=10000"
CATALINA_OPTS="$CATALINA_OPTS -Dnet.sf.jasperreports.governor.timeout=480000"
export CATALINA_OPTS
Penjelasan:
| Setelan | Artinya |
|---|---|
-XX:+UseG1GC | Petugas kebersihan bertim, bukan tunggal. Yang paling penting. |
-XX:MaxGCPauseMillis=200 | Target jeda GC maks 200 ms |
-XX:+ExitOnOutOfMemoryError | Kalau kehabisan heap, mati bersih supaya systemd bisa menghidupkan ulang otomatis. Tanpa ini, Tomcat menggantung selamanya. |
-XX:+HeapDumpOnOutOfMemoryError | Menulis file .hprof — bukti untuk mencari laporan mana penyebabnya |
governor.max.pages=10000 | Batalkan laporan yang melebihi 10.000 halaman |
governor.timeout=480000 | Batalkan laporan yang lebih dari 8 menit |
⚠️ Governor bukan jaminan penuh. Dia tidak menangkap OOM yang terjadi karena driver MySQL menampung hasil query raksasa di memori sebelum Jasper mulai memproses. Obat sesungguhnya adalah membatasi rentang tanggal di sisi aplikasi. Laporan terberat di RS-1 adalah LaporanFarmasiPerPasien — punya filter tanggal, tapi rentangnya dipilih user, jadi user bisa minta setahun sekaligus (satu output pernah 142 MB).
# /etc/systemd/system/tomcat.service
[Service]
Type=forking
PIDFile=/opt/tomcat/temp/tomcat.pid # WAJIB ada untuk Type=forking
Restart=on-failure
RestartSec=10
UMASK=0002 # lihat catatan di bawah
⚠️ Type=forking tanpa PIDFile membuat systemd kehilangan jejak proses — Restart=on-failure jadi tidak berfungsi. Ini yang terjadi di RS-1 sebelum 26 Mei 2026: auto-recovery ada di config tapi tidak pernah jalan.
systemctl daemon-reload
systemctl restart tomcat
Di /opt/tomcat/conf/server.xml, connector sering memakai default ~200 thread. Untuk beban laporan berat, 200 laporan bersamaan pasti menghabiskan heap:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
maxThreads="50"
acceptCount="100"
redirectPort="8443" />
maxThreads=50 + acceptCount=100 artinya: maksimal 50 laporan dikerjakan bersamaan, 100 berikutnya mengantre, sisanya ditolak. Antre lebih baik daripada semua gagal karena OOM.
rm -rf /opt/tomcat/webapps/{docs,examples,host-manager,manager}
Sisakan JavaBridge dan ROOT. Ini hanya soal keamanan, bukan performa — tapi sekalian saja.
SIMGos mensyaratkan baris berikut ada:
apache ALL=(ALL) NOPASSWD: ALL
Karena Jasper (berjalan sebagai user tomcat) menulis PDF dengan izin yang tidak bisa dibaca apache, dan kode SIMGos menjalankan sudo chmod tepat sebelum membacanya.
Gejala kalau baris ini hilang: semua cetak laporan gagal. Browser hanya menampilkan "Failed to load PDF document". HTTP statusnya 200 dengan Content-Length 0, sehingga lolos dari semua pemantauan berbasis status code. Error sebenarnya ada di /var/log/php-fpm/www-error.log sebagai Permission denied.
Ini pernah hilang saat migrasi OS RS-4 (CentOS 7 → AlmaLinux 9), karena migrasi biasanya sengaja tidak menyalin /etc/sudoers. Pasang sebagai drop-in:
echo 'apache ALL=(ALL) NOPASSWD: ALL' > /tmp/s && visudo -cf /tmp/s \
&& install -m 0440 -o root -g root /tmp/s /etc/sudoers.d/simgos
Solusi jangka panjang yang lebih aman:
UMASK=0002di unit systemd Tomcat, supaya Jasper menulis file yang bisa dibaca grup — menghilangkan kebutuhan sebagian besarchmod.
Ini bukan tuning kapasitas, tapi dampaknya paling besar dari semua yang ada di panduan ini. Baca sampai habis.
Secara bawaan PHP menyimpan session sebagai file (session.save_handler = files). Setiap kali kode memanggil session_start(), PHP mengunci file session user itu selama seluruh permintaan berlangsung.
Di SIMGos:
Laminas\Authentication\Storage\Session di konstruktornya — jadi setiap permintaan memanggil session_start()session_write_close() tidak pernah dipanggil di seluruh kode SIMGos — jadi kunci ditahan sampai permintaan selesaiHasilnya: puluhan permintaan dari satu user berbaris satu-satu menunggu kunci file yang sama. Bukan CPU, bukan database — antrean kunci file.
Bukti dari server-aplikasi RS-1: slow log php-fpm mencatat 33.712 permintaan lambat (>5 detik) per hari, 85% di antaranya macet di baris yang sama — DBService/src/Resource.php:116, yaitu konstruktor session. Sementara itu server-database RS-1 (MySQL) menganggur total: 0 slow query, 0 lock wait. Semua orang menyalahkan database; database tidak bersalah sama sekali.
Ganti penyimpanan session ke Redis, yang tidak mengunci (redis.session.locking_enabled = 0 adalah default phpredis).
# 1. Pasang
dnf install -y redis php-pecl-redis5
# 2. Setel redis: hanya localhost, batasi memori
cat >> /etc/redis/redis.conf <<'EOF'
maxmemory 512mb
maxmemory-policy allkeys-lru
save 3600 1
save 300 100
save 60 10000
EOF
systemctl enable --now redis
redis-cli ping # harus PONG
⚠️ AlmaLinux/RHEL 9 mengirim semua baris save dalam keadaan dikomentari. Tanpa baris save, restart Redis akan menghapus semua session — semua user ter-logout. Baris save di atas wajib dipulihkan.
# 3. /etc/php-fpm.d/www.conf
php_value[session.save_handler] = redis
php_value[session.save_path] = "tcp://127.0.0.1:6379"
php-fpm -t && systemctl reload php-fpm
Di server-aplikasi RS-1, setelah cutover: pegawai php-fpm turun dari 150 menjadi 43, dan gelombang permintaan lambat harian hilang.
session.gc_maxlifetime hanya 1440 detik (24 menit), tapi user aktif terus memperbaruinya.systemctl reload php-fpm, kembali ke file.Setiap upgrade PHP akan menghapus ekstensi phpredis yang dibuat untuk versi lama. Akibatnya: session_start() gagal → session tidak tersimpan → user login, halaman kembali ke login, berulang terus.
Terjadi di RS-3 1 Juli 2026 setelah PHP naik ke 8.2. Redis server-nya hidup sehat (PONG), tapi dbsize = 0 — tidak ada yang bisa menulis ke sana.
Cara memastikan (jalankan setelah SETIAP upgrade PHP):
php-fpm -m | grep redis # harus muncul "redis"
Perbaikan:
dnf install -y php-pecl-redis5
systemctl restart php-fpm
Masukkan pemeriksaan ini ke checklist upgrade PHP Anda. Ini adalah jenis kerusakan yang membuat seluruh rumah sakit tidak bisa login, dengan penyebab yang sama sekali tidak terlihat dari gejalanya.
Kalau hanya satu browser/komputer yang mendapat error 500 di setiap panggilan /webservice (sementara komputer lain baik-baik saja), itu bukan Redis. Itu PHPSESSID basi yang ditolak Laminas\Session\Validator\Id. Pola ini berasal dari session.sid_bits_per_character — nilai 5 hanya menghasilkan karakter [0-9a-v], jadi session ID lama yang mengandung w/x/y/z ditolak. Solusi: hapus cookie di browser tersebut. Kalau session.use_strict_mode = 0, masalah ini tidak sembuh sendiri.
Bagian ini bisa langsung disalin. Pilih peran server dulu, baru ukuran RAM + jumlah pengguna.
Langkah 1 — server Anda perannya apa?
| Peran | Isinya | Pakai resep |
|---|---|---|
| Semua-dalam-satu | httpd + php-fpm + mysqld + tomcat di satu mesin | 10.2 / 10.3 |
| Server aplikasi | httpd + php-fpm (+ redis). DB & Jasper di tempat lain | 10.4 |
| Server database | mysqld saja | 10.5 |
| Server laporan | tomcat + httpd + php-fpm ringan | 10.6 |
Langkah 2 — berapa pengguna bersamaan?
Yang dihitung adalah petugas yang login dan aktif bekerja pada jam yang sama, bukan total akun.
Cara mengukur cepat (jam sibuk, mis. jam 10 pagi):
# jumlah session aktif (kalau sudah pakai Redis)
redis-cli --scan --pattern 'PHPREDIS_SESSION:*' | wc -l
# kalau masih pakai file session
find /var/lib/php/session -name 'sess_*' -mmin -15 | wc -l
Kalau belum ada datanya, perkiraan kasar untuk SIMGos:
| Skala fasilitas | Perkiraan pengguna bersamaan di jam puncak |
|---|---|
| Klinik pratama | 5–15 |
| Klinik utama / RS tipe D kecil | 15–40 |
| RS tipe C | 40–100 |
| RS tipe B | 100–250 |
| RS tipe A / RS besar multi-gedung | 250–600 |
Langkah 3 — dua rumus yang mengikat semuanya:
Apache MaxRequestWorkers ≈ pengguna bersamaan × 2 (minimal 100)
PHP-FPM pm.max_children ≈ MaxRequestWorkers × 0,5 (naikkan sampai 1,0 kalau RAM longgar)
MySQL max_connections = pm.max_children + 50
Kenapa
max_childrenboleh setengah dariMaxRequestWorkers? Tidak semua permintaan butuh PHP. Apache juga melayani file statis ExtJS (.js,.css, gambar) dan menahan koneksi keep-alive yang menganggur — itu semua tidak menyentuh PHP sama sekali. Menyamakan keduanya (seperti di RS-2) hanya perlu kalau RAM Anda memang berlimpah.
Profil: 8 vCPU, 32 GB RAM, SSD. httpd + php-fpm + mysqld + tomcat + redis dalam satu server. Database ± 8 GB. Setara RS tipe C.
Perhitungannya:
Pengguna bersamaan : 100
MaxRequestWorkers : 100 × 2 = 200 → ServerLimit 8 × ThreadsPerChild 25 = 200 ✓
pm.max_children : 200 × 0,5 = 100
max_connections : 100 + 50 = 150 → bulatkan 200
innodb_buffer_pool : DB 8 GB × 1,2 = 9,6 → bulatkan 10G
(cek batas: 32 GB × 35% = 11 GB ✓ masih di bawah)
Tomcat Xmx : 2G
Anggaran RAM:
OS + systemd + cron 2,0 GB
MySQL (10G × 1,3) 13,0 GB
PHP-FPM (100 × 20 MB) 2,0 GB
Tomcat (2G + metaspace) 2,8 GB
Apache 0,5 GB
Redis 0,5 GB
Opcache 0,3 GB
---------
Total 21,1 GB dari 32 GB → sisa 10,9 GB ✓
Sisa 10 GB itu bukan pemborosan — Linux memakainya sebagai page cache untuk file database, dan itu memberi ruang aman kalau ada laporan berat mendadak.
/etc/httpd/conf.d/mpm-event.conf# SIMGos tuning — 32 GB / 8 vCPU / <100 pengguna
<IfModule mpm_event_module>
StartServers 4
ServerLimit 8
ThreadsPerChild 25
MaxRequestWorkers 200
MinSpareThreads 25
MaxSpareThreads 75
MaxConnectionsPerChild 10000
</IfModule>
Timeout 60
KeepAlive On
KeepAliveTimeout 5
MaxKeepAliveRequests 100
# jangan bocorkan versi
ServerTokens Prod
ServerSignature Off
/etc/php-fpm.d/www.conf (bagian yang diubah)pm = ondemand
pm.max_children = 100
pm.process_idle_timeout = 30s
pm.max_requests = 500
pm.status_path = /php-fpm-status
ping.path = /php-fpm-ping
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm/www-slow.log
; JANGAN pasang request_terminate_timeout di sini —
; operasi BPJS/INACBG yang sah bisa berjalan lama.
; Sesi Redis (lihat bagian 9 — cutover memutus semua login sekali)
php_value[session.save_handler] = redis
php_value[session.save_path] = "tcp://127.0.0.1:6379"
/etc/php.ini (baris yang diubah)memory_limit = 256M
max_execution_time = 120
post_max_size = 32M
upload_max_filesize = 32M
max_input_vars = 5000
realpath_cache_size = 4M
realpath_cache_ttl = 600
expose_php = Off
/etc/php.d/10-opcache.inizend_extension=opcache
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
opcache.save_comments=1
opcache.fast_shutdown=1
/etc/my.cnf.d/simgos.cnf# SIMGos tuning — 32 GB / 8 vCPU / <100 pengguna / DB ~8 GB
[mysqld]
# === Memori ===
innodb_buffer_pool_size = 10G
innodb_buffer_pool_instances = 8
innodb_redo_log_capacity = 2G
innodb_log_buffer_size = 64M
# === Disk I/O (SSD) ===
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 2
innodb_io_capacity = 1000
innodb_io_capacity_max = 2000
innodb_flush_neighbors = 0
# === Koneksi ===
max_connections = 200
thread_cache_size = 32
table_open_cache = 4000
table_definition_cache = 2000
# === Buffer per sesi (kecil — dikalikan jumlah koneksi) ===
sort_buffer_size = 2M
join_buffer_size = 2M
read_buffer_size = 256K
read_rnd_buffer_size = 512K
# === Tabel sementara (laporan SIMGos) ===
tmp_table_size = 256M
max_heap_table_size = 256M
# === Keamanan & visibilitas ===
skip_name_resolve = 1
max_allowed_packet = 64M
slow_query_log = 1
slow_query_log_file = /var/log/mysql-slow.log
long_query_time = 2
log_queries_not_using_indexes = OFF
max_execution_time = 120000 # 2 menit — rem laporan liar
performance_schema = ON
innodb_buffer_pool_dump_at_shutdown = 1
innodb_buffer_pool_load_at_startup = 1
/etc/systemd/system/mysqld.service.d/limits.conf[Service]
LimitNOFILE=32000
/opt/tomcat/bin/setenv.shCATALINA_OPTS="$CATALINA_OPTS -Xms1G -Xmx2G"
CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
CATALINA_OPTS="$CATALINA_OPTS -XX:MaxMetaspaceSize=384m"
CATALINA_OPTS="$CATALINA_OPTS -Djava.awt.headless=true"
CATALINA_OPTS="$CATALINA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tomcat/logs"
CATALINA_OPTS="$CATALINA_OPTS -XX:+ExitOnOutOfMemoryError"
CATALINA_OPTS="$CATALINA_OPTS -Dnet.sf.jasperreports.governor.max.pages=10000"
CATALINA_OPTS="$CATALINA_OPTS -Dnet.sf.jasperreports.governor.timeout=300000"
export CATALINA_OPTS
/opt/tomcat/conf/server.xml — connector:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
maxThreads="20" acceptCount="50"
redirectPort="8443" />
(20 laporan bersamaan sudah banyak untuk heap 2 GB — masing-masing laporan berat bisa memakan 100–300 MB.)
/etc/redis/redis.conf (tambahan di akhir)bind 127.0.0.1 -::1
protected-mode yes
maxmemory 512mb
maxmemory-policy allkeys-lru
save 3600 1
save 300 100
save 60 10000
/etc/sysctl.d/99-simgos.confnet.core.somaxconn = 4096
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10000 65535
vm.swappiness = 10
⚠️ Kalau server ini adalah kontainer LXC, hapus baris vm.swappiness — harus disetel di host Proxmox, di dalam kontainer hanya gagal diam-diam.
httpd -t && php-fpm -t && mysqld --validate-config
systemctl restart mysqld && systemctl restart php-fpm && \
systemctl reload httpd && systemctl restart tomcat
Struktur filenya sama persis seperti [10.2] — yang berubah hanya angkanya.
| Profil | 8 GB / 4 vCPU<br><25 pengguna | 16 GB / 4 vCPU<br><50 pengguna | 32 GB / 8 vCPU<br><100 pengguna | 64 GB / 8–16 vCPU<br><250 pengguna | 96–128 GB / 8–16 vCPU<br>250+ pengguna |
|---|---|---|---|---|---|
Apache ServerLimit | 4 | 6 | 8 | 16 | 32 |
Apache MaxRequestWorkers | 100 | 150 | 200 | 400 | 800 |
Apache StartServers | 2 | 2 | 4 | 4 | 8 |
PHP-FPM pm | ondemand | ondemand | ondemand | ondemand | ondemand |
PHP-FPM max_children | 25 | 50 | 100 | 250 | 600 |
| Opcache MB | 128 | 192 | 256 | 256 | 320 |
MySQL buffer_pool | 2G | 5G | 10G | 24G | 48G |
MySQL pool_instances | 2 | 4 | 8 | 8 | 8 |
MySQL max_connections | 80 | 100 | 200 | 300 | 700 |
MySQL redo_log_capacity | 512M | 1G | 2G | 4G | 8G |
MySQL tmp_table_size | 64M | 128M | 256M | 256M | 512M |
MySQL LimitNOFILE | 10000 | 16000 | 32000 | 32000 | 32000 |
Tomcat -Xms/-Xmx | 256m / 512m | 512m / 1G | 1G / 2G | 2G / 4G | 2G / 6G |
Tomcat maxThreads | 10 | 15 | 20 | 30 | 40 |
Redis maxmemory | 256mb | 256mb | 512mb | 1gb | 2gb |
| Sesi Redis | opsional | disarankan | wajib | wajib | wajib |
| Perkiraan RAM terpakai | ~5,5 GB | ~11 GB | ~21 GB | ~44 GB | ~72 GB |
Referensi yang sudah terbukti di lapangan:
| Server | Profil | Setelan |
|---|---|---|
| Klinik-A / Klinik-A2 | 3 vCPU, 10 GB, 10–15 pasien/hari | Apache SL4=100 · fpm 20 · bp 5G/4 inst · max_conn 64 · Tomcat 512m |
| Klinik-B | 3 vCPU, 12 GB, 20–30 pasien/hari | Apache SL6=150 · fpm 28 · bp 6G/6 inst · max_conn 64 · Tomcat 512m |
| Klinik-C | 4 vCPU, 14 GB (LXC) | fpm 100 · bp 6G · Tomcat 1G |
| RS-2 | 4 vCPU, 96 GB, puncak 105 req/detik | Apache SL32=800 · fpm 800 · bp 48G · max_conn 1000 |
Perhatikan: kedua klinik di atas memakai
max_connections = 64denganmax_children = 20–28. Cadangannya lebih kecil dari rumus "+50" karena di server sekecil itu hanya ada beberapa cron. Sesuaikan dengan jumlah integrasi yang aktif (SATUSEHAT, BPJS, aplicares, RSOnline, SITB — masing-masing memakai koneksi sendiri).
Isinya: httpd + php-fpm + redis. Database dan Jasper ada di server lain.
Yang berubah dibanding semua-dalam-satu:
| Profil | 8 GB / 4 vCPU<br><100 pengguna | 16 GB / 8 vCPU<br><250 pengguna | 32 GB / 16 vCPU<br>250+ pengguna |
|---|---|---|---|
Apache ServerLimit | 8 | 16 | 32 |
Apache MaxRequestWorkers | 200 | 400 | 800 |
PHP-FPM max_children | 150 | 350 | 700 |
| Opcache MB | 256 | 256 | 320 |
Redis maxmemory | 512mb | 1gb | 2gb |
| RAM terpakai | ~6 GB | ~12 GB | ~22 GB |
⚠️ Angka max_children di sini harus dijumlahkan lintas server kalau ada lebih dari satu server aplikasi. Dua server aplikasi masing-masing 350 → server database harus menyanggupi 700 + 50 = 750 koneksi.
/etc/php-fpm.d/www.confpm = ondemand
pm.max_children = 150
pm.process_idle_timeout = 30s
pm.max_requests = 500
pm.status_path = /php-fpm-status
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm/www-slow.log
; ⚠️ JANGAN pasang request_terminate_timeout global di sini.
; Di RS-1 ini sengaja dikosongkan — operasi BPJS/INACBG/tagihan
; memang berdurasi panjang dan akan ikut terbunuh.
php_value[session.save_handler] = redis
php_value[session.save_path] = "tcp://127.0.0.1:6379"
; MySQL ada di server lain — beri batas waktu koneksi
php_value[mysql.connect_timeout] = 10
php_value[default_socket_timeout] = 120
# /etc/httpd/conf.d/php.conf
# Panggilan ke server laporan — selaras dengan
# request_terminate_timeout=600 di server laporan
<Location "/webservice/report">
ProxySet timeout=600
</Location>
# ⚠️ Pembayaran / INACBG sengaja dibiarkan panjang.
# Memotongnya = transaksi tagihan gagal.
<Location "/webservice/pembayaran-tagihan">
ProxySet timeout=7200
</Location>
Kenapa harus dibedakan: di RS-1, timeout laporan pernah 3600 detik sementara server laporan sudah menyerah di detik ke-600. Selisihnya berarti koneksi menggantung sia-sia selama 50 menit sambil menahan slot pegawai — sedikit demi sedikit seluruh pool habis dan seluruh situs ikut lemot. Ini disebut back-pressure: kalau tetangga Anda menyerah, Anda harus ikut menyerah pada waktu yang sama.
Pisahkan lalu lintas yang berbeda sifatnya supaya laporan berat tidak menghabiskan pegawai untuk pendaftaran. Pola ini sudah dipakai di RS-3 (www, report, pembayaran):
# /etc/php-fpm.d/report.conf
[report]
listen = /run/php-fpm/report.sock
pm = ondemand
pm.max_children = 30
request_terminate_timeout = 600s ; boleh dipasang: ini khusus laporan
⚠️ Setiap pool wajib punya baris session Redis-nya sendiri. Kalau tertinggal, pool itu diam-diam kembali ke session file — dan masalah kunci file muncul lagi hanya di sebagian layar saja, yang jauh lebih sulit didiagnosis.
Isinya: mysqld saja. Tidak ada PHP, tidak ada Apache.
Yang berubah:
max_connections = jumlah max_children semua server aplikasi + 50skip_name_resolve = 1 mewajibkan semua grant memakai IP, bukan hostname| Profil | 16 GB / 4 vCPU<br>DB ≤ 10 GB | 32 GB / 8 vCPU<br>DB ≤ 20 GB | 64 GB / 16 vCPU<br>DB ≤ 45 GB | 128 GB / 16+ vCPU<br>DB ≤ 90 GB |
|---|---|---|---|---|
innodb_buffer_pool_size | 10G | 20G | 40G | 80G |
innodb_buffer_pool_instances | 8 | 8 | 8 | 8 |
innodb_redo_log_capacity | 2G | 4G | 8G | 16G |
max_connections | 200 | 400 | 800 | 1200 |
table_open_cache | 4000 | 6000 | 8000 | 8000 |
tmp_table_size | 256M | 512M | 1G | 1G |
thread_cache_size | 32 | 64 | 128 | 128 |
LimitNOFILE | 32000 | 32000 | 65000 | 65000 |
| RAM terpakai | ~14 GB | ~27 GB | ~54 GB | ~106 GB |
Aturan buffer pool: ambil yang lebih kecil antara ukuran DB × 1,2 dan RAM × 0,7.
Melebihi ukuran DB tidak menambah kecepatan sama sekali. RS-2 punya pool 48 GB untuk DB 25 GB — hanya 11% halaman terpakai, hit rate 99,98%. Tidak berbahaya, tapi 20 GB itu bisa dipakai untuk hal lain.
/etc/my.cnf.d/simgos.cnf — server DB khusus, 64 GB[mysqld]
# === Memori ===
innodb_buffer_pool_size = 40G
innodb_buffer_pool_instances = 8
innodb_redo_log_capacity = 8G
innodb_log_buffer_size = 128M
# === Disk I/O ===
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 2
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
innodb_flush_neighbors = 0
innodb_read_io_threads = 8
innodb_write_io_threads = 8
innodb_purge_threads = 4
# === Koneksi (= Σ max_children semua server app + 50) ===
max_connections = 800
thread_cache_size = 128
table_open_cache = 8000
table_definition_cache = 4000
skip_name_resolve = 1 # ⚠️ semua grant HARUS pakai IP
# === Buffer per sesi ===
sort_buffer_size = 2M
join_buffer_size = 2M
read_buffer_size = 256K
read_rnd_buffer_size = 512K
# === Laporan berat ===
tmp_table_size = 1G
max_heap_table_size = 1G
max_execution_time = 300000 # 5 menit — WAJIB (lihat 7.7)
# === Visibilitas ===
slow_query_log = 1
slow_query_log_file = /var/log/mysql-slow.log
long_query_time = 2
log_queries_not_using_indexes = OFF # ⚠️ jangan ON (lihat 7.6)
log_slow_admin_statements = OFF
performance_schema = ON
innodb_buffer_pool_dump_at_shutdown = 1
innodb_buffer_pool_load_at_startup = 1
max_allowed_packet = 128M
1. Batas file:
# /etc/systemd/system/mysqld.service.d/limits.conf
[Service]
LimitNOFILE=65000
2. Backup yang tidak bisa disandera laporan:
xtrabackup --backup \
--ftwrl-wait-timeout=120 \
--kill-long-queries-timeout=120 \
--kill-long-query-type=all \
...
Tanpa tiga baris ini, satu CetakMR2 yang tertahan menggagalkan backup semalam penuh. Sudah terjadi 5 kali di server-database RS-1.
3. Pemantauan query panjang (server-database RS-1 memakai /usr/local/bin/mysql-monitor.sh tiap 5 menit, memberi peringatan Telegram untuk query >300 detik dengan tombol "KILL"). Minimal, pasang cron sederhana:
*/5 * * * * root mysql -uroot -sNe "SELECT CONCAT(id,' ',time,'s ',LEFT(info,80)) FROM information_schema.processlist WHERE command!='Sleep' AND time>300" | logger -t mysql-longquery
4. ⚠️ Jangan pakai jemalloc. Terlihat sangat menggoda (di server-database RS-1: RSS turun 47 GB → 15 GB, swap 8,8 GB → 0). Tapi 3 jam kemudian mysqld SIGSEGV saat backup malam menjalankan FLUSH TABLES, dan backup malam itu gagal total. Penyebabnya bug use-after-free di MySQL 8.0.46 saat membongkar trigger/stored-procedure — glibc menutupinya, jemalloc membongkarnya. Sudah dikembalikan ke glibc dan tidak boleh dicoba lagi sampai jumlah trigger dikurangi.
5. Periksa beban tersembunyi sebelum menambah RAM:
mysql -uroot -e "SELECT TRIGGER_SCHEMA, COUNT(*) FROM information_schema.TRIGGERS GROUP BY 1 ORDER BY 2 DESC;"
mysql -uroot -e "SELECT db,name,status,last_executed FROM mysql.event;"
Di server-database RS-1, 78 trigger SATUSEHAT membuat satu UPDATE sederhana memeriksa 6–110 juta baris. Tidak ada jumlah RAM yang bisa memperbaiki itu.
Isinya: tomcat (JavaBridge + JasperReports) + httpd + php-fpm ringan.
Yang berubah:
request_terminate_timeout wajib dipasang di sini (kebalikan server aplikasi)maxThreads Tomcat: lebih baik laporan mengantre daripada semua gagal karena kehabisan heap| Profil | 8 GB / 4 vCPU<br>laporan ringan | 16 GB / 8 vCPU<br>sedang | 24 GB / 16 vCPU<br>berat |
|---|---|---|---|
Tomcat -Xms | 1G | 2G | 4G |
Tomcat -Xmx | 4G | 8G | 12G |
Tomcat MaxMetaspaceSize | 384m | 512m | 512m |
Connector maxThreads | 25 | 40 | 50 |
Connector acceptCount | 50 | 75 | 100 |
PHP-FPM max_children | 30 | 60 | 100 |
PHP-FPM request_terminate_timeout | 600s | 600s | 600s |
Jasper governor.timeout | 300000 (5 mnt) | 480000 (8 mnt) | 480000 (8 mnt) |
Jasper governor.max.pages | 5000 | 10000 | 10000 |
Apache MaxRequestWorkers | 100 | 150 | 200 |
| RAM terpakai | ~6 GB | ~11 GB | ~16 GB |
(Kolom paling kanan = nilai riil server-laporan RS-1.)
/opt/tomcat/bin/setenv.sh — 24 GBCATALINA_OPTS="$CATALINA_OPTS -Xms4G -Xmx12G"
CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
CATALINA_OPTS="$CATALINA_OPTS -XX:MaxMetaspaceSize=512m"
CATALINA_OPTS="$CATALINA_OPTS -Djava.awt.headless=true"
CATALINA_OPTS="$CATALINA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tomcat/logs"
CATALINA_OPTS="$CATALINA_OPTS -XX:+ExitOnOutOfMemoryError"
CATALINA_OPTS="$CATALINA_OPTS -Dnet.sf.jasperreports.governor.max.pages=10000"
CATALINA_OPTS="$CATALINA_OPTS -Dnet.sf.jasperreports.governor.timeout=480000"
export CATALINA_OPTS
⚠️ Kalau ada baris -Djava.rmi.server.hostname=... (untuk JMX), jangan diubah — di server-laporan RS-1 itu dipatok ke IP tertentu dan mengubahnya memutus pemantauan.
/etc/systemd/system/tomcat.service[Service]
Type=forking
PIDFile=/opt/tomcat/temp/tomcat.pid
Restart=on-failure
RestartSec=10
UMASK=0002
/etc/php-fpm.d/www.conf — server laporanpm = ondemand
pm.max_children = 100
pm.process_idle_timeout = 30s
; ⚠️ DI SINI WAJIB — laporan yang tertahan harus dibunuh
request_terminate_timeout = 600s
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm/www-slow.log
www-slow.log adalah alat diagnosa terpenting di server laporan — setiap permintaan >10 detik dicatat lengkap dengan jalur script dan backtrace-nya, sehingga Anda tahu laporan mana yang bermasalah.
# 1. Izin sudo (tanpa ini SEMUA cetak gagal — lihat jebakan #18)
echo 'apache ALL=(ALL) NOPASSWD: ALL' > /tmp/s && visudo -cf /tmp/s \
&& install -m 0440 -o root -g root /tmp/s /etc/sudoers.d/simgos
# 2. Buang aplikasi bawaan Tomcat
rm -rf /opt/tomcat/webapps/{docs,examples,host-manager,manager}
# 3. Journal permanen (log bertahan setelah reboot)
mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal
systemctl restart systemd-journald
Tempel ini di dekat server.
systemctl restart tomcat. Cukup. Sistem operasi tetap hidup, Apache tidak perlu disentuh.tail -50 /opt/tomcat/logs/catalina.out
ls -lh /opt/tomcat/logs/java_pid*.hprof # ada file = benar kehabisan memori
tail -f /var/log/php-fpm/www-slow.log # laporan mana yang macet
⚠️ Governor bukan jaminan penuh. Dia tidak menangkap kehabisan memori yang terjadi karena driver MySQL menampung hasil query raksasa sebelum Jasper mulai bekerja. Obat sesungguhnya ada di aplikasi: batasi rentang tanggal. Laporan terberat di RS-1 (LaporanFarmasiPerPasien) punya filter tanggal, tapi rentangnya dipilih user — dan ada yang meminta setahun sekaligus (output 142 MB).
Checklist saat memindahkan MySQL atau Tomcat ke server sendiri:
| Hal | Tindakan |
|---|---|
| Grant MySQL | Karena skip_name_resolve = 1, semua grant harus pakai IP: GRANT ... TO 'simgos'@'192.168.0.10'. Grant berbasis hostname akan gagal login. |
| Firewall | Buka hanya dari IP server aplikasi: MySQL 3306, Tomcat 8080. Jangan buka ke internet. |
| Config SIMGos | webservice/config/autoload/local.php → hostname diarahkan ke IP server DB (bukan 127.0.0.1). |
| Timeout koneksi | Jaringan bisa putus; pasang mysql.connect_timeout = 10 di php.ini supaya tidak menggantung selamanya. |
| Timeout proxy | Selaraskan dengan request_terminate_timeout server tujuan (lihat 10.4). Selisih = koneksi menggantung. |
| Sesi Redis | Pasang Redis di server aplikasi (localhost), bukan di server DB. Server laporan tidak ikut — dia pakai otentikasi berbasis IP. |
| NTP | Semua server harus sinkron jam. Selisih jam mengacaukan validasi session dan penelusuran log lintas server. |
| Zona waktu log | ⚠️ mysqld.log memakai UTC, log backup dan Apache biasanya WIB (+07:00). 21:35Z = 04:35 WIB. Wajib diingat saat mencocokkan kejadian antar-server. |
| Backup | Jadwalkan di server DB, dan pastikan tidak bentrok dengan restart terjadwal (di RS-1, restart 06:00 pernah menggagalkan backup yang masih menggantung sejak 02:00). |
| Pemantauan | Setiap server punya slow log sendiri. Saat mendiagnosis lemot, periksa ketiganya — penyebabnya sering di server yang tidak Anda curigai. |
httpd -t, php-fpm -t, mysqld --validate-configmysqld → php-fpm → httpd → tomcat
(dari belakang ke depan; kalau terbalik, Apache menerima permintaan padahal PHP belum siap)| Perubahan | Butuh | Jam |
|---|---|---|
max_connections naik | SET GLOBAL | kapan saja |
| Setelan php-fpm | reload | kapan saja |
MaxRequestWorkers (tanpa ubah ServerLimit) | reload httpd | kapan saja |
ServerLimit | restart httpd | jam sepi |
| Setelan MySQL InnoDB | restart mysqld | jam sepi |
| Cutover session ke Redis | reload php-fpm | jam sepi — semua ter-logout |
| Heap Tomcat | restart tomcat | kapan saja (laporan terputus) |
Cari jendela maintenance yang sudah ada. RS-2 punya
/root/weeklymtc.shsetiap Jumat 00:01 yang sudah menghentikan-menghidupkanhttpd php-fpm mysqld. Itu jendela gratis untuk perubahan yang butuh restart penuh — tidak perlu bikin downtime baru.
skrip tuning otomatis bisa dijadikan contoh. Sifat pentingnya:
.bak-<stempel> + bundle tar di /root/httpd -t dan php-fpm -t sebelum menyentuh service# managed by ... supaya jelas mana yang dikelola script# php-fpm
cp /etc/php-fpm.d/www.conf.bak-<stempel> /etc/php-fpm.d/www.conf
systemctl reload php-fpm
# apache
rm /etc/httpd/conf.d/mpm-event.conf
systemctl reload httpd
# mysql
rm /etc/my.cnf.d/simgos.cnf
systemctl restart mysqld
# semuanya
tar -xzf /root/tuning-backup/pre-<stempel>.tgz -C /
systemctl is-active mysqld php-fpm httpd tomcat
free -h
MySQL (bukan sekadar tertulis di file):
mysql -uroot -e "SHOW VARIABLES WHERE Variable_name IN (
'innodb_buffer_pool_size','innodb_buffer_pool_instances',
'max_connections','tmp_table_size','max_execution_time',
'slow_query_log','skip_name_resolve');"
Apache — pastikan tidak ada AH00515:
grep AH00515 /var/log/httpd/error_log | tail -5
Kosong = benar. Ada = MaxRequestWorkers Anda melebihi ServerLimit × ThreadsPerChild, dan Apache diam-diam memakai angka yang lebih kecil.
PHP-FPM:
php-fpm -tt 2>&1 | grep -E "max_children|pm ="
php-fpm -m | grep -E "redis|opcache"
Tomcat:
ps -ef | grep java | tr ' ' '\n' | grep -E "^-X"
| Metrik | Perintah | Target |
|---|---|---|
| Permintaan lambat php-fpm | wc -l /var/log/php-fpm/www-slow.log | turun drastis |
| Query lambat MySQL | wc -l /var/log/mysql-slow.log | mendekati 0 di jam kerja |
| Buffer pool hit rate | lihat [7.3] | ≥ 99% |
| Load average | uptime | < jumlah CPU |
| Swap terpakai | free -h | 0 |
| Pegawai php-fpm puncak | pgrep -c php-fpm | < 70% dari max_children |
AH00515 | grep error_log | tidak ada |
watch -n5 'echo "load: $(uptime | awk -F"load average:" "{print \$2}")";
echo "fpm : $(pgrep -c php-fpm)";
echo "httpd: $(pgrep -c httpd)";
echo "mysql: $(mysql -uroot -sNe "SHOW STATUS LIKE \"Threads_connected\"" | cut -f2)";
free -h | head -2'
Semua ini kejadian nyata di server produksi.
| # | Kesalahan | Akibat | Pencegahan |
|---|---|---|---|
| 1 | pm.max_children = 2156 bawaan installer | Klinik-C reboot 7× dalam 19 hari (butuh 84 GB untuk server 14 GB) | Selalu periksa nilai ini setelah instalasi baru |
| 2 | Menghitung RAM dari RSS | Perkiraan meleset 4× lipat, keputusan kapasitas salah | Pakai USS dari /proc/<pid>/smaps_rollup |
| 3 | Dua baris max_connections di my.cnf | Baris terakhir menang; nilai 2048 di baris 20 mati diam-diam | grep -n cari duplikat sebelum mengubah |
| 4 | MaxRequestWorkers > ServerLimit × ThreadsPerChild | Apache diam-diam memakai angka kecil + warning AH00515 setiap start selama berbulan-bulan | Hitung dan samakan; cek error log |
| 5 | max_children (800) > max_connections (500) | Bertukar dari "lemot" menjadi Too many connections | max_connections ≥ max_children + 50 |
| 6 | Naikkan max_connections tanpa LimitNOFILE | MySQL kehabisan file descriptor | Drop-in systemd LimitNOFILE=32000 |
| 7 | Session file (flock) | 33.712 permintaan lambat/hari, 85% macet di satu baris kode; DB menganggur total | Pindah ke Redis session |
| 8 | Upgrade PHP tanpa pasang ulang phpredis | Seluruh RS tidak bisa login (loop halaman login) | php-fpm -m | grep redis setelah SETIAP upgrade PHP |
| 9 | Baris save Redis dikomentari (bawaan EL9) | Restart Redis = semua session hilang | Pulihkan baris save |
| 10 | Tomcat -Xmx1024M + SerialGC di server 23 GB | Tomcat "hang", operator menekan tombol power 8× sehari | Heap memadai + G1GC + ExitOnOutOfMemoryError |
| 11 | Type=forking tanpa PIDFile | Restart=on-failure tidak pernah jalan | Tambahkan PIDFile= |
| 12 | Log vhost di luar /var/log/httpd/ | requests.log = 258 GB selama 2 tahun | Buat aturan logrotate sendiri; rotasi pertama -f |
| 13 | log_queries_not_using_indexes = ON | Slow log 14 GB, 27.800 query yang benar-benar lambat tersembunyi | Matikan; andalkan long_query_time |
| 14 | Laporan tanpa batas waktu | CetakMR2 jalan 10–14 jam → backup malam gagal berulang kali | max_execution_time + --kill-long-queries-timeout |
| 15 | Menyimpulkan "kurang index" tanpa EXPLAIN | Salah arah; ternyata rentetan 78 trigger | EXPLAIN dulu; hitung trigger sebagai root |
| 16 | information_schema.TRIGGERS sebagai user biasa | Terlihat 26 dari 460 trigger | Jalankan sebagai root |
| 17 | Mencoba jemalloc untuk mengatasi fragmentasi memori | RAM turun 47→15 GB, tapi mysqld SIGSEGV saat backup malam → backup gagal | Jangan pakai jemalloc di MySQL 8.0.46 dengan banyak trigger. Kalau tetap ingin, uji di server non-produksi. |
| 18 | Menghapus apache NOPASSWD demi keamanan | Semua cetak laporan mati, HTTP 200 Content-Length 0, lolos dari monitoring | Pasang /etc/sudoers.d/simgos; masukkan ke checklist migrasi |
| 19 | request_terminate_timeout global di server aplikasi | Transaksi BPJS/INACBG yang sah ikut terbunuh | Hanya di server laporan, atau pakai pool terpisah |
| 20 | Titik koma di akhir nilai config (= 30s;) | Nilai tidak berlaku, tanpa error | Perhatikan; validasi dengan php-fpm -tt |
| 21 | Ukur beban jam 01:00 | 70 pegawai dikira normal; siang hari ~10× lipat | Sampel jam sibuk, atau pasang cron sampler seminggu |
| 22 | Percaya Max_used_connections | Tidak berguna kalau mysqld baru direstart tadi malam | Cek Uptime dulu |
| 23 | copytruncate pada log raksasa | Menyalin 258 GB | Rename + reload; tunggu fd terlepas; baru kompres |
| 24 | ionice -c 3 (idle) untuk kompresi | Melambat jadi 5 MB/detik (ETA 12 jam) | Pakai ionice -c 2 -n 7 (12 MB/detik) |
| 25 | Drop-in my.cnf.d diabaikan | Tidak ada !includedir di /etc/my.cnf | grep includedir /etc/my.cnf dulu |
| 26 | Setelan sysctl vm.* di dalam kontainer LXC | Read-only, gagal diam-diam | Setel di host Proxmox, bukan di kontainer |
Periksa berurutan — jangan langsung ke database:
# 1. Session file? (paling sering)
grep -c "session\|Resource.php" /var/log/php-fpm/www-slow.log
php -i | grep session.save_handler
Kalau files dan slow log penuh → bagian 9.
# 2. Pegawai PHP habis?
grep "reached pm.max_children" /var/log/php-fpm/error.log | tail
# 3. Database benar-benar sibuk?
mysql -uroot -e "SHOW STATUS LIKE 'Threads_running'; SHOW PROCESSLIST;" | head -30
Threads_running ≤ 5 → database tidak bersalah, cari di PHP.
# 4. Server laporan menahan koneksi?
tail -50 /var/log/php-fpm/www-slow.log
curl -s -o /dev/null -w "%{http_code} %{time_total}\n" http://<report-server>:8080/JavaBridge/
# izin sudo hilang?
sudo -l -U apache | head
grep "Permission denied" /var/log/php-fpm/www-error.log | tail
→ jebakan #18
# Tomcat kehabisan memori?
ls -lh /opt/tomcat/logs/java_pid*.hprof
tail -50 /opt/tomcat/logs/catalina.out
→ bagian 8
journalctl -b -1 -p err | tail -50 # log boot sebelumnya
dmesg | grep -i "killed process" # OOM Killer beraksi?
free -h # swap terpakai?
grep -c "max_children" /etc/php-fpm.d/www.conf
Kalau Killed process ... mysqld → RAM terlalu diklaim berlebih. Cek jebakan #1 dan #2.
⚠️ Aktifkan journal permanen supaya log bertahan setelah reboot:
mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal
systemctl restart systemd-journald
grep -iE "error|failed" /var/log/autobackup.log | tail
grep "Forcing close of thread" /var/log/mysqld.log | tail
Nomor thread yang sama muncul di keduanya → ada query berjam-jam yang memblokir. → bagian 7.7 & 7.8
⚠️ Waktu di mysqld.log adalah UTC, sedangkan log backup biasanya WIB (+07:00). 21:35Z = 04:35 WIB.
php-fpm -m | grep redis # kosong = penyebabnya
redis-cli ping
redis-cli dbsize # 0 padahal ada yang login = tidak bisa menulis
grep "save handler" /var/log/php-fpm/www-error.log | tail
→ jebakan #8
Bukan masalah server. Hapus cookie di browser tersebut. → bagian 9.6
#!/bin/bash
echo "=== $(hostname) $(date) ==="
systemctl is-active mysqld php-fpm httpd tomcat | paste -sd' '
uptime
free -h | head -2
df -h / | tail -1
echo "php-fpm workers : $(pgrep -c php-fpm)"
echo "httpd workers : $(pgrep -c httpd)"
echo "mysql conns : $(mysql -uroot -sNe 'SHOW STATUS LIKE "Threads_connected"' | cut -f2)"
echo "slow php (24j) : $(find /var/log/php-fpm/www-slow.log -mtime -1 2>/dev/null && wc -l < /var/log/php-fpm/www-slow.log)"
echo "AH00515 : $(grep -c AH00515 /var/log/httpd/error_log)"
echo "OOM kills : $(dmesg | grep -ci 'killed process')"
mysqldumpslow -s t -t 10 /var/log/mysql-slow.log
⚠️ Jangan jalankan pada slow log berukuran belasan GB di jam kerja — sangat lambat.
mysql -uroot -e "
SELECT id, user, host, db, time, LEFT(info,120) AS query
FROM information_schema.processlist
WHERE command != 'Sleep' AND time > 5
ORDER BY time DESC;"
mysql -uroot -e "KILL <id>;"
tail -f /var/log/php-fpm/www-slow.log
Setiap entri menunjukkan file dan baris kode persis di mana permintaan tersangkut. Ini alat diagnosa terbaik yang Anda punya — pastikan request_slowlog_timeout sudah aktif.
Aktifkan di vhost:
<LocationMatch "^/(php-fpm-status|php-fpm-ping)$">
Require ip 127.0.0.1
ProxyPass unix:/run/php-fpm/www.sock|fcgi://localhost
</LocationMatch>
Lalu:
curl -s localhost/php-fpm-status?full | head -30
buffer_pool = 30–40% RAM kalau berbagi server, 60–70% kalau MySQL sendirian.php-fpm -m | grep redis.ExitOnOutOfMemoryError, PIDFile.Disusun dari catatan penanganan nyata di 8 rumah sakit dan 6 klinik pengguna SIMGos di Indonesia. Nama fasilitas disamarkan. Angka pada tabel referensi adalah konfigurasi yang benar-benar berjalan di produksi.
Masukkan RAM, jumlah CPU, jumlah pengguna, dan susunan server Anda — kalkulator akan menghasilkan seluruh file konfigurasi siap salin beserta anggaran RAM-nya. Semua perhitungan berjalan di browser Anda; spesifikasi yang Anda isi tidak pernah dikirim ke mana pun.
Buka kalkulatorMembangun sistem kesehatan digital yang aman, terukur, dan sesuai standar untuk institusi kesehatan Indonesia.
© 2026 DEKATE. All rights reserved.