标签: 表分区

  • 深入 Zabbix 陷阱排查:原生 Housekeeper 扫表引发的 History Syncer 阻塞与 Proxy 队列雪崩实战

    监控系统本身的雪崩,是运维架构中最不能忍受的“灯下黑”。近期接手处理了一起 Zabbix 分布式集群的严重延迟故障。现象极为惨烈:全局 30+ Zabbix Proxy 出现严重的数据积压,Server 端监控队列(Queue)飙升至 50万以上,引发大面积的“Zabbix agent on xxx is unreachable”误告警,On-Call 手机直接被打爆。

    直接抛结论:当 Zabbix 集群的 NVPS(每秒处理的新值数量)超过 5000 时,绝对不能依赖 Zabbix 原生的 Housekeeper 来清理历史数据。本次故障的根因正是 Housekeeper 唤醒后,向后端 MySQL 提交了海量的 DELETE 语句,导致 InnoDB Buffer Pool 频繁换页,底层存储 I/O 被随机写彻底打满。数据库的极度缓慢导致 Zabbix Server 的 history syncer(历史数据同步器)进程 100% 阻塞,进而耗尽 History Cache,最终拒绝 Proxy 的数据写入请求,引发全链路雪崩。

    解法极其粗暴但有效:全局关闭 Housekeeper 的历史数据清理功能,将 Zabbix 核心时序表(history、history_uint、trends 等)全部改造为基于 clock 字段的按天表分区(Table Partitioning),通过 DROP PARTITION 替代 DELETE 实现 O(1) 复杂度的过期数据淘汰。

    故障现场与弱智的灾难链

    排查过程中,我第一时间查看了 Zabbix Server 的内部性能指标图表(Zabbix internal process busy %)。图表显示:

    • history syncer 进程繁忙度直接拉平在 100%。

    • trapper 进程(负责接收 Proxy 数据的进程)繁忙度紧随其后,也达到 100%。

    • Zabbix 内部缓存 History cache % free 跌至 0%。

    登录数据库节点,敲下 iostat -x 1,磁盘 %util 钉死在 100%,w/s(每秒写请求)极高。 再切进 MySQL 执行 SHOW FULL PROCESSLIST;,满屏都是类似下面的慢查询:

    DELETE FROM history_uint WHERE itemid=45923 AND clock<1696118400;
    

    这就是典型的架构认知缺失。指望关系型数据库用标准 SQL 处理时序数据的生命周期,是一种极其天真的做法。

    底层原理解析:为什么 DELETE 是时序数据的毒药?

    在 MySQL InnoDB 引擎中,执行一条 DELETE 语句的成本是极高的。它不是简单地把数据抹掉,而是:

    1. 寻址与锁:在二级索引和聚簇索引(B+树)中找到目标记录,加上行锁(甚至间隙锁)。

    2. 写 Undo Log:为了支持事务回滚和 MVCC,必须把删除前的数据写入 Undo 表空间。

    3. 写 Redo Log:修改内存中的数据页,并记录 Redo Log 以保证持久化。

    4. B+ 树维护:大量删除会导致数据页变为空(Page Empty),触发树的分裂与合并(Page Merge),产生极严重的写放大(Write Amplification)。

    当 Housekeeper 试图一次性删除几千万条过期监控数据时,InnoDB 的 Buffer Pool 被大量的脏页填满,后台刷脏(Page Cleaner)线程疯狂工作,直接导致数据库整体吞吐量暴跌。 此时,Zabbix Server 的 history syncer 进程正试图将内存中的新监控数据批量 INSERT 到 MySQL。由于 MySQL 处于高负载的锁竞争和 I/O 拥塞状态,INSERT 被迫挂起。 history syncer 阻塞后,Zabbix Server 分配的 HistoryCacheSize(通常配置为几十MB到几百MB)迅速被新来的数据填满。缓存一旦打满,接收 Proxy 数据的 trapper 进程就无法将数据写入缓存,只能挂起。最终,Proxy 无法推送数据,只能将数据堆积在 Proxy 本地的 SQLite 库中,全局队列雪崩就此形成。

    改造方案与落地实战

    不要试图通过调优 InnoDB 参数(如 innodb_io_capacity 或 innodb_buffer_pool_size)来解决这个问题,那是治标不治本。解法只有一种:表分区。

    1. 禁用 Housekeeper

    在 Zabbix Web 界面:Administration -> General -> Housekeeping。 将 History 和 Trends 的 Enable internal housekeeping 勾选全部去掉。(注意:Events、Services 等小数据量表可以保留由 Housekeeper 清理)。 或者在 zabbix_server.conf 中直接修改配置并重启:

    # 禁用所有后台自动清理操作(非极端情况不建议全禁,建议走 GUI 精细化控制)
    HousekeepingFrequency=0
    

    2. 核心表实施 Range 分区

    对于 MySQL,必须按时间戳(clock)字段对大表进行拆分。Zabbix 核心的大表主要有:

    • history (浮点数)

    • history_uint (无符号整数 – 通常是最大的表)

    • history_str, history_text, history_log

    • trends, trends_uint

    以 history_uint 为例,改造分区的核心 SQL 逻辑如下:

    -- 注意:操作前必须停止 Zabbix Server,确保无新数据写入
    ALTER TABLE history_uint 
    PARTITION BY RANGE (clock) (
        PARTITION p20231001 VALUES LESS THAN (UNIX_TIMESTAMP('2023-10-02 00:00:00')),
        PARTITION p20231002 VALUES LESS THAN (UNIX_TIMESTAMP('2023-10-03 00:00:00')),
        -- 预先建好未来几天的分区...
        PARTITION p_max VALUES LESS THAN MAXVALUE
    );
    

    (注:如果原表已经包含海量数据,直接 ALTER 会锁表很久,建议采用新建分区表 -> INSERT INTO ... SELECT -> RENAME TABLE 的平滑迁移方案。)

    3. 部署分区维护脚本

    分区建好后,需要通过 Cron 每天定时执行脚本,创建未来的新分区,并 DROP 掉过期的老分区。

    ALTER TABLE history_uint DROP PARTITION p20230901;
    

    降维打击就在这里:DROP PARTITION 是一个 DDL 操作,它在文件系统层面上直接删除了对应的 .ibd 数据文件。不走 Buffer Pool,不写 Undo Log,不触发 B+ 树合并,时间复杂度永远是 O(1)。原本需要数小时且耗尽 I/O 的清理任务,现在只需要几毫秒。

    总结

    Zabbix 是一个优秀的监控框架,但它的默认配置是为了让新手能在单机上无脑跑起来而设计的,绝对不是为了生产环境的高可用。在面对大规模监控场景时,任何依赖 RDBMS 进行高频 Delete 操作的设计都是反模式的。如果条件允许,直接使用 TimescaleDB(PostgreSQL 的时序插件)作为 Zabbix 后端,利用其原生的 Chunk 机制进行数据淘汰,是更优雅的现代化选择。

    排查清单:Zabbix 队列堆积与延迟速查

    1. 查 Zabbix 内部性能瓶颈:先看 Zabbix server performance 面板,如果 history syncer > 90% 且 History cache % free < 10%,问题必在后端数据库写入慢。如果 poller 进程 > 90%,则是自定义监控脚本超时或网络不通导致进程耗尽。

    2. 查数据库 I/O 负载:使用 iostat 观察磁盘写延迟,通过 SHOW PROCESSLIST 抓取长事务或慢查询,确认是否存在 DELETE 大量扫表。

    3. 确认 Housekeeper 配置:进入 Administration -> General -> Housekeeping,确认 NVPS > 1000 时,是否已强制接管并禁用了 History 和 Trends 的内置清理。

    4. 查 Proxy 积压状态:在 Server 端执行 zabbix_get -s -k "zabbix[proxy,history,queue]",精准定位是哪个 Proxy 无法上报数据,区分是全局故障还是单节点网络隔离。