【Zabbix地獄からの脱出】DB肥大化を完全制圧する!ハウスキーパー极限突破とパーティショニング実装の極意
テックリードの私たちが、ある日突然直面する「Zabbix WebUIが重い」「ヒストリのグラフを描画した瞬間にDBが死ぬ」という悪夢。その元凶のほとんどは、単一の巨大な`history`および`trends`テーブルによるデータベースの圧迫です。
「とりあえずハウスキーパーを動かしておけばいい」
そんな甘い認識でいると、深夜の障害アラートよりも先に、DBのディスク容量枯渇による全サービス停止という本当の地獄が訪れます。
今回は、Zabbixのパフォーマンス限界を突破し、数千万〜数億メトリクスを秒速でさばき切るための「ハウスキーパーチューニングの極限」と、RDBの物理的限界を超える「テーブルパーティショニングの実装手順(PostgreSQL/MySQL対応)」を、現場の即戦力となるコードと共にお届けします。
—
1. 標準ハウスキーパーの限界と「戦わない」ための設計思想
Zabbixの標準ハウスキーパーは、内部で `DELETE` クエリをゴリゴリ発行して古いデータを消し去ります。しかし、巨大なテーブルに対する `DELETE` は以下の致命的な問題を引き起こします。
1. テーブルロック(Table Lock)とI/Oスパイク: 削除処理中、該当テーブルまたはインデックスがロックされ、Zabbixサーバーからの新規データINSERTがブロックされる。
2. デッドタプル(PostgreSQL)と断片化(MySQL): ディスク容量は空にならない(VACUUMやOPTIMIZEが必要)、あるいはインデックスのツリー構造が破壊されクエリが遅延する。
3. シングルスレッドの呪い: デフォルトのハウスキーパーはシングルスレッドで動くため、データ蓄積スピードが削除スピードを超過した瞬間から、DBは死へのカウントダウンを始めます。
黄金の掟:消すな、捨てろ
私たちオブザーバビリティ・アーキテクトが取るべきアプローチは、「古い行を細かく消す(Delete)」のではなく、「時間単位の箱(パーティション)をごっそり切り捨てる(Drop)」ことです。これにより、ディスクの解放は一瞬で終わり、I/O負荷はゼロになります。
—
2. 標準ハウスキーパーを極限まで最適化する(応急処置)
パーティショニング導入までの繋ぎ、あるいは小規模環境であれば、`zabbix_server.conf` のハウスキーパー設定を以下のように極限までチューニングします。
/etc/zabbix/zabbix_server.conf
ハウスキーパーの実行間隔を短くし、1回あたりの処理量を減らしてI/Oスパイクを防ぐ
HousekeepingFrequency=1
1回に削除するタスク数(デフォルトは40000だが、DBのスペックに合わせて調整)
MaxHousekeeperDelete=100000
履歴データの保持期間(日数・時間は用途に応じて短縮を検討)
HistoryStorageDateIndex=1
しかし、これだけでは数千万行を超えるテーブルの根本的な解決にはなりません。次章の本丸、パーティショニングへ進みましょう。
—
3. PostgreSQLにおけるテーブルパーティショニング実装手順
PostgreSQL 10以降で標準サポートされた「宣言的パーティショニング(Declarative Partitioning)」を用い、時間(日単位または月単位)ごとにテーブルを分割します。
ステップ1: 既存テーブルの移行設計(方針)
既存の `history` テーブルをパーティション化されたテーブルに作り直す場合、ダウンタイムが発生するため、オフピーク時にメンテナンスウィンドウを設けるか、トリガーを用いた段階的移行を行います。ここでは新規構築・再設計を前提としたDDLを示します。
ステップ2: パーティション対応スキーマの作成(PostgreSQL)
— 1. 親テーブルの作成(データを直接格納せず、パーティションへルーティングする)
CREATE TABLE history_new (
itemid BIGINTantle NOT NULL,
clock INTEGER NOT NULL,
value NUMERIC(20,4) NOT NULL,
ns INTEGER NOT NULL
) PARTITION BY RANGE (clock);
— 2. インデックスの定義(親に定義すると子テーブルに継承されます)
CREATE INDEX history_new_itemid_clock_idx ON history_new (itemid, clock);
— 3. パーティションの作成(例: UNIX時間ベースでの日別パーティション)
— 2023年11月1日のパーティション
CREATE TABLE history_20231101 PARTITION OF history_new
FOR VALUES FROM (‘1698796800’) TO (‘1698883200’);
— 2023年11月2日のパーティション
CREATE TABLE history_20231102 PARTITION OF history_new
FOR VALUES FROM (‘1698883200’) TO (‘1698969600’);
ステップ3: 自動パーティション生成プロシージャ(PostgreSQL)
人間が毎日パーティションを手動で作るなどという愚行は、私たちのチームでは禁止です。PL/pgSQLで自動化します。
CREATE OR REPLACE FUNCTION create_partition_history(p_date DATE)
RETURNS VOID AS $$
DECLARE
v_start_epoch BIGINT;
v_end_epoch BIGINT;
v_table_name TEXT;
v_start_date TEXT;
v_end_date TEXT;
BEGIN
v_start_epoch := EXTRACT(EPOCH FROM p_date);
v_end_epoch := EXTRACT(EPOCH FROM p_date + INTERVAL ‘1 day’);
v_table_name := ‘history_’ || TO_CHAR(p_date, ‘YYYYMMDD’);
v_start_date := TO_CHAR(p_date, ‘YYYY-MM-DD’);
v_end_date := TO_CHAR(p_date + INTERVAL ‘1 day’, ‘YYYY-MM-DD’);
— すでに存在していなければ作成
IF NOT EXISTS (
SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = v_table_name
) THEN
EXECUTE format(
‘CREATE TABLE %I PARTITION OF history_new FOR VALUES FROM (%s) TO (%s);’,
v_table_name, v_start_epoch, v_end_epoch
);
RAISE NOTICE ‘Created partition: %’, v_table_name;
END IF;
END;
$$ LANGUAGE plpgsql;
—
4. MySQL (InnoDB) におけるテーブルパーティショニング実装手順
MySQL環境の場合、`RANGE COLUMNS` または `RANGE (UNIX_TIMESTAMP(…))` を使用してパーティショニングを行います。
— MySQL用 history パーティションテーブル定義
CREATE TABLE `history` (
`itemid` bigint(21) NOT NULL,
`clock` int(11) NOT NULL DEFAULT ‘0’,
`value` double(16,4) NOT NULL DEFAULT ‘0.0000’,
`ns` int(11) NOT NULL DEFAULT ‘0’,
KEY `history_1` (`itemid`,`clock`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (clock) (
PARTITION p20231031 VALUES LESS THAN (1698796800),
PARTITION p20231101 VALUES LESS THAN (1698883200),
PARTITION p20231102 VALUES LESS THAN (1698969600),
PARTITION p_max VALUES LESS THAN MAXVALUE
);
MySQLパーティショニング運用の極意:`DROP PARTITION` による瞬時削除
古いデータを削除する際は、以下のコマンドを実行するだけです。CPU負荷もディスクI/Oもほとんど発生せず、一瞬でストレージが解放されます。
— 2023年10月31日分のデータをコンマ数秒で完全消去
ALTER TABLE history DROP PARTITION p20231031;
これをcronやZabbixのスクリプト機能(あるいは後述のメンテナンスツール)で日次で自動実行させます。
—
5. チーム開発を加速させる設定共有化とツールチェーン
ZabbixのDBメンテナンスやパーティション管理を属人化させないため、Infrastructure as Code (IaC) および自動化スクリプトとしてリポジトリで管理します。
チーム共有用:パーティション管理設定(YAML)
Ansibleなどの構成管理ツールから読み込ませるための定義ファイルのベストプラクティスです。
zabbix_partition_config.yaml
—
zabbix_db:
type: “postgres”
dbname: “zabbix”
host: “localhost”
retention_days:
history: 30
trends: 365
partition_interval: “1 day”
automation:
enabled: true
cron_expression: “0 1 ” # 毎日深夜1時に実行
チーム開発のルール(GitOps)
1. DML/DDLの直接本番投入禁止: DBのスキーマ変更やパーティション規則の変更は、必ずGitHub/GitLab経由でプルリクエストを作成し、CI/CDパイプライン(またはレビュー承認)を経て適用する。
2. ハウスキーパーの無効化: パーティショニングを導入したテーブルに対しては、Zabbix標準のハウスキーパー機能を必ずOFF(`Housekeeper=0` または対象テーブルの無効化)に設定すること。二重で削除処理が走り競合を引き起こすのを防ぎます。
—
6. プロの実践テクニック:監視の監視(Meta-Monitoring)
DBのパーティションが正しく作られているか、ハウスキーパー(あるいは自動ドロップスクリプト)が正常に機能しているかを監視しなければ、エンジニアとしての夜は眠れません。以下のZabbix内部メトリクスを必ず監視アラートに組み込んでください。
- DBの空き容量監視: `vfs.fs.size[/var/lib/mysql,pfree]` でディスク枯渇を検知。
- ハウスキーパー実行時間監視: Zabbix内部ログを `log[/var/log/zabbix/zabbix_server.log,”Housekeeper took”]` で監視し、処理時間が異常に延びていないかをチェック。
- パーティション存在確認: 日次でcronジョブの終了ステータスを監視し、明日のパーティションが作成されていなければ即座にSlackへ通知を飛ばす。
結びにかえて
ZabbixのDB肥大化問題は、場当たり的な設定変更では絶対に解決しません。ハウスキーパーの限界を理解し、「テーブルパーティショニングによる物理的な時間分割と瞬時破棄」というアーキテクチャ上の正しい選択を行うことこそが、真のオブザーバビリティ環境を手に入れる唯一の道です。
あなたの管理するZabbixを、数千万メトリクスをも軽々と受け止める「鉄壁の監視基盤」へと生まれ変わらせてください。エンジニアリングの力で、無駄な運用の苦しみから解放されましょう。