Diagram alur PHP-FPM pool 80 child vs MySQL max_connections 151 default — visualisasi overflow saat traffic spike + arrow ke connection pool exhaustion
Diagram alur PHP-FPM pool size 80 child versus MySQL max connections 151 default mismatch yang menyebabkan connection pool exhaustion saat traffic spike di VPS WordPress

MySQL “Too Many Connections” di VPS: Debug & Tuning my.cnf untuk RAM 2 GB

Diposting pada

Transparency note: Artikel ini berdasarkan incident nyata klien LarhTech tanggal 13 Mei 2026. Detail teknis (versi software, angka, lokasi file) akurat. Nama klien anonymized. Tuning my.cnf yang saya tampilkan adalah hasil iteratif setelah 4 minggu monitoring post-incident, bukan copy-paste dari docs MariaDB official.

Rabu 13 Mei 2026, jam 14:23 WIB. Klien WhatsApp saya:

“Pak, tokonya 503 semua. Padahal saya lagi flash sale Hari Konsumen. Customer udah complain di chat.”

Saya buka SSH, langsung ke MySQL error log:

2026-05-13 14:25:11 0 [Warning] Aborted connection 18472 to db: 'wp_klien' user: 'wpklien' host: 'localhost'
2026-05-13 14:25:13 0 [Warning] Too many connections
2026-05-13 14:25:13 0 [Warning] Too many connections
2026-05-13 14:25:14 0 [Warning] Too many connections
... (continuous 14 baris per detik)

Threads_connected hit max 151 dari connection pool. Trigger: traffic 5x baseline karena promo flash sale H-1 Hari Konsumen (Klien mirror promo Shopee). VPS Contabo SMALL 4 vCPU / 2 GB RAM / Singapore. Klien baru migrasi dari shared 6 bulan lalu, itu kenapa configurasi MySQL nya masih default Ubuntu 24.04.

Quick fix 2 menit: increase temporary max_connections=200, restart MariaDB. Klien online kembali. Tapi itu cuma tunda crash, di traffic peak berikutnya saya tahu akan kena OOM-killed.

Tuning permanent saya apply H+1 saat scheduled maintenance 22:00-00:00 WIB. Counter-intuitively, saya TURUNKAN max_connections dari 151 → 80. Plus matching PHP-FPM pool. Plus tuning innodb buffer pool.

Result 30 hari kemudian: 0 incident di traffic 5x baseline lagi (Black Friday minor sale), threads_connected max 62/80. Artikel ini step-by-step debugging + tuning my.cnf yang saya pakai. Counter-intuitive di banyak tempat, saya jelaskan kenapa.

1. Apa yang Terjadi Saat MySQL “Too Many Connections” Hit Production

Dari sisi pembaca/customer, gejala-nya:

  • HTTP 503 Service Unavailable di seluruh halaman
  • “Error establishing a database connection” putih layar khas WordPress
  • WooCommerce checkout gagal dengan error “ada masalah pemrosesan, mohon coba lagi”
  • API endpoint timeout (REST API /wp-json/)
  • WP-Cron tidak jalan background

Dari sisi server log, signature:

[Warning] Too many connections
[Warning] Aborted connection ... 'wpklien'

Pembeda dengan slow query (yang sudah saya bahas di artikel DB Query Slow):

Dimensi Slow Query Too Many Connections
Symptom 1 user lambat (TTFB 1500 ms) Banyak user gagal serentak (503)
Root cause Index missing, query plan buruk PHP-FPM pool > MySQL max_connections, atau connection leak
Detect slow.log + EXPLAIN SHOW PROCESSLIST + error.log
Fix Index + query rewrite Tuning my.cnf + match PHP-FPM

Kedua jenis bisa terjadi bersamaan, slow query yang occupy connection makin lama bisa trigger too many connections di traffic peak. Tapi root cause-nya beda dimensi.

2. Setup Tooling: 4 Command Wajib untuk Debug MySQL Live

Saya pakai 4 command dasar saat insiden + 3 tool external setelah quick fix. Klarifikasi: VPS ini pakai MariaDB 10.11 (Ubuntu 24.04 default). Semua command compatible MySQL 8.0 dengan minor variasi. Saya pakai istilah “MySQL” interchangeably untuk SEO purpose.

Masuk MySQL CLI:

sudo mysql -u root -p
# atau pakai socket auth (Ubuntu default):
sudo mysql

4 query live debug:

-- 1. Current threads + state
SHOW PROCESSLIST;

-- 2. Cumulative status counter
SHOW GLOBAL STATUS LIKE 'Connections';
SHOW GLOBAL STATUS LIKE 'Threads%';
SHOW GLOBAL STATUS LIKE 'Aborted_%';

-- 3. Config aktif
SHOW GLOBAL VARIABLES LIKE 'max_connections';
SHOW GLOBAL VARIABLES LIKE 'wait_timeout';
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';

-- 4. Tail error log dari shell (paralel)
sudo tail -f /var/log/mysql/error.log

Tool external (install sekali, pakai mingguan untuk monitor):

# mysqltuner — script Perl analisa config + suggest tuning
wget -O /usr/local/bin/mysqltuner.pl \
    https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl
chmod +x /usr/local/bin/mysqltuner.pl

# Percona Toolkit — collection of tools
sudo apt install -y percona-toolkit

# Stress test
sudo apt install -y apache2-utils  # untuk ab (Apache Bench)

💡 Pro tip #1: Jangan run mysqltuner.pl saat MySQL baru direstart. Tool butuh minimum 24 jam uptime untuk gather statistik akurat (counter cumulative). Ideal: run setelah 7 hari traffic normal.

3. Tahap 1: SHOW PROCESSLIST, Baca Pattern Connection Saat Crisis

Saat insiden 14:23 WIB, output SHOW PROCESSLIST saya truncate (151 row, sample 8 baris representative):

+------+----------+-----------+----------+---------+------+----------+------------------------+
| Id   | User     | Host      | db       | Command | Time | State    | Info                   |
+------+----------+-----------+----------+---------+------+----------+------------------------+
| 18230| wpklien  | localhost | wp_klien | Sleep   | 312  |          | NULL                   |
| 18231| wpklien  | localhost | wp_klien | Sleep   | 289  |          | NULL                   |
| 18232| wpklien  | localhost | wp_klien | Sleep   | 234  |          | NULL                   |
| 18255| wpklien  | localhost | wp_klien | Query   | 4    | Sending  | SELECT * FROM wp_posts |
| 18260| wpklien  | localhost | wp_klien | Query   | 2    | Locked   | UPDATE wp_options ...  |
| 18267| wpklien  | localhost | wp_klien | Sleep   | 178  |          | NULL                   |
| 18271| wpklien  | localhost | wp_klien | Query   | 1    | Updating | UPDATE wp_postmeta ... |
| 18278| wpklien  | localhost | wp_klien | Sleep   | 156  |          | NULL                   |
+------+----------+-----------+----------+---------+------+----------+------------------------+

Breakdown state via aggregation:

SELECT command, state, COUNT(*) AS cnt 
FROM information_schema.processlist 
GROUP BY command, state 
ORDER BY cnt DESC;

Output saat insiden:

+---------+--------------+-----+
| command | state        | cnt |
+---------+--------------+-----+
| Sleep   |              | 127 |   <- 84% threads idle!
| Query   | Sending data |  12 |
| Query   | Locked       |   4 |
| Query   | Updating     |   5 |
| Connect |              |   3 |
+---------+--------------+-----+

Anomaly interpretation:

State % Indikator
Sleep >50% Connection leak — aplikasi tidak close connection setelah query. Ditahan PHP-FPM keepalive.
Locked >20% Lock contention — transaksi panjang block other queries.
Sending data >10% Query mengirim large result set tanpa pagination — cek query plan + index.
Connecting/Authenticating >5% TCP/socket bottleneck — biasanya tanda VPS resource exhaustion.

Real finding di klien: 84% threads state Sleep. Itu connection leak, bukan traffic spike murni. Banyak connection yang dibuka saat early traffic burst tapi tidak ditutup, accumulate ke 127 connection idle sebelum traffic spike kedua hit. Spike kedua butuh 24 connection lagi → overflow.

⚠️ Catatan #1: Sleep connection tidak berarti “connection rusak”, itu connection yang sedang nunggu PHP-FPM child process kirim query berikutnya (keep-alive). Normal untuk WordPress: 30-50% Sleep di idle state. Tapi >70% Sleep di traffic peak = leak.

Screenshot terminal SHOW PROCESSLIST output dengan 151 row + tabel breakdown state (84% Sleep, 8% Query Sending, 3% Query Locked, dll)
Screenshot terminal MySQL SHOW PROCESSLIST menampilkan 151 row dengan mayoritas state Sleep persen 84 indikator connection leak aplikasi WordPress

4. Tahap 2: error.log + slow.log Audit

Error log path:

  • MariaDB Ubuntu 24.04: /var/log/mysql/error.log
  • MySQL 8.0: /var/log/mysql/error.log (sama)

Pattern signature untuk Too Many Connections:

# Cari pattern + jam onset (kapan pertama kali hit)
sudo grep "Too many connections" /var/log/mysql/error.log | head -20

# Aborted connection pattern (forced disconnect karena timeout)
sudo grep "Aborted connection" /var/log/mysql/error.log | wc -l

# OOM-killed pattern (MySQL crash karena RAM habis)
sudo journalctl -k --since "1 hour ago" | grep -i "killed process"

3.284 baris dalam 14 menit. Itu ribuan attempt connection yang ditolak, setiap attempt yang ditolak = 1 user yang lihat error 503.

Activate slow query log temporary untuk identifikasi query yang occupy connection lama:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_slow_admin_statements = 'ON';

Setelah 30 menit, analisis slow.log:

# Pakai pt-query-digest dari Percona Toolkit
sudo pt-query-digest /var/log/mysql/slow.log | head -50

Output top 3 query culprit di klien:

# Profile
# Rank Query ID                 Response time      Calls  R/Call   Item
# ==== ====================== ============= ====== ======== ===========
#    1 0xABC...123             125.4s 38.2%   840   0.149s  SELECT wp_options
#    2 0xDEF...456              98.7s 30.1%   324   0.305s  SELECT wp_postmeta
#    3 0x789...012              42.1s 12.8%   128   0.329s  SELECT wp_options

Query #1 yang call 840 kali dalam 30 menit dengan rata-rata 149 ms = total 2 menit 5 detik connection occupation. Akumulasi di traffic peak = bottleneck.

💡 Pro tip #2: pt-query-digest adalah tool aggregate query, group query yang strukturnya sama (dengan parameter berbeda). Sangat berguna untuk identifikasi pattern, bukan satu query individual. Pakai mingguan untuk audit kesehatan MySQL.

5. Tahap 3: PHP-FPM Pool vs MySQL max_connections — Root Cause Tersering

Pattern paling umum di klien-klien LarhTech yang hit Too Many Connections: developer/agency setup PHP-FPM pm.max_children = 80 (untuk ladeni traffic burst) tapi MySQL max_connections = 151 (default Ubuntu). Saat traffic spike:

  • 80 PHP-FPM child spawn bersamaan
  • Setiap child buka 2-3 connection ke MySQL: main connection + AJAX call + cron callback
  • Total demand connection: 80 × 2.5 = 200 connection
  • MySQL max: 151 → overflow 49 connection

Rumus matching yang harus dipakai:

pm.max_children × estimated_connection_per_request ≤ max_connections

Untuk WordPress + WooCommerce + active cron, estimated_connection_per_request ≈ 2 (main query + 1 AJAX/cron rata-rata, didapat dari SHOW STATUS LIKE 'Threads_connected' saat traffic baseline dibagi pm.max_children aktual).

Cek aktual PHP-FPM:

grep -E "pm\.|^pm\s*=" /etc/php/8.3/fpm/pool.d/www.conf

Output klien sebelum tuning:

pm = dynamic
pm.max_children = 80
pm.start_servers = 12
pm.min_spare_servers = 8
pm.max_spare_servers = 20
pm.max_requests = 0

pm.max_children=80 × 2 = 160 > 151 (max_connections). Mismatch confirmed.

Kalkulasi RAM budget untuk VPS 2 GB:

Component Budget % Catatan
MySQL (innodb buffer + thread) 1.000 MB 50% Heaviest consumer
PHP-FPM (worker × 35 MB) 600 MB 30% Recycle via pm.max_requests
OS + Nginx + Redis + buffer 400 MB 20% Reserved
TOTAL 2.000 MB 100% Match VPS plan

Untuk PHP-FPM budget 600 MB, child rata-rata 35 MB → max_children = 600 ÷ 35 ≈ 17 static atau 35 dynamic (dengan recycle aggresif via pm.process_idle_timeout).

Untuk MySQL max_connections: 35 PHP child × 2 connection + safety margin = 80.

Itu kenapa saya TURUNKAN dari 151 → 80, bukan naikkan. Naikkan = MySQL akan OOM-killed (setiap connection cost ~10-12 MB RAM → 200 × 11 MB = 2.2 GB > 2 GB available).

Diagram visualisasi matching calculation pm.max_children × 2 ≤ max_connections + pie chart RAM budget 2 GB
Diagram visualisasi matching calculation PHP-FPM pm max children versus MySQL max connections plus pie chart RAM budget 2 GB MySQL PHP Nginx OS

⚠️ Catatan #2: Kalkulasi RAM budget di atas spesifik untuk WordPress + WooCommerce + Redis object cache. Stack Laravel/Node.js + queue worker = recalculate per stack. Sample: Laravel app dengan queue worker biasanya lebih hemat RAM PHP-FPM (rata-rata 20 MB/child) tapi butuh budget untuk supervisor + queue worker process terpisah.

6. Tahap 4: Audit Connection Leak Aplikasi (Plugin Pattern)

WordPress + WooCommerce notorious untuk plugin yang tidak close connection clean. Pattern signature:

  • AJAX endpoint long-polling (poll tiap 30 detik) tanpa close connection
  • Cron job spawn yang tidak terminate clean (orphan PHP process)
  • Sleep connection user_x dengan time >300 detik

Query investigasi:

SELECT id, user, host, db, command, time, state, 
       LEFT(info, 100) AS info_preview
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 60
ORDER BY time DESC
LIMIT 30;

Output klien (sample 5 baris top):

+-------+---------+-----------+----------+---------+------+-------+----------------------------+
| id    | user    | host      | db       | command | time | state | info_preview               |
+-------+---------+-----------+----------+---------+------+-------+----------------------------+
| 18230 | wpklien | localhost | wp_klien | Sleep   | 4214 |       | SELECT * FROM wp_options...|
| 18256 | wpklien | localhost | wp_klien | Sleep   | 3987 |       | SELECT * FROM wp_options...|
| 18281 | wpklien | localhost | wp_klien | Sleep   | 3654 |       | SELECT * FROM wp_options...|
| 18305 | wpklien | localhost | wp_klien | Sleep   | 3421 |       | SELECT * FROM wp_options...|
| 18332 | wpklien | localhost | wp_klien | Sleep   | 3198 |       | SELECT * FROM wp_options...|
+-------+---------+-----------+----------+---------+------+-------+----------------------------+

5 Sleep connection ber-jam-jam (3.000-4.000 detik = 50-70 menit) dengan last query identik: SELECT * FROM wp_options WHERE option_name LIKE 'transient_%'.

Tracing dari klien dashboard: ternyata plugin abandoned (terakhir update 2 tahun lalu) yang setup AJAX poll tiap 30 detik untuk “cek update transient cache”, tapi gak close connection di endpoint handler.

Fix sementara sambil identify plugin:

-- Drop wait_timeout dari default 28800 (8 jam!) → 60 detik
SET GLOBAL wait_timeout = 60;
SET GLOBAL interactive_timeout = 60;

Effect: MySQL paksa drop Sleep connection yang idle >60 detik. Connection leak yang tidak diawasi akan dibersihkan otomatis.

Fix permanent:

  • Identify plugin culprit via Anda audit + filter PHP-FPM access log untuk endpoint AJAX yang dipakai plugin (anonymized di sini)
  • Replace plugin atau patch handler (close mysqli connection di destructor)
  • Apply wait_timeout=60 di my.cnf permanent (Section 7)

💡 Pro tip #3: Wait timeout 60 detik AMAN untuk WordPress karena PHP-FPM child recycle dengan worker model — connection ke MySQL diopen per-request, ditutup setelah request selesai. 60 detik adalah lifetime maksimum 1 connection idle, bukan execution timeout query.

7. Tahap 5: my.cnf Final untuk RAM 2 GB (Production-Ready)

File: /etc/mysql/mariadb.conf.d/99-tuning.cnf (Ubuntu 24.04 MariaDB). Untuk MySQL 8.0: /etc/mysql/mysql.conf.d/99-tuning.cnf.

[mysqld]
# === CONNECTION LIMITS ===
# DOWN dari 151 default untuk match RAM budget
max_connections = 80
thread_cache_size = 16
wait_timeout = 60
interactive_timeout = 60
max_allowed_packet = 64M

# === INNODB BUFFER POOL (40% RAM) ===
# Biggest impact untuk reduce disk I/O
innodb_buffer_pool_size = 768M
innodb_buffer_pool_instances = 1
innodb_log_file_size = 128M
innodb_log_buffer_size = 16M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_file_per_table = 1

# === TEMP TABLE ===
tmp_table_size = 32M
max_heap_table_size = 32M

# === TABLE CACHE ===
table_open_cache = 1000
table_definition_cache = 600
open_files_limit = 5000

# === SLOW QUERY LOG (selalu aktif untuk audit) ===
slow_query_log = 1
long_query_time = 1
slow_query_log_file = /var/log/mysql/slow.log
log_slow_admin_statements = 1
log_queries_not_using_indexes = 0  # Set 1 hanya saat audit, OFF di production

# === MISC ===
performance_schema = ON
skip_name_resolve = 1   # Skip DNS lookup untuk connection (faster)

Penjelasan per setting yang counter-intuitive:

Setting Default Tuned Reasoning
max_connections 151 80 Match PHP-FPM pool + RAM budget. Naikkan = OOM.
wait_timeout 28800 (8 jam!) 60 Drop Sleep connection cepat. Cegah connection leak akumulasi.
innodb_buffer_pool_size 128M (Ubuntu) 768M 40% RAM. Cache table/index in-memory. Biggest performance gain.
innodb_flush_log_at_trx_commit 1 2 Trade durability for perf. WordPress workload acceptable (kehilangan 1 detik commit saat crash, tidak fatal).
tmp_table_size 16M 32M Cegah temp table spill ke disk. Slow query rate turun.
skip_name_resolve 0 1 Skip reverse DNS. Connection setup lebih cepat. Trade-off: hostname-based GRANT tidak work.

Apply:

# Backup config existing
sudo cp /etc/mysql/mariadb.conf.d/50-server.cnf \
        /etc/mysql/mariadb.conf.d/50-server.cnf.backup

# Edit/create file
sudo nano /etc/mysql/mariadb.conf.d/99-tuning.cnf
# Paste config block di atas, save

# Restart MariaDB
sudo systemctl restart mariadb

# Verify aktif
mysql -e "SHOW GLOBAL VARIABLES LIKE 'max_connections'; SHOW GLOBAL VARIABLES LIKE 'wait_timeout'; SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';"
Screenshot file my.cnf editor (vimnano) dengan setting innodb_buffer_pool 768M, max_connections 80, wait_timeout 60 highlighted + output mysqltuner check MAX MEMORY USAGE 1.45G 2G
Screenshot editor my cnf MariaDB dengan setting innodb buffer pool 768M max connections 80 wait timeout 60 highlighted dan output mysqltuner check max memory usage

⚠️ Catatan #3: Setelah restart MariaDB, monitor 1-2 jam ketat. Kalau ada plugin yang fail karena wait_timeout=60 (mis. plugin yang AJAX long-polling 90 detik), tweak ke 120 detik. Tapi jangan balik ke default 28800, itu undo seluruh fix.

8. Tahap 6: Matching PHP-FPM Pool — Update www.conf

File: /etc/php/8.3/fpm/pool.d/www.conf.

Penjelasan kunci:

Setting Value Reasoning
pm.max_children 35 Kalkulasi 600 MB ÷ 35 MB/child. Match max_connections = 35 × 2 + safety = 80.
pm.max_requests 500 Recycle worker setelah 500 request — cegah memory leak akumulasi (PHP extension occasional leak).
pm.process_idle_timeout 30s Kill idle worker setelah 30 detik — free RAM untuk burst. Spawn baru saat traffic naik via pm.start_servers.

Apply:

sudo systemctl reload php8.3-fpm

# Verify worker spawn
ps aux | grep "php-fpm: pool www" | wc -l   # harus minimum 8 (pm.start_servers)

Setup Nginx untuk expose FPM status (untuk monitoring):

# /etc/nginx/sites-available/yoursite.com
location ~ ^/(fpm-status|fpm-ping)$ {
    allow 127.0.0.1;
    deny all;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Lalu monitor pool real-time:

curl -s http://127.0.0.1/fpm-status | head -20

9. Validasi & Stress Test Setelah Tuning

3 tool untuk validate:

Tool 1:  mysqltuner:

perl /usr/local/bin/mysqltuner.pl --user root --pass yourpass --noconfirm

Cek section penting di output:

[--] InnoDB Buffer Pool Hit Rate: 99.8% (250M cached / 280M total)
[OK] Maximum reached memory usage: 1.45G (72.5% of installed RAM)
[OK] Maximum possible memory usage: 1.68G (84% of installed RAM)
[OK] Slow queries: 0% (0/12428)
[OK] Highest usage of available connections: 38% (31/80)
[OK] Aborted connections: 0.00% (0/12428)

Threshold success:

  • InnoDB hit rate >99%
  • Max memory usage <80% installed RAM
  • Slow queries <1%
  • Highest connection usage <70% (headroom untuk spike)

Tool 2: Apache Bench stress test:

# 1000 request, concurrent 50 (simulasi traffic 5x baseline)
ab -n 1000 -c 50 https://klien-site.com/shop/

# Lebih realistik dengan keep-alive
ab -k -n 1000 -c 50 https://klien-site.com/shop/

Selama ab jalan, monitor real-time:

watch -n 1 "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"; SHOW STATUS LIKE \"Threads_running\";'"

Output saat stress test (klien post-tuning):

+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 62    |   <- Peak, masih di bawah 80 max
| Threads_running   | 4     |   <- Active query count, healthy
+-------------------+-------+

Tool 3: Pengujian ulang skenario flash sale:

Setelah 1 minggu, klien run Black Friday minor sale (traffic 5x baseline). Hasil:

Metric Sebelum tuning Sesudah tuning
Threads_connected peak 151 (max) 62 / 80
Error 503 count 3.284 0
MySQL RES memory Unpredictable spike 420 MB – 1.8 GB Stable 980 MB
WP admin response time >30s 1.8s
MySQL crash (OOM) 3 kali/minggu 0 dalam 30 hari
Downtime 24 menit 0

💡 Pro tip #4: Run stress test di staging environment dulu, bukan production. Saya pakai snapshot VPS klien yang clone ke VPS test ($5/bulan separate) untuk simulasi sebelum apply tuning permanent. Worth $5 sebulan untuk avoid bricking production saat tuning.

Screenshot output ab + grafik monitoring Threads_connected stable 62 saat 1000 request concurrent 50 + tabel beforeafter metrics
Screenshot apache bench ab output 1000 request concurrent 50 dan grafik monitoring MySQL Threads connected stable 62 dari max 80 saat stress test traffic 5x baseline

10. Cheat Sheet: Tuning my.cnf untuk RAM 1 GB, 4 GB, 8 GB

Untuk pembaca yang VPS-nya beda size, baseline starting point. Iteratif, apply, monitor 7 hari, adjust.

Setting 1 GB 2 GB 4 GB 8 GB
max_connections 40 80 150 300
innodb_buffer_pool_size 384M 768M 1.5G 4G
innodb_log_file_size 64M 128M 256M 512M
innodb_log_buffer_size 8M 16M 32M 64M
tmp_table_size 16M 32M 64M 128M
max_heap_table_size 16M 32M 64M 128M
thread_cache_size 8 16 32 64
table_open_cache 500 1000 2000 4000
wait_timeout 60 60 60 90
PHP-FPM pm.max_children 18 35 70 150
Catatan stack WP minimal WP+Woo WP+Woo high traffic WP multisite
# === SAMPLE my.cnf untuk RAM 4 GB ===
[mysqld]
max_connections = 150
thread_cache_size = 32
wait_timeout = 60
innodb_buffer_pool_size = 1500M
innodb_buffer_pool_instances = 2
innodb_log_file_size = 256M
innodb_log_buffer_size = 32M
tmp_table_size = 64M
max_heap_table_size = 64M
table_open_cache = 2000
open_files_limit = 8000

Disclaimer: Angka baseline untuk WordPress + WooCommerce + Redis object cache. Workload Laravel/Node.js + queue worker = recalculate per stack. Jangan langsung apply tabel ini; tune incremental + monitor.

11. FAQ

Saya pakai shared hosting (cPanel), perlu tuning my.cnf?

Tidak relevant. Provider yang manage. Tapi kalau hit limit mysql.connection_limit saat traffic naik, upgrade plan atau migrasi ke VPS adalah solusi. Saya bahas migrasi di Migrasi WordPress dari Shared ke VPS dalam 90 Menit.

max_connections turun 151 → 80, ini sounds dangerous. Bukannya lebih aman naik?

Counter-intuitive: connection cost ~10-12 MB RAM/slot. 500 connection × 11 MB = 5.5 GB, server 2 GB akan OOM-killed di traffic peak. Lebih baik 80 connection yang stabil daripada 500 connection yang crash. Aturan emas: total_connection × per_connection_overhead ≤ available_RAM.

Saya pakai cPanel dengan database MySQL shared. Bisa apply tuning ini?

cPanel managed = config terkunci. Provider yang tune. Anda hanya bisa optimize query pattern dari aplikasi side: install object cache Redis, reduce query dengan plugin caching, hapus plugin yang generate >100 query/page-load.

innodb_buffer_pool 40% RAM, kenapa bukan 70% seperti rekomendasi MySQL official?

Rekomendasi 70% untuk dedicated database server. Untuk WordPress + PHP-FPM + Nginx + Redis di 1 server all-in-one VPS (kasus 90% pembaca LarhTech), 40% lebih realistis. Headroom untuk PHP-FPM 30% + OS+Nginx+Redis 20% + buffer 10%. Naikkan ke 70% di VPS 2 GB = PHP-FPM kelaparan RAM = error 502 Bad Gateway alih-alih 503.

Apa perbedaan MariaDB vs MySQL untuk WordPress?

95% interchangeable untuk WordPress. MariaDB default Ubuntu 24.04. MySQL 8.0 punya beberapa feature beda (CTE recursive lebih lengkap, window function, JSON aggregation). Tuning my.cnf 95% sama; perbedaan minor di nama variable (mis. query_cache_* masih support di MariaDB 10.11 tapi deprecated di MySQL 8.0).

Cleanup sudah, gimana monitor jangka panjang?

- mysqltuner.pl mingguan — 0 3 * * 1 perl /usr/local/bin/mysqltuner.pl --user root --pass ... --noconfirm | mail -s "mysqltuner $(hostname)" [email protected] - slow query log harian — pt-query-digest /var/log/mysql/slow.log setiap pagi -Threshold alert di monitoring — Threads_connected >80% max_connections = trigger Telegram/email. Tool: Netdata atau Prometheus + Alertmanager

12. Penutup

“Too many connections” hampir selalu simptom mismatch antara PHP-FPM pool dan MySQL max_connections, bukan MySQL terlalu kecil. Solusi yang benar = matching calculation berdasarkan RAM budget, bukan brute force naikkan limit.

Workflow tuning yang saya ikuti di klien-klien LarhTech:

  1. Diagnose: SHOW PROCESSLIST + error.log + slow.log
  2. Map RAM budget: MySQL 50% + PHP-FPM 30% + OS/Nginx/Redis 20%
  3. Match config: max_connections = pm.max_children × 2 + safety
  4. Tune incremental: 1 setting per maintenance window, monitor 7 hari, adjust
  5. Validate: mysqltuner + ab stress test + production traffic monitor

Tuning my.cnf adalah seni iteratif, bukan satu-kali-apply-selesai. Setiap workload beda, setiap traffic pattern beda. Tabel cheat sheet di Section 10 = baseline, bukan final config.

Untuk deep-dive topik terkait:

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *