ARTICLE DETAIL

资讯详情

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

Zabbix数据库history表膨胀怎么办?从清理到分区全攻略

Zabbix数据库history表膨胀怎么办?从清理到分区全攻略 1. 问题现场喊了半年磁盘告警真凶就是history表如果你接手过一套跑了两年以上的Zabbix大概率会遇到一模一样的事监控节点本身没挂应用也正常运行反而是Zabbix所在的数据库机器隔三差五报警“磁盘使用率超过90%”。我这边出问题的时候数据库目录已经飙到200多GB整个监控页面的图形和最新数据刷半天都出不来点个主机列表都卡。一开始还怀疑是被黑或者日志文件膨胀最后手动查了一下表大小好家伙history_uint一张表占了120多GBhistory又占了30多GB剩下的trends和trends_uint加起来也有小20GB。其他配置表、审计表加起来不到1GB。这个场景应该能引起很多Zabbix运维的共鸣。Zabbix本身很轻量真正压垮系统的从来不是Server进程而是它拼命往数据库里塞的监控数据。所谓history相关数据占用太大本质上是Zabbix把每个监控项每一次采集到的原始值都存进了数据库而且这些表只会无限增长如果没有合理的保留策略和维护手段数据库磁盘迟早会被吃干抹净。先说清楚一件事如果你只是想快速释放磁盘直接删数据确实能腾出空间但真正的坑在于删完以后数据库文件可能一点都没变小。这里牵扯到存储引擎的底层机制、Zabbix的Housekeeper处理方式、以及数据保留策略怎么配置才合理。这篇文章把我实际踩过的坑、验证过的方案从应急止血到长期治理都梳理一遍适合正在被history表膨胀折磨的朋友也适合准备从头规划一套不爆炸的Zabbix监控平台的人参考。1.1 先定位到底哪张表在占空间在动手清理之前别凭感觉猜先用SQL看一眼所有表的真实大小。我习惯直接查information_schemaSELECT table_name, ROUND(data_length / 1024 / 1024, 2) AS data_mb, ROUND(index_length / 1024 / 1024, 2) AS index_mb, ROUND((data_length index_length) / 1024 / 1024, 2) AS total_mb, table_rows FROM information_schema.tables WHERE table_schema zabbix ORDER BY (data_length index_length) DESC LIMIT 20;拿这套语句去跑基本一眼就能定位罪魁祸首。Zabbix监控库里面最占空间的几张表几乎永远都是history、history_uint、trends、trends_uint这几兄弟。它们数据的增长速度跟监控项数量、采集频率、保留天数直接相关一般不会出现别的表突然暴涨到几十GB的情况。如果发现history_log或者history_text也很大多半是采集了日志文件、配置文本之类的数据这类表单条记录体积更大消耗磁盘的能力比数值型数据还夸张。另外建议顺便看一下数据库所在磁盘的整体使用率、binlog日志大小和慢查询日志是否正常。很多时候history表膨胀不单是空间问题还会把数据库的I/O拖垮进而导致Zabbix Server写入超时、页面响应慢最终变成一连串连锁故障。1.2 影响面比你想象的大history相关表占空间大不只是“磁盘不够用”这么简单。在我实际处理过程中至少出现过三类问题第一是备份时间越来越长。用mysqldump备份200GB的库跑一次要三四个小时而且备份出来的文件也大得吓人。第二是查询性能下降。Zabbix前端画监控图形的时候要频繁读取history表做聚合计算表越大索引越松查询就越慢用户体感就是页面刷新很卡。第三是删除数据时产生的锁和事务问题。清理数据如果没有分批执行直接一把梭DELETEInnoDB会长时间持有行锁binlog体积瞬间猛增极端情况下还会拖垮业务库。所以说解决history数据占用空间过大的问题不能只把它当存储清理来看要从数据模型、写入频率、保留周期、表维护机制几个维度一起处理。2. 根因拆解history表的数据是怎么存下来的2.1 Zabbix数据库里这几张表分别是什么角色Zabbix的监控数据进入数据库后会根据监控项的类型分别写入不同的表。理解这张对应关系后续做清理和优化的时候才不会选错对象。表名存储内容常见数据大小history_uint无符号整数型指标CPU、内存、流量等最大占空间最狠history浮点型指标温度、百分比、自定义数值等次大history_str字符串型指标设备状态、版本等看业务history_text文本型指标巡检报告、日志内容等单条很大history_log日志型监控项原始日志增长非常快trends_uint无符号整数的每小时聚合值小trends浮点数的每小时聚合值小history系列表存的是每次采集的原始值数据量跟采集次数完全成正比。trends系列表则是由Zabbix Server在后台每小时对历史数据进行一次聚合只保存最大值、最小值、平均值、样本数一小时一个指标只占一条记录所以无论采集频率多高trends表的增长速度都是稳定可控的。很多人的误区在于把“监控趋势图的数据”等同于“history表的数据”。其实Zabbix画出来的几小时、几天、几个月的趋势图很多都是从trends表聚合出来的不一定要保留很细粒度的history数据。理论上如果你完全不在乎秒级原始采样曲线history保留24小时甚至更短都够用长期趋势靠trends就足够了。2.2 为什么delete掉数据之后磁盘空间没释放这是history表问题里最容易被误解的一点。很多人发现磁盘满了顺手进数据库执行了DELETE FROM history_uint WHERE clock ...结果一看操作成功、记录数少了但磁盘空间纹丝不动。原因在于MySQL/InnoDB的存储机制。InnoDB在删除数据时只是把数据页中对应的记录标记为“已删除”并不会立刻把物理空间还给操作系统。打个比方就像你用铅笔在草稿纸上写了密密麻麻的字后来拿橡皮擦擦掉了内容但这张纸还占着桌面的位置纸张本身没有被扔进回收站。只有当你重新向这张纸上写数据才有可能覆盖掉擦除后的空白区域。如果没有后续操作把这些页重新整理、合并那这些物理空间会一直被表格占用体现在操作系统层面就是磁盘空间没有变化。所以删除history数据后还差一个关键的步骤重建表或者优化表把碎片合并并释放未使用的空间。常见的做法是OPTIMIZE TABLE或者ALTER TABLE ... ENGINEInnoDB原理是重建整个表的数据和索引把散落的空闲页回收。这可能也是最容易踩坑的一步空间已经足够满的时候执行OPTIMIZE重建表会产生临时文件需要临时空间。如果磁盘已经99%了操作基本上会失败。所以正确的顺序是先删一部分数据确认有足够临时空间后再优化。2.3 被忽略的“隐形空间”索引和碎片除了数据本身索引占的空间也不容小觑。history_uint表的主键一般是(itemid, clock)在Zabbix查询历史数据时经常按itemid和clock范围查询所以这个组合索引是必要的。但表数据几百GB的时候索引大小通常也有数据量的20%左右。在我那张120GB的history_uint表里光索引就有接近25GB。另外在高频率采集场景下数据是持续不断写入的而Housekeeper又是按时间范围去删除旧数据。持续写入和随机删除两股操作同时进行会不断产生页分裂和碎片。碎片一定程度后即使表里实际数据量没那么多物理文件也比逻辑数据大得多。这也是为什么很多Zabbix库查table_rows看着不多文件却异常大的原因。3. 止血操作先删旧数据再回收空间3.1 明确删除目标别把还能用的数据一把清掉动手之前先想清楚一个问题要保留多长时间的history数据长期历史曲线在trends表里已经有了所以history保留30天还是90天业务上大概率都能接受。我当时定的策略是“history保留15天trends保留365天”既保证了短周期排障能看原始采样点长期趋势又不丢。确定好目标后先清理超过保留时间的数据。考虑到直接的DELETE FROM history_uint WHERE clock ...可能会锁表并对数据库造成较大压力强烈建议分批删除。下面这个思路是循环删50000条删完一批等两秒再继续避免单次事务过大#!/bin/bash DB_USERzabbix DB_PASSyour_password DB_NAMEzabbix KEEP_DAYS15 KEEP_UNIX$(date -d $KEEP_DAYS days ago %s) while true; do ROWS$(mysql -u$DB_USER -p$DB_PASS $DB_NAME -N -e \ DELETE FROM history_uint WHERE clock $KEEP_UNIX LIMIT 50000; SELECT ROW_COUNT();) echo deleted $ROWS rows if [ $ROWS -eq 0 ]; then break fi sleep 2 done这段脚本同样可以用来清history、history_str等表只需要把表名换掉。不建议在生产环境一次性执行不加LIMIT的大DELETE尤其当history表有几十G的时候单条DELETE会长时间占用CPU和I/OZabbix Server写入历史数据会全部卡住界面上直接出现“Zabbix server is not running”的提示。如果环境里装好了Percona Toolkit更推荐用pt-archiver来做归档删除。它可以按主键顺序分批处理对主库压力小很多pt-archiver \ --source h127.0.0.1,P3306,Dzabbix,thistory_uint,uzabbix,pyour_password \ --where clock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 15 DAY)) \ --limit 10000 \ --purge \ --commit-each注意pt-archiver默认不是删除模式需要显式加--purge参数否则它只会把数据复制到别的地方。这个工具更专业支持分批、限速能显著降低对在线监控业务的影响。3.2 删除后如何真正释放磁盘空间DELETE只是逻辑删除接下来必须重建表才能把空间退给操作系统。最直接的方式是OPTIMIZE TABLE history_uint; OPTIMIZE TABLE history; OPTIMIZE TABLE trends_uint; OPTIMIZE TABLE trends;执行完以后再看一下磁盘空间通常会立刻少一大截。我自己当时从200GB直接降到了60GB左右效果立竿见影。但这里有几个坑必须提前说OPTIMIZE TABLE在执行期间会长时间占用表的写锁如果直接在业务高峰期跑Zabbix历史数据写入会阻塞界面会出现大量图表加载失败的情况。建议安排在监控低峰期或者先Zabbix Server进入维护模式再操作。OPTIMIZE TABLE需要额外的临时空间。InnoDB重建表时会在数据目录生成临时文件如果磁盘剩余空间很小操作会直接报错。这种情况可以尝试先分批多删除一些数据或者用ALTER TABLE history_uint ENGINEInnoDB这种类似重写的操作看实际临时空间需求。如果数据量大到几十GB并且不能接受长时间锁表可以考虑使用pt-online-schema-change来在线重建表。它是通过创建一张新表、在线拷贝数据、原子切换表名的方式来完成的整个过程基本不阻塞读写pt-online-schema-change \ h127.0.0.1,P3306,Dzabbix,thistory_uint,uzabbix,pyour_password \ --alter ENGINEInnoDB \ --no-swap-tables不过这类工具在高负载库上也会消耗较多I/O需要根据你的数据库规格来控制速度比如加--max-load参数限制线程数。3.3 千万别轻易对整个表做TRUNCATE有一种最“简单粗暴”的释放空间方法就是TRUNCATE TABLE history_uint。TRUNCATE会直接把整个表的数据清空并且重新分配存储空间速度飞快效果立竿见影。但我要给第一次处理这个问题的朋友提个醒不到万不得已不要在生产环境直接TRUNCATE。TRUNCATE意味着这张表里的所有历史数据全部丢失没有还原的可能。如果Zabbix当前还在使用中清空history表不会影响它继续写入新数据但你之前存下来的性能基线、排障依据、历史曲线全部没了。如果企业里有人需要回溯几个月前的监控数据来做容量规划或故障分析这样的操作很容易造成业务事故。除非你已经确认history数据毫无价值、而且监控平台可以接受短期无历史曲线否则建议永远把“按时间范围删除”和“分区删除”作为首选把TRUNCATE当作最后的一根救命稻草。4. 治本方案保留策略、分区表和TimescaleDB4.1 从Zabbix前端调整数据保留策略止血做完以后必须从配置源头控制数据增长。Zabbix的保留策略分两个层级一个是全局设置一个是单个监控项的覆盖设置。全局设置入口在Administration - General - Housekeeping。里面可以分别设置“数据存储周期”History storage period和“趋势存储周期”Trend storage period默认情况下History存储91天Trends存储365天。如果监控项特别多history直接改成15天或30天会舒服很多。但全局设置只对“没有单独设置保留时间的监控项”生效。Zabbix中的每个监控项都有自己的Keep history (in days)和Keep trends (in days)参数如果不设置则使用全局默认值。如果一些关键监控项被单独设成了365天那就算全局改短这些项的数据还是会一直积累。所以调整全局配置后建议执行下面这条SQL检查是否存在特殊保留策略的监控项SELECT i.itemid, h.host AS host_name, i.name AS item_name, i.history, i.trends FROM items i JOIN hosts h ON h.hostid i.hostid WHERE i.history 30 OR i.trends 365 ORDER BY i.history DESC;需要注意的是修改保留策略之后Zabbix的Housekeeper会开始慢慢清理超过保留期的数据但它同样是执行DELETE逻辑不会马上让磁盘文件变小。所以调整保留策略只是一个“长期控制增长”的手段已经占用的大空间必须配合手动删除和表重建才能释放。4.2 给history表做分区一次配置一劳永逸如果你所在的公司Zabbix监控项不少且未来会长期运行我强烈建议把history相关表改造成MySQL分区表。分区的核心思路是把一个巨型表按clock字段拆成多个物理分区一个分区管一个时间段比如每个月一个分区。这样当数据超过保留期时直接DROP PARTITION某个月份比DELETE一条一条删快得多而且分区整体释放的物理空间很干净。在Zabbix 4.0以上的版本中history表本身的主键是(itemid, clock)clock已经在索引里面所以按clock做RANGE分区是可行的。改造SQL类似这样ALTER TABLE history_uint PARTITION BY RANGE (clock) ( PARTITION p202501 VALUES LESS THAN (UNIX_TIMESTAMP(2025-02-01 00:00:00)), PARTITION p202502 VALUES LESS THAN (UNIX_TIMESTAMP(2025-03-01 00:00:00)), PARTITION p202503 VALUES LESS THAN (UNIX_TIMESTAMP(2025-04-01 00:00:00)), PARTITION pMAX VALUES LESS THAN MAXVALUE );这里定义pMAX非常重要不然下个月的数据会找不到分区写入。每个月的月初通过REORGANIZE把pMAX再次拆分生成一个本月分区和一个新的pMAXALTER TABLE history_uint REORGANIZE PARTITION pMAX INTO ( PARTITION p202504 VALUES LESS THAN (UNIX_TIMESTAMP(2025-05-01 00:00:00)), PARTITION pMAX VALUES LESS THAN MAXVALUE );清理旧数据时直接删除过期月份的分区空间瞬间归还ALTER TABLE history_uint DROP PARTITION p202501;每天要做的事只剩下“自动创建下个月的分区、自动删除两个月前的分区”。我写了个简单的cron脚本挂在数据库节点上#!/bin/bash DB_USERzabbix DB_PASSyour_password DB_NAMEzabbix TABLEShistory history_uint history_str history_log trends trends_uint # 生成下个月分区 NEXT_MONTH$(date -d 1 month %Y%m) NEXT_START$(date -d 1 month %Y-%m-01) for TABLE in $TABLES; do mysql -u$DB_USER -p$DB_PASS $DB_NAME -e \ ALTER TABLE $TABLE REORGANIZE PARTITION pMAX INTO (PARTITION p$NEXT_MONTH VALUES LESS THAN (UNIX_TIMESTAMP($NEXT_START)), PARTITION pMAX VALUES LESS THAN MAXVALUE); done # 删除三个月前的分区 OLD_MONTH$(date -d -3 month %Y%m) for TABLE in $TABLES; do mysql -u$DB_USER -p$DB_PASS $DB_NAME -e ALTER TABLE $TABLE DROP PARTITION p$OLD_MONTH; done这个方案的最大优点是清理数据效率极高几乎不产生锁也不会像DELETE那样产生大量binlog。分区后的history_uint表日常查询时还能通过分区裁剪只扫描对应时间范围的分区查询速度也会有明显提升。4.3 更省心的TimescaleDB / PostgreSQL方案如果你的Zabbix数据库用的是PostgreSQL可以让TimescaleDB帮你解决历史数据的问题。Zabbix从4.0版本开始就支持TimescaleDB扩展安装数据库时可以用带--with-timescaledb的方式初始化schema也可以手动对已有的库做改造。TimescaleDB的思路是把history表建成hypertable数据按时间自动分到不同的chunk里清理时直接删除chunk跟MySQL分区表的效果一样。同时它提供压缩策略对超过一定时间的历史数据自动压缩压缩后存储体积通常能减少70%以上。基础改造SQL大概长这样CREATE EXTENSION IF NOT EXISTS timescaledb; SELECT create_hypertable(history_uint, clock, chunk_time_interval 86400000); ALTER TABLE history_uint SET ( timescaledb.compress, timescaledb.compress_segmentby itemid ); SELECT add_compression_policy(history_uint, INTERVAL 7 days);需要提醒的是TimescaleDB的改造最好在Zabbix数据库初始化阶段就做因为把一个已经跑了很多年的巨型history表在线转换成hypertable过程非常耗时中间也需要锁表。打算长期采用PostgreSQL TimescaleDB架构的建议在新建平台时就规划好。5. 容量预估和源头治理别再盲目加监控项5.1 用一条公式算清楚未来空间解决当前的磁盘问题之后更重要的是学会提前估算空间。history表的大小跟三个变量强相关监控项数量、采集频率、保留天数。单日数据量(行) ≈ 监控项数量 × 86400 ÷ 采集间隔(秒) 历史数据总量(行) ≈ 单日数据量 × 保留天数以5000个监控项、采集间隔60秒、保留30天为例单日行数 5000 × 86400 ÷ 60 7,200,000 30天总行数 7,200,000 × 30 216,000,000按每行数据加索引约55字节估算history_uint表大概要占11GB以上加上复制、备份、磁盘剩余空间冗余实际磁盘规划至少要按15GB到20GB来预留。如果采集间隔从60秒改成30秒同样的监控项数量空间占用直接翻倍。trends表的计算相对简单每个监控项每小时一条聚合记录一年365天每条记录加上索引大约40字节5000个监控项 × 24小时 × 365天 × 40字节 ≈ 1.7GB从这个公式就能看出来如果业务没有强需求把监控项采集间隔提高节省的空间会非常可观。与其在数据库满了以后疯狂清理不如在创建监控项时把采集频率定得合理一些。5.2 减少采集项的“水分”很多Zabbix平台会越用越臃肿是因为部署了很多模板每个模板自带几十个监控项其中不少监控项是压在“统计值”上的比如某个网卡的累计流量或者某个设备的状态值这些数据对运维来说可能一年都用不上一次。建议定期用下面这条SQL找出过去7天都没有产生新数据的item确认后停用或删除SELECT i.itemid, h.host AS host_name, i.name AS item_name, i.key_, i.type, i.state FROM items i JOIN hosts h ON h.hostid i.hostid WHERE i.status 0 AND i.lastclock UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 7 DAY)) LIMIT 200;对于确实需要保留但变化不频繁的监控项可以考虑把采集间隔改大比如设备状态类从30秒改成300秒网络流量类保持60秒或更短。数据价值高低决定了采集频率不要把所有的监控项都一视同仁地定为10秒或30秒。5.3 给数据库空间加一个自监控脚本既然Zabbix本身就是监控系统完全可以监控它自己的数据库。我是在Zabbix里建了一个Agent端自定义脚本每天跑一次SQL把history相关表的总体积作为监控项收进来超过阈值自动报警mysql -uzabbix -pyour_password zabbix -N -e \ SELECT ROUND((data_length index_length) / 1024 / 1024, 2) FROM information_schema.tables WHERE table_schema zabbix AND table_name IN (history,history_uint,history_str,history_log)再加一个触发器当这几张历史表总体积超过20GB时触发警告。这样再也不会出现“磁盘满了才发现”的被动局面。6. 常见坑位与排查实录6.1 Housekeeper明明配置了数据还是不删很多人会发现Zabbix后台已经设置了保留15天但查数据库里还有半年前的数据。原因多半是之前手动删除大量数据时导致数据库主从延迟、或者Housekeeper执行时间过长任务一直被卡住。可以先看看库里的housekeeper表积累了多少条待删除任务SELECT COUNT(*) FROM zabbix.housekeeper;如果这个数长期很大说明Housekeeper的删除速度跟不上新数据的产生速度。这种情况下靠Housekeeper慢慢删不现实还是建议先手动分批删除再考虑分区表方案。对于已经做了分区的环境可以直接把Zabbix的Housekeeper内部删除历史数据功能关掉完全靠定期DROP PARTITION来清理效率最高。6.2 OPTIMIZE TABLE执行到一半失败OPTIMIZE TABLE重建表的过程会占用临时文件空间。有一次我在磁盘占用已经到97%的情况下执行OPTIMIZE结果等了两个小时直接报错原因是临时空间不够。后来我是先删了一个月的旧历史数据把磁盘占用降到70%以下才重新执行成功。所以我的经验是磁盘清理顺序必须是“先压缩数据、再释放空间、最后优化表”。如果磁盘已经很满优先考虑分批删除足够多的旧数据而不是直接对着一张200GB的表执行OPTIMIZE。针对超大表也可以使用pt-online-schema-change来替代OPTIMIZE。它通过新建表的方式在线重建占用的额外空间在新建表过程中同样存在但锁表时间很短对线上监控系统友好很多。6.3 清理完数据库之后前端提示“Zabbix server is not running”清理历史数据期间如果Zabbix Server向数据库写入历史数据的请求全部超时前端就会显示“Zabbix server is not running”。这个提示不一定代表Server进程真的挂了更多的可能是在清理操作卡住了数据库连接或者Server进程后面所有数据库操作都在排队。排查思路很简单先看进程systemctl status zabbix-server如果进程还在再进数据库看有没有长时间未提交的事务SELECT * FROM information_schema.innodb_trx\G; SHOW PROCESSLIST;一般会看到一堆Waiting for table metadata lock或者超长的UPDATE、DELETE语句。这时可以等操作结束或者杀掉导致阻塞的会话。等数据库恢复正常后Zabbix Server一般会自动恢复写入前端提示过几分钟就会消失。6.4 最后再分享一个小技巧如果以后新建Zabbix监控平台建议初始化数据库之后就把history相关表按周或者按月分区建好而不是等出了磁盘告警再改造。Zabbix官方文档里虽然没给出一套强制要求但数据库分区的维护脚本网上有很多成熟方案可以提前准备好集成到部署脚本里。分区表不仅在清理数据时快还能避免频繁DELETE造成的碎片问题这几乎是所有大型Zabbix平台的标准做法。我在处理这次history表爆炸问题时最大的体会是“删数据只是治标分区和保留策略才是治本”。如果只做一次大清理但不调整采集频率和保留周期过不了三个月磁盘告警还会再回来。把数据按照价值分级对待该细采的细采该缩短保留周期的缩短再配合分区或者TimescaleDBZabbix的数据库才能真正跑得轻松。
返回列表