ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zabbix 3.0.10数据库膨胀清理:history_uint与alerts大表瘦身实战

Zabbix 3.0.10数据库膨胀清理:history_uint与alerts大表瘦身实战 一个跑了一年多的 Zabbix 3.0.10数据库膨胀几乎必然会发生。尤其当你打开 MySQL 看到history_uint和alerts几个表占了十几个 GB前端查询历史告警越来越慢甚至在 Zabbix 前端里点开“问题”都会转圈。这个版本不像后面 4.0/5.0 自带那么完善的分区支持维护老版本清理历史告警问题数据几乎是必修课。写这篇文章是因为我最近刚帮一个朋友处理了同样的场景Zabbix 3.0.10数据库后端是 MySQL某天磁盘告警一查全是告警历史表和问题事件表最后用一套手工脚本安全把几个大表从 60GB 缩到 20GB 以内。下面把整个思路、具体 SQL、自动脚本和踩过的坑都整理出来供同样被困在 3.0.x 老版本里的运维参考。1. 先搞清楚 Zabbix 3.0.10 的数据都放在哪儿很多人在清理前容易犯一个错误上来就DELETE FROM history;结果发现数据量没降多少还误删了想要的趋势数据。Zabbix 里的“历史数据”和“告警数据”其实是两套体系先分清楚后面才不会把该留的也删了。1.1 监控历史数据与告警数据是两套体系history系列表存的是监控项采集到的原始值比如 CPU、内存、流量、磁盘 IO。Zabbix 按数值类型拆成了多张表history存浮点数history_uint存整数history_str存短字符串history_text存长文本history_log存日志文件。这些表是增长最快的大户但它们属于“监控历史”不完全等于告警数据。另一套是与“问题/告警”相关的表。events表记录触发器状态变化的事件也就是问题产生、恢复、发现、自动注销等完整事件流alerts表记录每一次告警动作的发送情况比如发邮件、发脚本、标记动作执行结果problem表是 Zabbix 3.0 开始新增的“问题”视图表前端“问题”页面能看到的内容基本都来自它。你说的“历史告警问题数据”实际指的就是alerts、events、problem以及它们关联的acknowledges这类表。从这里可以得出一个结论如果是磁盘空间告急清理优先级最高的是history和trends如果是要让前端“问题/告警”历史变清爽重心则在alerts、events、problem。两者可以一起做但 SQL 条件不能混着写。1.2 3.0.10 版本的表结构特征Zabbix 3.0.10 的表结构和高版本有区别。在 3.0.x 里events表字段大概是eventid、source、object、objectid、value、clock、ns、value_changed、acknowledged。problem表里会保存与问题状态相关的r_clock、r_eventid、r_ns这类恢复时间字段。alerts表则记录了eventid、mediatypeid、sendto、status、retries、error等动作发送信息。如果对这些字段不熟动手前先跑两条查询看一眼实际结构SHOW CREATE TABLE events; SHOW CREATE TABLE problem; SHOW CREATE TABLE alerts;把字段结构确认清楚再写删除 SQL能少走很多弯路。老版本的官方文档和前端页面不一定对得上实际库里到底有没有某个列以SHOW CREATE TABLE为准。下面这张表是 3.0.10 里最常见的几类大表以及它们在清理时的定位表名存储内容数据量增长特点清理建议history/history_uint监控项原始采集值每秒都可能写入增长最猛按保留天数清理严格控制history_str/history_text长文本型监控项字符型主机名、告警信息多视情况保留更短周期trends/trends_uint小时级趋势数据相对稳定但久了也大建议保留一年以上alerts告警动作发送记录依赖动作配置和告警频率保留 90~180 天即可events所有触发器/发现事件每次问题产生、恢复都插入保留 90~180 天即可problem前端问题列表数据如果长期不清理会留大量已恢复记录只保留当前问题和最近历史问题auditlog用户操作日志登录、改配置都写按需保留这张表也解释了为什么我们待会要按“先删子表、再删主表”的顺序而不是把events一下清掉。2. 动手清理前先想清楚保留策略和备份方案这一步别省。Zabbix 3.0.10 的数据库清理不像高版本有可视化按钮全得靠 SQL。一旦删错可能把正在告警的问题也删了或者把审计需要的历史记录弄丢。2.1 保留多久合适结合容量和审计需求没有统一的天数我的建议是history/history_uint保留 7~30 天。如果 Grafana 有长期趋势需求可以通过trends来补不用靠原始历史。trends/trends_uint保留 365 天左右个别需要长期回溯的监控项可以在前端单独调整。alerts和events保留 90~180 天。如果公司有告警审计要求千万不能小于 180 天否则审计要历史记录时很被动。problem只保留当前未恢复问题和近 90 天已恢复问题避免前端问题列表越翻越长。我见过一个极端案例某环境每天产生 8~10 万条告警动作记录一个月alerts表就增加了近 300 万行。算下来一年光alerts就超 3000 万行按单行 200 字节估算光这一张表就要 6GB 以上还不算索引。如果不定保留策略清理速度肯定赶不上增长速度。2.2 备份与影响范围评估清理前要做备份但大数据库做全量mysqldump不现实。比较稳妥的做法是如果有从库或备份系统先确认最近一次备份可用再在从库上做一次快照验证。单库超过 50GB 时优先在从库上把备份导出到一个独立文件而不是在主库上mysqldump。如果没有从库至少要针对要删除的alerts、events、problem表做一次独立备份比如导出这些表今天之前的数据。清理期间的影响范围也要提前评估。alerts、events都是大表DELETE 时会占用较多数据库 IO可能导致 Zabbix Server 写入变慢、前端查询变卡甚至告警动作发送延迟。所以一定要选在低峰期比如凌晨 2 点到 5 点并通告相关同事避免误报。2.3 检查当前数据库引擎和空间占用在清理前先定位最占空间的表。MySQL 后端可以用这段 SQLSELECT table_name AS 表名, engine AS 引擎, ROUND((data_length index_length) / 1024 / 1024, 2) AS 总大小MB, table_rows AS 估算行数 FROM information_schema.tables WHERE table_schema zabbix ORDER BY (data_length index_length) DESC LIMIT 20;如果后端是 PostgreSQL换这条SELECT relname AS table_name, pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size, n_live_tup AS live_rows FROM pg_class c JOIN pg_stat_user_tables t ON c.relname t.relname WHERE c.relname NOT LIKE pg_% ORDER BY pg_total_relation_size(c.oid) DESC LIMIT 20;另外要确认innodb_file_per_table参数。MySQL 5.6/5.7 默认是开启的每个 InnoDB 表有独立的.ibd文件这样删除后执行OPTIMIZE TABLE才能释放磁盘空间。如果没开所有表都挤在共享表空间里删除后即便OPTIMIZE也很难缩容。3.0.10 时代很多环境还在用 MySQL 5.5这一点尤其要留意。3. 手工清理历史告警问题数据的标准流程清理告警问题数据我建议按这个顺序执行先删alerts再处理problem里的已恢复历史记录最后删events。原因很简单alerts和problem都关联了events.eventid先删子表可以避免留下孤儿数据也避免后面删除events时出现外键关联报错。3.1 先拿“告警动作记录”开刀alerts 表alerts表是你确认动作是否发送成功、邮件是否有报错的关键记录但它不是唯一的数据来源。只要 Zabbix Server 的日志和审计系统独立保留alerts没必要留太久。删除前先看一眼要删多少SELECT COUNT(*) AS cnt FROM alerts WHERE clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY));确认数量之后分块删除。一次性DELETE几百万行在 InnoDB 里会产生超大事务持有大量行锁副作用是你可能把 Zabbix Server 的写事务也堵死。分块的标准姿势是每次只删 5000 行DELETE FROM alerts WHERE clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY)) LIMIT 5000;重复执行直到影响行数为 0。如果担心删除顺序不稳定也可以加ORDER BY alertid LIMIT 5000让每次删除的是一个递增区间DELETE FROM alerts WHERE clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY)) ORDER BY alertid LIMIT 5000;3.2 再清理历史事件表eventsevents表保存了所有事件流水。光看容量它可能没有history_uint大但它直接影响前端“事件”和“问题”历史页面。在删除前需要确认一个问题当前problem表里还有哪些事件正在使用。我们可以用LEFT JOIN把所有当前仍被综合问题表引用的eventid排除掉DELETE FROM events WHERE eventid NOT IN ( SELECT eventid FROM problem ) AND clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY));这种写法在大表上性能不理想尤其是problem表很大时NOT IN会让优化器做大量扫描。更稳妥的写法是分块删除每次先挑一批要删的eventidDELETE FROM events WHERE eventid IN ( SELECT eventid FROM ( SELECT e.eventid FROM events e LEFT JOIN problem p ON e.eventid p.eventid WHERE p.eventid IS NULL AND e.clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY)) ORDER BY e.eventid LIMIT 5000 ) tmp );如果这步报“You cant specify target table for update in FROM clause”可以把内层子查询改成 JOIN 形式DELETE e FROM events e JOIN ( SELECT e2.eventid FROM events e2 LEFT JOIN problem p ON e2.eventid p.eventid WHERE p.eventid IS NULL AND e2.clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY)) ORDER BY e2.eventid LIMIT 5000 ) x ON e.eventid x.eventid;这里有个细节如果你之前已经用OPTIMIZE TABLE或者别的方式把problem表的时间范围缩短了但events里还有对应的事件这条语句会把对应事件保留下来。换句话说清理顺序很重要必须先把不再需要的problem历史问题记录清掉再删events。3.3 处理 problem 表里已经恢复但残留的问题记录Zabbix 3.0.10 的problem表容易出现两类问题正常场景下问题恢复后r_clock会写入恢复时间但表里仍然保留这条已恢复问题记录用于前端“问题历史”显示。异常场景下Zabbix Server 由于数据库抖动或处理异常问题已经恢复了但problem表还留着r_clock 0的记录前端就一直显示“正在处理”。对第一类我们要清理的是已恢复且超过保留期的记录。可以用这条 SQLDELETE FROM problem WHERE r_clock 0 AND r_clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY));不要动r_clock 0的记录因为r_clock 0通常代表当前仍未恢复一旦误删前端会丢失正在告警的问题并且在触发器仍处于异常状态时你短期内看不到告警。对第二类问题已经实际恢复但problem表状态没更新最安全的做法不是直接 DELETE而是先重启 Zabbix Server 让它重新计算一次问题状态systemctl restart zabbix-server如果重启后仍然显示问题再去人工确认事件历史。确认主机对应触发器确实已经产生了“恢复”事件再删除残留的problem记录。比如查到残留的eventid 123456可以执行DELETE FROM problem WHERE eventid 123456; DELETE FROM acknowledges WHERE eventid 123456;这里是“确认后”再操作别拿这条命令当成通用清理手段。3.4 如果 history 和 trends 也想清磁盘压力太大时history系列表肯定要处理。删除命令很简单但问题在于表太大直接 DELETE 会长时间锁表。建议也是分块DELETE FROM history_uint WHERE clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY)) LIMIT 5000;这里有几个坑要提一下。history_uint的写入频率非常高如果 Zabbix Server 还在运行删除事务和写入事务混在一起优化器可能选择奇怪的执行计划。我的实操经验是清理大表前临时停掉 Zabbix Server 的 housekeeper甚至停掉 Server 本体的采集进程一段时间不然很容易出现“一直删不完”的假象。另外删除后空间不会立刻释放。MySQL InnoDB 的表空间默认不会因为 DELETE 变小你必须在删除后执行OPTIMIZE TABLE alerts, events, problem, history_uint, history, trends, trends_uint;如果是 PostgreSQLVACUUM (FULL, ANALYZE) alerts; VACUUM (FULL, ANALYZE) events; REINDEX TABLE events;OPTIMIZE TABLE是重打表数据并重建索引耗时和表大小成正比。执行期间会有短暂锁表请务必放在低峰期并提前算好磁盘可用空间因为重建过程需要临时占用一些额外空间。4. 把清理写成自动化脚本并接入定时任务手敲 SQL 只适合一次性清理。如果已经形成“三个月清一次”的固定维护周期直接把清理动作写成脚本要比每次都人工查、人工删省事得多。下面给一个我实际在用的 MySQL 后端清理脚本核心思路是设置保留天数、分块删除、记录日志。4.1 MySQL 版本的一键清理脚本#!/bin/bash # zabbix_cleanup.sh # 针对 Zabbix 3.0.10 MySQL 后端清理历史/告警/问题数据 DB_HOST127.0.0.1 DB_PORT3306 DB_USERzabbix DB_PASSyour_password DB_NAMEzabbix EVENT_DAYS180 ALERT_DAYS180 PROBLEM_DAYS90 HISTORY_DAYS30 TRENDS_DAYS365 MYSQLmysql -h${DB_HOST} -P${DB_PORT} -u${DB_USER} -p${DB_PASS} ${DB_NAME} -N -s EVENT_TS$(date -d ${EVENT_DAYS} days ago %s) ALERT_TS$(date -d ${ALERT_DAYS} days ago %s) PROBLEM_TS$(date -d ${PROBLEM_DAYS} days ago %s) HISTORY_TS$(date -d ${HISTORY_DAYS} days ago %s) TRENDS_TS$(date -d ${TRENDS_DAYS} days ago %s) log() { echo $(date %F %T) $1 } delete_by_chunk() { local table$1 local ts$2 while :; do local cnt cnt$(${MYSQL} -e SELECT COUNT(*) FROM ${table} WHERE clock ${ts};) if [ ${cnt} -le 0 ]; then break fi log 清理 ${table} 剩余 ${cnt} 行 ${MYSQL} -e DELETE FROM ${table} WHERE clock ${ts} LIMIT 5000; sleep 1 done } log 开始清理 alerts delete_by_chunk alerts $ALERT_TS log 清理已恢复且超过保留期的 problem while :; do cnt$(${MYSQL} -e SELECT COUNT(*) FROM problem WHERE r_clock 0 AND r_clock ${PROBLEM_TS};) if [ ${cnt} -le 0 ]; then break fi ${MYSQL} -e DELETE FROM problem WHERE r_clock 0 AND r_clock ${PROBLEM_TS} LIMIT 5000; sleep 1 done log 清理 events保留 problem 仍在引用的记录 while :; do cnt$(${MYSQL} -e SELECT COUNT(*) FROM events e LEFT JOIN problem p ON e.eventid p.eventid WHERE p.eventid IS NULL AND e.clock ${EVENT_TS};) if [ ${cnt} -le 0 ]; then break fi ${MYSQL} -e DELETE e FROM events e JOIN (SELECT e2.eventid FROM events e2 LEFT JOIN problem p ON e2.eventid p.eventid WHERE p.eventid IS NULL AND e2.clock ${EVENT_TS} ORDER BY e2.eventid LIMIT 5000) x ON e.eventid x.eventid; sleep 1 done for table in history history_uint history_str history_text history_log; do log 开始清理 ${table} delete_by_chunk $table $HISTORY_TS done for table in trends trends_uint; do log 开始清理 ${table} delete_by_chunk $table $TRENDS_TS done log 开始优化表 ${MYSQL} -e OPTIMIZE TABLE alerts, events, problem, history, history_uint, history_str, history_text, history_log, trends, trends_uint; log 清理完成这个脚本把password直接放在脚本里有安全隐患推荐改用~/.my.cnf或者环境变量。比如在配置文件里写[client] host127.0.0.1 userzabbix passwordyour_password然后把脚本里的-p${DB_PASS}去掉脚本看起来更干净也不会在进程列表中暴露密码。4.2 用 crontab 定时执行把这个脚本放到/opt/scripts/下给执行权限chmod x /opt/scripts/zabbix_cleanup.sh然后加 crontab比如每个月 1 号凌晨执行0 3 1 * * /opt/scripts/zabbix_cleanup.sh /var/log/zabbix_cleanup.log 21日志一定要保留。下一次清理前先看上一次跑完过了多久、每张表删了多少才能判断这个清理频率是否跟得上数据增长速度。如果每次跑完不到半个月就又爆空间说明保留天数设得太长或者 Zabbix 采集 item 数量本身需要优化而不是继续靠清理脚本硬扛。4.3 清理完怎么验证清理完不要直接走人建议做三件事看磁盘空间释放了多少确认OPTIMIZE TABLE执行成功。打开 Zabbix 前端分别看“问题”“事件”“报表”页面是否有异常。跑一条最近数据查询确认数据写入无异常当天监控值正常展示。如果前端某个页面查历史变空白先检查保留天数是否把“最近”也误删了。比如clock存的 Unix 时间戳date %s换算这一步写错很容易删掉近几天的数据。5. 常见问题与排查技巧实录这节收集几个我在清理过程中真正遇到过的问题按速查表的方式整理出来遇到同类情况可以直接照做。现象常见原因解决办法执行 DELETE 卡住不动大事务锁等待Zabbix Server 正在写入同一张表低峰期执行分块删除必要时临时停 Server删完空间没释放InnoDB 表空间没有重建执行OPTIMIZE TABLE并确保innodb_file_per_tableON前端“问题”仍显示已解决的告警problem表残留未更新记录先重启 Zabbix Server 让状态重算确认恢复后再清理残留alerts删了events还很大两个表没同步清理按顺序先删alerts再删eventshousekeeper 总是和手动清理抢资源没有关闭 housekeeper临时配置StartHousekeepers0清理完再改回 1误删了当天数据时间阈值计算有误从备份恢复对应表后续用UNIX_TIMESTAMP时先SELECT确认PostgreSQL 删除后空间不释放缺少 VACUUM FULL执行VACUUM (FULL, ANALYZE)这里单独说一下StartHousekeepers0的操作。Zabbix 3.0.10 的 housekeeper 是 Zabbix Server 自带的清理线程默认开启它会定期按保留策略删除历史数据。手动清理时如果不关会出现两个进程同时 DELETE 同一张表互相争锁轻则删除变慢重则产生大量锁等待。操作方式是修改zabbix_server.confStartHousekeepers0然后重启zabbix-server。清理完再改回 1再次重启。这个方法只建议在维护窗口做不要长期关闭 housekeeper否则之后还得全靠手动清理。另一个容易被忽略的点是分区表。如果你的环境已经把history_uint、events按天或按月做了分区那清理效率会高很多因为可以直接DROP PARTITION而不是逐行 DELETE。但 3.0.10 官方原生没有默认分区很多人的分区脚本是自己写的。如果你准备以后长期维护 3.0.10建议尽早把大表改造成分区表否则数据量到亿级之后再跑这种清理脚本也会非常痛苦。最后再分享一个个人经验清理 Zabbix 老版本数据库这种事最怕的不是数据库太大而是“这表到底是什么含义”没搞清楚。我在现场踩过最狠的坑是直接执行了DELETE FROM events WHERE clock ...结果虽然没有外键约束但前端在查看历史问题时能看到事件记录problem表里又留着对应的已恢复记录两边对不上查询结果非常怪。后来所有操作统一成“先删 alerts 和已恢复 problem再删 events”的顺序才稳定下来。如果你也要在 3.0.10 上清理历史告警问题数据强烈建议先跑一遍只读统计仔细核对表结构和保留周期再动手。这样清完不翻车磁盘也干净。
返回列表