Publicado por Sysadmin Urbano | Infraestrutura, SysOps e DevOps
Um guia prático para quem vive na linha de frente da operação de sistemas.
Particionamento no Zabbix: o passo final para domar seu banco
Capítulo 3 da série “Zabbix — da Migração ao Fine-Tuning” no Sysadmin Urbano.
Este post mostra como transformar as tabelas history* e trends* em particionadas por tempo no MySQL, migrando dados de forma segura (downtime mínimo) e automatizando a criação das próximas partições e o expurgo das antigas. É a forma mais previsível e rápida de manter o crescimento sob controle.
Resumo: criar uma cópia particionada, carregar apenas o período que você quer manter, RENAME atômico, e um script diário que cria “a partição de amanhã” e remove as antigas. Sem deletes gigantes, sem sustos.
1) Pré-requisitos e planejamento
- Banco: MySQL 8+ (MariaDB também funciona com ajustes).
- Coluna de partição:
clock(epoch, presente emhistory*etrends*). - Retenção sugerida: history = 90 dias (diária) | trends = 365 dias (mensal).
- Espaço em disco para coexistir tabela antiga + nova durante a migração.
- Janela curta (segundos) apenas para o
RENAME.
2) Migração: criar cópia particionada e trocar
2.1) History — partição diária
Exemplo com history. Repita o mesmo para history_uint, history_str, history_text, history_log.
-- A) Criar tabela particionada (inclua alguns dias + pMAX)
CREATE TABLE zabbix.history_p LIKE zabbix.history;
ALTER TABLE zabbix.history_p
PARTITION BY RANGE (clock) (
PARTITION p2025_10_10 VALUES LESS THAN (UNIX_TIMESTAMP('2025-10-11')),
PARTITION p2025_10_11 VALUES LESS THAN (UNIX_TIMESTAMP('2025-10-12')),
PARTITION p2025_10_12 VALUES LESS THAN (UNIX_TIMESTAMP('2025-10-13')),
PARTITION pMAX VALUES LESS THAN (MAXVALUE)
);
-- B) Carregar apenas o período que deseja manter (ex.: 90 dias)
INSERT /*+ MAX_EXECUTION_TIME(600000) */ INTO zabbix.history_p
SELECT * FROM zabbix.history
WHERE clock >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY));
-- C) Troca atômica (faça com zabbix-server parado apenas neste passo)
RENAME TABLE zabbix.history TO zabbix.history_old,
zabbix.history_p TO zabbix.history;
-- D) Depois, quando confirmar, descarte a antiga
DROP TABLE zabbix.history_old;
2.2) Trends — partição mensal
Exemplo com trends. Repita para trends_uint.
CREATE TABLE zabbix.trends_p LIKE zabbix.trends;
ALTER TABLE zabbix.trends_p
PARTITION BY RANGE (clock) (
PARTITION p2025_10 VALUES LESS THAN (UNIX_TIMESTAMP('2025-11-01')),
PARTITION p2025_11 VALUES LESS THAN (UNIX_TIMESTAMP('2025-12-01')),
PARTITION pMAX VALUES LESS THAN (MAXVALUE)
);
INSERT /*+ MAX_EXECUTION_TIME(600000) */ INTO zabbix.trends_p
SELECT * FROM zabbix.trends
WHERE clock >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 365 DAY));
RENAME TABLE zabbix.trends TO zabbix.trends_old,
zabbix.trends_p TO zabbix.trends;
DROP TABLE zabbix.trends_old;
Dica: se a tabela for enorme, copie por janelas (ex.: por dia/mês) com vários INSERT ... WHERE para suavizar I/O.
3) Uso diário: criar a próxima partição e remover as antigas
Depois da migração, a limpeza vira DROP PARTITION (rápido) e a prevenção vira “criar a partição do amanhã”. O script abaixo faz os dois de forma segura.
3.1) Script de manutenção automática
Salve como /opt/zbx_partitions/maintain_partitions.sh e dê chmod +x. Ajuste as variáveis no topo.
#!/usr/bin/env bash
set -euo pipefail
# ======= CONFIG =======
DB="zabbix"
MYSQL="mysql --defaults-extra-file=$HOME/.my.cnf --batch --raw"
HISTORY_TABLES=("history" "history_uint" "history_str" "history_text" "history_log")
TRENDS_TABLES=("trends" "trends_uint")
HISTORY_RETENTION_DAYS=90 # manter 90d em history*
TRENDS_RETENTION_DAYS=365 # manter 365d em trends*
# mínimo de espaço livre (em %) no filesystem do datadir
MIN_FREE_PCT=5
# ======= FUNÇÕES =======
die(){ echo "ERROR: $*" >&2; exit 1; }
check_free_space(){
local mount
mount="$(awk '$2=="/var/lib/mysql"{print $2}' /proc/mounts || echo "/")"
local pct
pct=$(df -P "$mount" | awk 'NR==2{print $5}' | tr -d '%')
local free=$((100-pct))
if (( free < MIN_FREE_PCT )); then
die "Espaço livre insuficiente em $mount (${free}% < ${MIN_FREE_PCT}%). Abortado."
fi
}
part_exists(){
local tbl=$1 part=$2
$MYSQL -N -e "SELECT 1 FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='${tbl}'
AND PARTITION_NAME='${part}'" | grep -q 1
}
add_daily_partition(){
local tbl=$1 day=$2 next=$3 # day=YYYY-MM-DD, next=YYYY-MM-DD(+1)
local pname="p${day//-/\_}"
if part_exists "$tbl" "$pname"; then
echo " - $tbl: partição $pname já existe"
return 0
fi
echo " - $tbl: criando partição $pname (< ${next})"
$MYSQL -e "ALTER TABLE ${DB}.${tbl}
REORGANIZE PARTITION pMAX INTO (
PARTITION ${pname} VALUES LESS THAN (UNIX_TIMESTAMP('${next}')),
PARTITION pMAX VALUES LESS THAN (MAXVALUE)
);"
}
add_monthly_partition(){
local tbl=$1 first_of_month=$2 first_of_next=$3 # YYYY-MM-01
local pname="p${first_of_month:0:4}_${first_of_month:5:2}"
if part_exists "$tbl" "$pname"; then
echo " - $tbl: partição $pname já existe"
return 0
fi
echo " - $tbl: criando partição $pname (< ${first_of_next})"
$MYSQL -e "ALTER TABLE ${DB}.${tbl}
REORGANIZE PARTITION pMAX INTO (
PARTITION ${pname} VALUES LESS THAN (UNIX_TIMESTAMP('${first_of_next}')),
PARTITION pMAX VALUES LESS THAN (MAXVALUE)
);"
}
drop_older_than_days(){
local tbl=$1 keep_days=$2
local cutoff_epoch
cutoff_epoch=$($MYSQL -N -e "SELECT UNIX_TIMESTAMP(DATE_SUB(CURDATE(), INTERVAL ${keep_days} DAY))")
while read -r pname pdescr; do
local less
less=$(echo "$pdescr" | sed -n "s/.*LESS THAN (\([0-9]\+\)).*/\1/p")
[ -z "$less" ] && continue
if (( less <= cutoff_epoch )); then
echo " - $tbl: DROP PARTITION $pname (boundary ${less} <= cutoff ${cutoff_epoch})"
$MYSQL -e "ALTER TABLE ${DB}.${tbl} DROP PARTITION ${pname};"
fi
done < <(
$MYSQL -N -e "SELECT PARTITION_NAME,
CONCAT('LESS THAN (',PARTITION_DESCRIPTION,')')
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA='${DB}' AND TABLE_NAME='${tbl}'
AND PARTITION_NAME IS NOT NULL
AND PARTITION_NAME!='pMAX'
ORDER BY PARTITION_DESCRIPTION"
)
}
# ======= MAIN =======
echo "== Zabbix partitions maintenance ($(date -Is)) =="
check_free_space
TODAY=$(date -u +%F)
TOMORROW=$(date -u -d "$TODAY +1 day" +%F)
FIRST_NEXT_MONTH=$(date -u -d "$(date -u +%Y-%m-01) +1 month" +%F)
FIRST_NEXT_NEXT_MONTH=$(date -u -d "$FIRST_NEXT_MONTH +1 month" +%F)
echo "-- Garantindo partição DIÁRIA de amanhã para history* --"
for t in "${HISTORY_TABLES[@]}"; do
add_daily_partition "$t" "$TOMORROW" "$(date -u -d "$TOMORROW +1 day" +%F)"
done
echo "-- Garantindo partição MENSAL seguinte para trends* (se mudança de mês) --"
for t in "${TRENDS_TABLES[@]}"; do
add_monthly_partition "$t" "$FIRST_NEXT_MONTH" "$FIRST_NEXT_NEXT_MONTH"
done
echo "-- Drop de partições antigas (retenção) --"
for t in "${HISTORY_TABLES[@]}"; do
drop_older_than_days "$t" "$HISTORY_RETENTION_DAYS"
done
for t in "${TRENDS_TABLES[@]}"; do
drop_older_than_days "$t" "$TRENDS_RETENTION_DAYS"
done
echo "OK."
3.2) Cron diário
Execute todo dia às 01:10 UTC, gerando log de manutenção.
# /etc/cron.d/zbx_partitions
10 1 * * * root /opt/zbx_partitions/maintain_partitions.sh >> /var/log/zbx_partitions.log 2>&1
4) Auditoria e verificação
4.1) Listar partições atuais
SELECT PARTITION_NAME, PARTITION_DESCRIPTION
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA='zabbix' AND TABLE_NAME='history'
ORDER BY PARTITION_DESCRIPTION;
4.2) Conferir se existe pMAX
SELECT COUNT(*) FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA='zabbix' AND TABLE_NAME='history' AND PARTITION_NAME='pMAX';
4.3) Tamanho por partição (aproximação)
SELECT PARTITION_NAME,
ROUND(DATA_LENGTH/1024/1024,2) AS data_mb,
ROUND(INDEX_LENGTH/1024/1024,2) AS idx_mb
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA='zabbix' AND TABLE_NAME='history'
ORDER BY PARTITION_NAME;
5) Boas práticas
- Backup antes da migração e antes de grandes drops.
- Janela curta apenas para o
RENAME; a cópia pode rodar antes. - Retenção do Housekeeper alinhada com o plano de partições (para não competir).
- UTC no script (evita dores com DST); se preferir local, ajuste os
dates. - Monitorar espaço e I/O: o script já barra se faltar disco.
Sobre o Sysadmin Urbano
O Sysadmin Urbano nasceu da vivência real no front das operações de infraestrutura moderna. Aqui falamos de servidores, containers, automação, boas práticas e também dos desafios invisíveis da rotina de quem mantém sistemas vivos. Sem fórmulas mágicas, sem tutoriais pela metade — apenas conteúdo prático, direto e feito para quem sabe que a TI é tanto técnica quanto sobrevivência.
Gostou deste conteúdo?
Siga o Sysadmin Urbano para mais artigos técnicos sobre Infraestrutura, SysOps e DevOps.

Nenhum comentário:
Postar um comentário