Fixing Slow Zabbix DB: Practical MySQL Partition Automation (Part 4)

Ready to streamline your complex Zabbix operations?

I’ve distilled the most valuable insights from this blog into one essential guide. Take full control of your environment with the Zabbix 7 Enterprise Optimization Handbook [Mastering Hybrid Infrastructure Monitoring with a Kubernetes First Approach].

👉 Get your PDF copy here: https://jikimy.gumroad.com/l/zabbixmaster


— Automating Daily and Monthly Partitions with Cron and Shell Scripts


Overview

This article explains how to automatically maintain and manage the partition structure created in Part 3.
Because Zabbix collects a massive amount of data, manually adding or dropping partitions every day or month is inefficient and error-prone.

Here, we implement full automation using two maintenance scripts:

  • /usr/local/bin/zbx-part-history.sh — creates daily (history) partitions and drops data older than 90 days.
  • /usr/local/bin/zbx-part-trends.sh — creates monthly (trends) partitions and drops data older than 12 months.

1) Prerequisites

  • Steps 1 – 3 completed (partition structure applied for history and trends)
  • MySQL user: zabbix
  • /data/history and /data/trends directories exist
  • All operations run in UTC (the cron timezone must be fixed to UTC)

2) Daily Automation Script for History

File path: /usr/local/bin/zbx-part-history.sh

#!/usr/bin/env bash
set -euo pipefail

# ===== Basic Settings =====
DB="zabbix"
DB_USER="zabbix"
DB_PASS="your_database_password_here"
DB_HOST="127.0.0.1"
TARGET_DIR="/data/history"
DAYS_KEEP=90   # Retention in days

MYSQL() {
  mysql -u"${DB_USER}" -p"${DB_PASS}" -h "${DB_HOST}" --protocol=TCP "${DB}" -Nse "$@" 2>/dev/null
}

# ===== Calculate Today's UTC Boundary =====
TODAY_UTC=$(date -u +%Y-%m-%d)
CURRENT_EPOCH=$(date -u -d "${TODAY_UTC} 00:00:00" +%s)
NEXT_BOUNDARY_EPOCH=$((CURRENT_EPOCH + 86400))   # Next midnight
NEXT_TAG=$(date -u -d "@${CURRENT_EPOCH}" +%Y%m%d)

# ===== Check for Existing Partition =====
EXISTS=$(MYSQL "
  SELECT COUNT(*) FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='history'
    AND PARTITION_NAME='p${NEXT_TAG}';
")

# ===== Create New Partition if Missing =====
if [[ "${EXISTS:-0}" -eq 0 ]]; then
  MYSQL "
    ALTER TABLE ${DB}.history
    REORGANIZE PARTITION pMAX INTO (
      PARTITION p${NEXT_TAG}
        VALUES LESS THAN (${NEXT_BOUNDARY_EPOCH})
        DATA DIRECTORY='${TARGET_DIR}',
      PARTITION pMAX VALUES LESS THAN (MAXVALUE)
        DATA DIRECTORY='${TARGET_DIR}'
    );
  "
fi

EXISTS_UINT=$(MYSQL "
  SELECT COUNT(*) FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='history_uint'
    AND PARTITION_NAME='p${NEXT_TAG}';
")

if [[ "${EXISTS_UINT:-0}" -eq 0 ]]; then
  MYSQL "
    ALTER TABLE ${DB}.history_uint
    REORGANIZE PARTITION pMAX INTO (
      PARTITION p${NEXT_TAG}
        VALUES LESS THAN (${NEXT_BOUNDARY_EPOCH})
        DATA DIRECTORY='${TARGET_DIR}',
      PARTITION pMAX VALUES LESS THAN (MAXVALUE)
        DATA DIRECTORY='${TARGET_DIR}'
    );
  "
fi

# ===== Drop Old Partitions Automatically =====
CUTOFF_EPOCH=$(date -u -d "${DAYS_KEEP} days ago 00:00:00" +%s)

TO_DROP=$(MYSQL "
  SELECT GROUP_CONCAT(PARTITION_NAME)
  FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='history'
    AND PARTITION_NAME REGEXP '^p[0-9]{8}$'
    AND PARTITION_DESCRIPTION < ${CUTOFF_EPOCH};
" || true)

if [[ -n "${TO_DROP:-}" && "${TO_DROP}" != "NULL" ]]; then
  MYSQL "ALTER TABLE ${DB}.history DROP PARTITION ${TO_DROP};"
fi

TO_DROP2=$(MYSQL "
  SELECT GROUP_CONCAT(PARTITION_NAME)
  FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='history_uint'
    AND PARTITION_NAME REGEXP '^p[0-9]{8}$'
    AND PARTITION_DESCRIPTION < ${CUTOFF_EPOCH};
" || true)

if [[ -n "${TO_DROP2:-}" && "${TO_DROP2}" != "NULL" ]]; then
  MYSQL "ALTER TABLE ${DB}.history_uint DROP PARTITION ${TO_DROP2};"
fi

Grant execution permission:

# chmod +x /usr/local/bin/zbx-part-history.sh

3) Monthly Automation Script for Trends

File path: /usr/local/bin/zbx-part-trends.sh

#!/usr/bin/env bash
set -euo pipefail

# ===== Basic Settings =====
DB="zabbix"
DB_USER="zabbix"
DB_PASS="your_database_password_here"
DB_HOST="127.0.0.1"
TARGET_DIR="/data/trends"
MONTHS_KEEP=12   # Retention in months

MYSQL() {
  mysql -u"${DB_USER}" -p"${DB_PASS}" -h "${DB_HOST}" --protocol=TCP "${DB}" -Nse "$@"
}

# ===== Calculate Month Boundaries =====
CURRENT_MONTH=$(date -u +%Y-%m-01)
CURRENT_EPOCH=$(date -u -d "${CURRENT_MONTH}" +%s)
NEXT_MONTH_EPOCH=$(date -u -d "${CURRENT_MONTH} +1 month" +%s)
NEXT_TAG=$(date -u -d "${CURRENT_MONTH}" +%Y%m)

# ===== Create New Partition if Missing =====
EXISTS=$(MYSQL "
  SELECT COUNT(*) FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='trends'
    AND PARTITION_NAME='p${NEXT_TAG}';
")

if [[ "${EXISTS:-0}" -eq 0 ]]; then
  MYSQL "
    ALTER TABLE ${DB}.trends
    REORGANIZE PARTITION pMAX INTO (
      PARTITION p${NEXT_TAG}
        VALUES LESS THAN (${NEXT_MONTH_EPOCH})
        DATA DIRECTORY='${TARGET_DIR}',
      PARTITION pMAX VALUES LESS THAN (MAXVALUE)
        DATA DIRECTORY='${TARGET_DIR}'
    );
  "
fi

EXISTS_UINT=$(MYSQL "
  SELECT COUNT(*) FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='trends_uint'
    AND PARTITION_NAME='p${NEXT_TAG}';
")

if [[ "${EXISTS_UINT:-0}" -eq 0 ]]; then
  MYSQL "
    ALTER TABLE ${DB}.trends_uint
    REORGANIZE PARTITION pMAX INTO (
      PARTITION p${NEXT_TAG}
        VALUES LESS THAN (${NEXT_MONTH_EPOCH})
        DATA DIRECTORY='${TARGET_DIR}',
      PARTITION pMAX VALUES LESS THAN (MAXVALUE)
        DATA DIRECTORY='${TARGET_DIR}'
    );
  "
fi

# ===== Drop Old Monthly Partitions =====
CUTOFF_EPOCH=$(date -u -d "${CURRENT_MONTH} -${MONTHS_KEEP} months" +%s)

TO_DROP=$(MYSQL "
  SELECT GROUP_CONCAT(PARTITION_NAME)
  FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='trends'
    AND PARTITION_NAME REGEXP '^p[0-9]{6}$'
    AND PARTITION_DESCRIPTION < ${CUTOFF_EPOCH};
" || true)

if [[ -n "${TO_DROP:-}" && "${TO_DROP}" != "NULL" ]]; then
  MYSQL "ALTER TABLE ${DB}.trends DROP PARTITION ${TO_DROP};"
fi

TO_DROP2=$(MYSQL "
  SELECT GROUP_CONCAT(PARTITION_NAME)
  FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='trends_uint'
    AND PARTITION_NAME REGEXP '^p[0-9]{6}$'
    AND PARTITION_DESCRIPTION < ${CUTOFF_EPOCH};
" || true)

if [[ -n "${TO_DROP2:-}" && "${TO_DROP2}" != "NULL" ]]; then
  MYSQL "ALTER TABLE ${DB}.trends_uint DROP PARTITION ${TO_DROP2};"
fi

Grant execution permission:

# chmod +x /usr/local/bin/zbx-part-trends.sh

4) Cron Registration

Create the cron file:

# cat >/etc/cron.d/zbx-partition <<'CRON'
CRON_TZ=UTC
5 1 * * * root /usr/local/bin/zbx-part-history.sh >/var/log/zbx-part-history.log 2>&1
10 1 1 * * root /usr/local/bin/zbx-part-trends.sh  >/var/log/zbx-part-trends.log  2>&1
CRON

# chmod 644 /etc/cron.d/zbx-partition
# chown root:root /etc/cron.d/zbx-partition

5) Execution Summary

ScriptSchedule (UTC)ExecutorMySQL UserFunction
zbx-part-history.shDaily 01:05root (cron)zabbixAdds next-day partitions + drops data older than 90 days
zbx-part-trends.sh1st day of month 01:10root (cron)zabbixAdds next-month partitions + drops data older than 12 months

Log files:

  • /var/log/zbx-part-history.log
  • /var/log/zbx-part-trends.log

6) Verification Checklist

  • Confirm that /data/history and /data/trends contain files named like pYYYYMMDD.ibd and pYYYYMM.ibd.
  • Run SHOW CREATE TABLE zabbix.history\G and verify that new partitions appear.
  • Check the cron execution status with: grep CRON /var/log/syslog

7) Next Part Preview

In Part 5, we’ll disable the Zabbix Housekeeper process and tune server parameters such as
CacheSize, HistoryCacheSize, TrendCacheSize, and StartDBSyncers to balance Zabbix’s internal cache with MySQL I/O load for maximum performance.

🛠 마지막 수정일: 2025.12.12

ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. 본문 및 이미지를 무단 복제·배포할 수 없습니다. 공유 시 반드시 원문 링크를 명시해 주세요.
ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. All rights reserved. Unauthorized copying or redistribution of the text and images is prohibited. When sharing, please include the original source link.

💡 도움이 필요하신가요?
Zabbix, Kubernetes, 그리고 다양한 오픈소스 인프라 환경에 대한 구축, 운영, 최적화, 장애 분석, 광고 및 협업 제안이 필요하다면 언제든 편하게 연락 주세요.

📧 Contact: jikimy75@gmail.com
💼 Service: 구축 대행 | 성능 튜닝 | 장애 분석 컨설팅

📖 E-BooK [PDF] 전자책 (Gumroad): Zabbix 엔터프라이즈 최적화 핸드북
블로그에서 다룬 Zabbix 관련 글들을 기반으로 실무 중심의 지침서로 재구성했습니다. 운영 환경에서 바로 적용할 수 있는 최적화·트러블슈팅 노하우까지 모두 포함되어 있습니다.


💡 Need Professional Support?
If you need deployment, optimization, or troubleshooting support for Zabbix, Kubernetes, or any other open-source infrastructure in your production environment, or if you are interested in sponsorships, ads, or technical collaboration, feel free to contact me anytime.

📧 Email: jikimy75@gmail.com
💼 Services: Deployment Support | Performance Tuning | Incident Analysis Consulting

📖 PDF eBook (Gumroad): Zabbix Enterprise Optimization Handbook
A single, production-ready PDF that compiles my in-depth Zabbix and Kubernetes monitoring guides.