MySQL Exporter: Monitor MySQL Server ด้วย Prometheus

การติดตาม MySQL Server ให้ทำงานได้อย่างมีประสิทธิภาพต้องอาศัย Metrics ที่ครบถ้วนและต่อเนื่อง ทั้ง Query Performance, Connection Usage, InnoDB Buffer Pool, Replication Lag และอื่น ๆ อีกมาก การเก็บข้อมูลเหล่านี้ด้วยตนเองจากคำสั่ง SHOW STATUS หรือ information_schema อาจใช้ได้กับการ debug เฉพาะหน้า แต่ไม่เหมาะกับการติดตามระยะยาว เพราะไม่มี Historical Data สำหรับฐานข้อมูลและไม่มีระบบแจ้งเตือนอัตโนมัติ

mysqld_exporter คือเครื่องมือ Open Source จากทีม Prometheus ที่แปลง Metrics ของ MySQL ให้อยู่ในรูปแบบที่ Prometheus สามารถดึงไปเก็บได้ ผสานกับ Grafana เพื่อทำ Dashboard และ Alertmanager สำหรับส่งแจ้งเตือน ทำให้ได้ระบบ Observability ครบวงจรสำหรับฐานข้อมูล บทความนี้จะอธิบายการติดตั้ง ตั้งค่า และออกแบบ Alert Rules สำหรับ mysqld_exporter แบบ Production-ready

mysqld_exporter คืออะไร

mysqld_exporter เป็น Prometheus Exporter ที่เขียนด้วยภาษา Go ทำหน้าที่เชื่อมต่อกับฐานข้อมูลผ่าน TCP หรือ Unix Socket จากนั้นรันคำสั่ง SQL เช่น SHOW GLOBAL STATUS, SHOW GLOBAL VARIABLES, SELECT จาก information_schema และ performance_schema เพื่อดึง Metrics แล้ว expose ออกเป็น HTTP Endpoint (ค่า default คือพอร์ต 9104) ให้ Prometheus Server scrape ตามรอบเวลาที่กำหนด

ข้อดีหลักของการใช้ exporter แทนการเขียน script เอง คือมี Metrics Naming มาตรฐาน (เช่น mysql_global_status_threads_connected) ทำให้สามารถใช้ Dashboard และ Alert Rules สำเร็จรูปจาก Grafana Labs ได้ทันที ลดเวลาในการ setup จากหลายสัปดาห์เหลือไม่กี่ชั่วโมง

การเตรียม Database User สำหรับ Exporter

ก่อนติดตั้ง exporter ต้องสร้าง User ในฐานข้อมูลที่มีสิทธิ์จำกัดเฉพาะที่จำเป็น ตามหลัก Principle of Least Privilege ห้ามใช้ root หรือ user ที่มีสิทธิ์ ALL PRIVILEGES เด็ดขาด เพราะหาก exporter ถูก compromise จะทำให้ข้อมูลเสียหายทั้งหมด

CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'StrongPassword!@#'
  WITH MAX_USER_CONNECTIONS 3;

GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.*
  TO 'exporter'@'localhost';

FLUSH PRIVILEGES;

คำอธิบายสิทธิ์ที่ให้: PROCESS ใช้สำหรับ SHOW PROCESSLIST, REPLICATION CLIENT ใช้สำหรับ SHOW SLAVE STATUS / SHOW MASTER STATUS, SELECT ใช้สำหรับอ่านข้อมูลจาก performance_schema และ information_schema MAX_USER_CONNECTIONS=3 ป้องกัน exporter เปิด connection เกินจำเป็น

วิธีที่ 1: ติดตั้งจาก Binary

# ดาวน์โหลด binary
VERSION="0.15.1"
wget https://github.com/prometheus/mysqld_exporter/releases/download/v${VERSION}/mysqld_exporter-${VERSION}.linux-amd64.tar.gz

tar xvzf mysqld_exporter-${VERSION}.linux-amd64.tar.gz
sudo mv mysqld_exporter-${VERSION}.linux-amd64/mysqld_exporter /usr/local/bin/
sudo chmod +x /usr/local/bin/mysqld_exporter

# สร้าง user สำหรับรัน exporter
sudo useradd --no-create-home --shell /bin/false mysqld_exporter

# สร้างไฟล์ config สำหรับ credentials
sudo tee /etc/mysqld_exporter.cnf <<EOF
[client]
user=exporter
password=StrongPassword!@#
host=localhost
EOF

sudo chmod 600 /etc/mysqld_exporter.cnf
sudo chown mysqld_exporter:mysqld_exporter /etc/mysqld_exporter.cnf

สร้าง systemd Service

sudo tee /etc/systemd/system/mysqld_exporter.service <<EOF
[Unit]
Description=Prometheus MySQL Exporter
After=network.target mysqld.service

[Service]
Type=simple
User=mysqld_exporter
Group=mysqld_exporter
ExecStart=/usr/local/bin/mysqld_exporter \\
  --config.my-cnf=/etc/mysqld_exporter.cnf \\
  --collect.info_schema.innodb_metrics \\
  --collect.info_schema.innodb_tablespaces \\
  --collect.info_schema.processlist \\
  --collect.perf_schema.eventsstatements \\
  --collect.perf_schema.file_events \\
  --collect.slave_status \\
  --web.listen-address=:9104
Restart=always

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now mysqld_exporter
sudo systemctl status mysqld_exporter

วิธีที่ 2: ติดตั้งด้วย Docker Compose

version: '3.8'

services:
  mysqld_exporter:
    image: prom/mysqld-exporter:v0.15.1
    container_name: mysqld_exporter
    restart: unless-stopped
    ports:
      - "9104:9104"
    command:
      - '--mysqld.username=exporter'
      - '--collect.info_schema.innodb_metrics'
      - '--collect.info_schema.innodb_tablespaces'
      - '--collect.info_schema.processlist'
      - '--collect.perf_schema.eventsstatements'
      - '--collect.slave_status'
    environment:
      MYSQLD_EXPORTER_PASSWORD: StrongPassword!@#
    networks:
      - monitoring
    depends_on:
      - mysql

networks:
  monitoring:
    external: true

Collectors ที่สำคัญ

mysqld_exporter มี collectors หลายกลุ่ม บางกลุ่มเปิดเป็น default บางกลุ่มต้องเปิดเพิ่มเติมผ่าน command flag การเปิดทุก collector อาจสร้างภาระให้ฐานข้อมูลเกินจำเป็น ดังนั้นควรเลือกเปิดเฉพาะที่ต้องใช้

CollectorDefaultข้อมูลที่เก็บ
global_statusเปิดSHOW GLOBAL STATUS (Threads, Queries, Connections)
global_variablesเปิดSHOW GLOBAL VARIABLES (max_connections, buffer_pool_size)
info_schema.innodb_metricsปิดInnoDB Buffer Pool, Row Lock, I/O
info_schema.processlistปิดจำนวน query ที่กำลังรัน แยกตาม state/user
perf_schema.eventsstatementsปิดTop queries by latency, rows examined
slave_statusเปิดReplication lag และ error (เฉพาะ slave)
info_schema.tablesปิดขนาด table แต่ละตัว (หนัก — ใช้เฉพาะเมื่อจำเป็น)

เชื่อม Prometheus กับ Exporter

แก้ไขไฟล์ prometheus.yml เพิ่ม job สำหรับ scrape mysqld_exporter กำหนด interval ตามความละเอียดที่ต้องการ ปกติ 15-30 วินาทีเพียงพอสำหรับการติดตามฐานข้อมูล

scrape_configs:
  - job_name: 'mysql'
    scrape_interval: 15s
    static_configs:
      - targets:
          - 'db-primary.internal:9104'
          - 'db-replica-1.internal:9104'
          - 'db-replica-2.internal:9104'
        labels:
          env: 'production'
          role: 'mysql'
    relabel_configs:
      - source_labels: [__address__]
        regex: '([^:]+):.*'
        target_label: instance
        replacement: '$1'

หลังแก้ไขแล้ว reload Prometheus ด้วย SIGHUP หรือ curl -X POST localhost:9090/-/reload จากนั้นเข้า Prometheus UI ไปที่ Status -> Targets ตรวจสอบว่า target ขึ้นเป็น UP ทุกตัว

Alert Rules ที่แนะนำ

การตั้ง Alert ที่ดีต้องมีทั้ง Warning (เตือนก่อนถึงขั้นวิกฤต) และ Critical (ต้องแก้ทันที) พร้อมกำหนด for duration เพื่อป้องกัน false positive จาก metric spike ชั่วคราว ต่อไปนี้คือ Alert Rules ที่ใช้งานจริงใน Production สำหรับการติดตามสถานะเซิร์ฟเวอร์ฐานข้อมูล

groups:
  - name: mysql_alerts
    interval: 30s
    rules:
      - alert: MySQLDown
        expr: mysql_up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "MySQL instance down ({{ $labels.instance }})"
          description: "mysqld_exporter ไม่สามารถเชื่อมต่อ MySQL ได้เกิน 1 นาที"

      - alert: MySQLHighConnections
        expr: |
          (mysql_global_status_threads_connected
           / mysql_global_variables_max_connections) > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MySQL connections สูง ({{ $labels.instance }})"
          description: "ใช้ connection {{ $value | humanizePercentage }} ของ max_connections"

      - alert: MySQLSlowQueries
        expr: |
          rate(mysql_global_status_slow_queries[5m]) > 1
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Slow queries เพิ่มขึ้นอย่างต่อเนื่อง"
          description: "{{ $value }} slow queries/sec ในช่วง 5 นาทีที่ผ่านมา"

      - alert: MySQLInnoDBBufferPoolLowHitRate
        expr: |
          (rate(mysql_global_status_innodb_buffer_pool_reads[5m])
           / rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])) > 0.05
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "InnoDB Buffer Pool hit rate ต่ำ"
          description: "Miss rate = {{ $value | humanizePercentage }} ควรเพิ่ม innodb_buffer_pool_size"

      - alert: MySQLReplicationLag
        expr: mysql_slave_lag_seconds > 30
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Replication lag สูง ({{ $labels.instance }})"
          description: "Replica ล่าช้ากว่า master {{ $value }} วินาที"

      - alert: MySQLReplicationBroken
        expr: mysql_slave_status_slave_sql_running == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Replication หยุดทำงาน ({{ $labels.instance }})"
          description: "Slave SQL thread ไม่ทำงาน ต้องตรวจสอบทันที"

      - alert: MySQLTableLocksWaited
        expr: |
          rate(mysql_global_status_table_locks_waited[5m]) > 5
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Table lock wait สูง"
          description: "เกิด table lock contention {{ $value }} ครั้ง/วินาที"

PromQL Queries ที่ใช้บ่อย

รวม PromQL queries ที่นักพัฒนาและ DBA ใช้งานบ่อยในการวิเคราะห์ประสิทธิภาพและสุขภาพของเซิร์ฟเวอร์ฐานข้อมูล

# QPS (Queries Per Second)
rate(mysql_global_status_queries[5m])

# Connection usage (%)
mysql_global_status_threads_connected
  / mysql_global_variables_max_connections * 100

# InnoDB Buffer Pool Hit Ratio (%)
(1 - rate(mysql_global_status_innodb_buffer_pool_reads[5m])
     / rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])) * 100

# Disk I/O: rows read/written per second
rate(mysql_global_status_innodb_rows_read[5m])
rate(mysql_global_status_innodb_rows_inserted[5m])

# Uptime ของ DB Server (วินาที)
mysql_global_status_uptime

# Table size (MB) จาก info_schema.tables collector
mysql_info_schema_table_size / 1024 / 1024

# Replica lag
mysql_slave_lag_seconds

Grafana Dashboard สำเร็จรูป

ไม่ต้องสร้าง Dashboard เอง สามารถ import จาก Grafana Labs ได้ทันที Dashboard ยอดนิยมที่แนะนำมีดังนี้

  • ID 7362 – MySQL Overview: Dashboard มาตรฐานจาก Percona แสดงทุก metric หลัก ครอบคลุม connections, QPS, buffer pool, replication, query cache
  • ID 14057 – Exporter Quickstart and Dashboard: Dashboard ที่ออกแบบเฉพาะสำหรับ exporter version ใหม่
  • ID 12826 – DB Details: รายละเอียดลึก เช่น InnoDB row operations, lock waits, temp tables

วิธี import เพียงเข้า Grafana -> Dashboards -> Import ใส่ ID แล้วเลือก Prometheus data source ที่เชื่อมกับ mysqld_exporter ระบบจะสร้าง Dashboard พร้อมใช้ทันทีภายในไม่กี่วินาที

mysql_up = 0 หลังติดตั้งใหม่

ปัญหาส่วนใหญ่เกิดจาก authentication ตรวจสอบ 3 จุดหลัก ได้แก่ password ในไฟล์ config ถูกต้องหรือไม่, user exporter ถูกสร้างและมีสิทธิ์ครบหรือไม่ (ดูด้วย SHOW GRANTS FOR ‘exporter’@’localhost’), และ hostname ใน my.cnf ตรงกับ hostname ที่ MySQL อนุญาตหรือไม่ (localhost ไม่เท่ากับ 127.0.0.1 ใน MySQL authentication)

Scrape timeout เพราะ collector หนัก

หากเปิด collector ที่หนักเช่น info_schema.tables บนฐานข้อมูลขนาดใหญ่ อาจทำให้ exporter ตอบกลับเกิน scrape_timeout ของ Prometheus แก้ด้วย 3 วิธี ได้แก่ ปิด collector ที่หนัก, เพิ่ม scrape_timeout เป็น 30s-60s, หรือแยก job ออกเป็น 2 job (light และ heavy) ที่ interval ต่างกัน

Metrics หาย/ไม่ครบ

หากบาง metric ไม่ปรากฏใน Prometheus ตรวจสอบว่า performance_schema ถูกเปิดในฐานข้อมูลหรือไม่ (SHOW VARIABLES LIKE ‘performance_schema’) หากปิดอยู่ต้องเปิดในไฟล์ my.cnf แล้ว restart service และสำหรับ InnoDB metrics ต้องเปิด innodb_monitor_enable ด้วย

# ใน my.cnf
[mysqld]
performance_schema = ON
innodb_monitor_enable = all

Best Practices

  • สร้าง exporter user แยกต่างหาก ให้สิทธิ์เฉพาะ PROCESS, REPLICATION CLIENT, SELECT — ห้ามใช้ root
  • จำกัด MAX_USER_CONNECTIONS = 3 ป้องกัน exporter กิน connection เกินความจำเป็น
  • เปิด collector เท่าที่จำเป็น — หลีกเลี่ยง info_schema.tables ถ้าฐานข้อมูลมี table หลายพันตัว
  • รัน exporter บน host เดียวกับฐานข้อมูล (localhost) ลด latency และไม่ต้องเปิด firewall
  • ใช้ HTTPS/TLS และ Basic Auth หากต้อง expose exporter ข้าม network
  • เชื่อม metrics เข้า Alertmanager และส่งไป Slack/LINE/Email/PagerDuty ตาม severity
  • เก็บข้อมูลอย่างน้อย 30 วันสำหรับ trend analysis และ capacity planning
  • Review Alert Rules ทุกไตรมาส ปรับ threshold ตาม baseline ที่เปลี่ยนไป

สรุป

mysqld_exporter เป็นเครื่องมือสำคัญที่ช่วยให้การติดตามเซิร์ฟเวอร์ฐานข้อมูลเปลี่ยนจากงานที่ต้องทำด้วยมือเป็นระบบ Observability อัตโนมัติ ผสานกับ Prometheus และ Grafana ให้ Dashboard ครบถ้วน รวมถึง Alertmanager สำหรับแจ้งเตือนก่อนระบบล่ม หัวใจของการใช้งานที่ดีคือการสร้าง user ที่มีสิทธิ์จำกัด เปิดเฉพาะ collector ที่จำเป็น และตั้ง Alert Rules ที่มีทั้ง Warning และ Critical พร้อม for duration ที่เหมาะสม

ทีมที่ต้องดูแลฐานข้อมูลหลายเครื่อง ควรนำ exporter มา deploy ทุก instance ตั้งแต่วันแรกที่เปิดใช้งาน จะช่วยให้มี Historical Data สำหรับวิเคราะห์ปัญหาในอนาคต และสามารถทำ Capacity Planning ได้อย่างแม่นยำตามข้อมูลจริง ไม่ใช่การคาดเดา