採用情報

お問い合わせ

BLOG

Zabbix テック・ラウンジ

2018 年 11 月 26 日

Zabbix 3.2以降の新機能解説(Zabbix 4.0を見据えて) その17 - Timer系トリガー関数

こんにちは、MIRACLE ZBX サポートを担当している花島タケシです。Timer 系トリガー関数の評価処理が Timer プロセスから追い出されました。今回はこのことについて解説します。

Timer 系トリガー関数の評価処理が Timer プロセスから追い出されました

Timer 系トリガー関数とは ?

最初に「Timer 系トリガー関数」について解説をしておきます。
Timer 系トリガー関数は以下の 6 つとなります。

  • nodata
  • date
  • dayofmonth
  • dayofweek
  • time
  • now

これらを含んだトリガー式は、DB Syncer による、監視データ取得時の評価とは別に、30 秒おきに Timer プロセスにより評価が行われていました。( ちなみに、Timer 系トリガー関数を含んだトリガー式が正しい呼び方となるため、タイトルはちょっと嘘です。)

Zabbix LLC のドキュメントでは下記のように記されています。

https://www.zabbix.com/documentation/3.0/manual/config/triggers

引用

If time-based functions (nodata(), date(), dayofmonth(), dayofweek(), time(), now()) are used in the expression, the trigger is recalculated every 30 seconds by a Zabbix timer process. If both time-based and non-time-based functions are used in an expression, it is recalculated when a new value is received and every 30 seconds.

ソースコードを見ると説明は正しくなく、Timer 系トリガー関数のみから成るトリガー式も取得時に評価がされます。

DB Syncer と Timer プロセスの両方による評価の問題点

あるトリガー式が DB Syncer プロセスと Timer プロセスの両方により評価されることで問題がありました。
Timer プロセスが評価を行うときに最新の情報を使用できずに想定外の動作をするというものです。
この問題は Value Cache により緩和されたとはいえ、共有メモリへのアクセスのロックによる遅延の問題もあります。

ZBX-13396 において、この問題の解決が行われ、Timer プロセスがトリガー式の評価を行わないように修正されています。

改修されたコードを見ていく

当然のごとく、Timer プロセスに関するソースコード (src/zabbix_server/timer/timer.c) からは、トリガー式評価に関するコードは全て削除されました。
2.2.0 からは、トリガー式の評価の処理を並列で行えるようにするために、Timer プロセスが複数起動できるように修正されました。では、その実装も削除されたのか ? というと、そういうことにはなっておりません。ソースコードのコメントにもあるように、その他の実装のために残されています。

+ /* temporary block other timer processes until trigger based maintenance is merged in */
+ if (1 != process_num)
+ {
+ zbx_setproctitle("%s #%d [idle]", get_process_type_string(process_type), process_num);
+
+ for (;;)
+ zbx_sleep_loop(SEC_PER_DAY);
+ }

( と言っても、現在 (4.0.0alpha8) では、複数起動するようになっていた場合、最初以外のプロセスはずっとスリープしているだけです。)

今まで Timer プロセスで行っていた、30 秒に一回行われる Timer 系トリガー式の評価はどこで行われるようになったか ? というと、当然 DB Syncer プロセスとなります。通常のトリガー評価が DB Syncer で行われるのですから、ここに移動することが当然でしょう。

src/zabbix_server/dbsyncer/dbsyncer.c

73 for (;;)
74 {
...
84 next_sync = DCsync_history(ZBX_SYNC_PARTIAL, &sync_num);

デーモンとしてのループ処理となります。
ここで、キャッシュから DB への書き出し処理を DCsync_history() にて行っています。
この関数の実装がが従来と大きく異なります。

従来の主だったコードは下記となります。

2161 DBbegin();
2162
2163 if (0 != (program_type & ZBX_PROGRAM_TYPE_SERVER))
2164 {
2165 DCmass_update_items(history, history_num);
2166 DCmass_add_history(history, history_num);
2167 DCmass_update_triggers(history, history_num);
2168 DCmass_update_trends(history, history_num);
2169 DCflush_nextchecks();
2170
2171 /* processing of events, generated in functions: */
2172 /* DCmass_update_items() */
2173 /* DCmass_update_triggers() */
2174 /* DCflush_nextchecks() */
2175 process_events();
2176 }
2177 else
2178 {
2179 DCmass_proxy_add_history(history, history_num);
2180 DCmass_proxy_update_items(history, history_num);
2181 }
2182
2183 DBcommit();

Zabbix サーバに関しては下記を行っています。

  • アイテム情報の更新。(DCmass_update_items())
  • 監視データの DB への書き出し (DCmass_add_history())
  • トリガー評価 (DCmass_update_triggers())
  • トレンドデータの更新 (DCmass_update_trends())
  • イベントの生成 (process_events())

下記のようなコードと改められました。

src/libs/zbxdbcache/dbcache.c :

2800 int sync_server_history(ZBX_DC_HISTORY *history, int sync_type, int *total_num)
2801 {
...
2861 do
2862 {
...
2868 LOCK_CACHE;
2869
2870 hc_pop_items(&history_items); /* select and take items out of history cache */
2871
2872 if (0 != history_items.values_num)
2873 {
2874 history_num = DCconfig_lock_triggers_by_history_items(&history_items, &triggerids);
2875
2876 /* if there are unavailable items, push them back in history queue */
2877 if (history_num != history_items.values_num)
2878 hc_push_busy_items(&history_items);
2879 }
2880 else
2881 history_num = 0;
2882
2883 UNLOCK_CACHE;

このブロックでは、Sync すべきアイテムを l.2870 にて抽出し、そのアイテムに関するトリガーを l.2874 にて抽出し、ロックしています。ヒストリキャッシュのロックを行ってから処理をしているため、他のプロセスとの競合はありません。( つまり、あるプロセスが行う / 行おうとしているトリガーの評価を、他のプロセスがすることはないということです。)

2885 if (0 != history_num)
2886 {
...
2905 if (FAIL != (ret = DBmass_add_history(history, history_num)))
2906 {
2907 DCconfig_items_apply_changes(&item_diff);
2908 DCmass_update_trends(history, history_num, &trends, &trends_num);
2909
2910 do
2911 {
2912 DBbegin();
2913
2914 DBmass_update_items(&item_diff, &inventory_values);
2915 DBmass_update_trends(trends, trends_num, &trends_diff);
2916
2917 /* process internal events generated by DCmass_prepare_history() */
2918 zbx_process_events(NULL, NULL);
2919
2920 if (ZBX_DB_OK == (txn_error = DBcommit()))
2921 DCupdate_trends(&trends_diff);
2922 else
2923 zbx_reset_event_recovery();
2924
2925 zbx_vector_uint64_pair_clear(&trends_diff);
2926 }
2927 while (ZBX_DB_DOWN == txn_error);
2928 }
...
2934 }

l.2905 にてヒストリテーブルへの書き出しを行います。
成功した場合、キャッシュ内のアイテム情報の更新を行い、l.2908 でキャッシュ内のトレンドデータの更新を行います。
ll.2910-2927 のブロックにて、DB でのアイテム情報の更新、トレンドデータの更新を行います。

4.0.0 では、DC... とは別に DB... という関数のが多数導入され、キャッシュと DB 操作の違いが明確になりました。

2936 if (FAIL != ret)
2937 {
2938 zbx_dc_get_timer_triggerids(&timer_triggerids, time(NULL), ZBX_HC_TIMER_MAX);
2939 timers_num = timer_triggerids.values_num;
2940
2941 if (0 != history_num || 0 != timers_num)
2942 {
2943 zbx_vector_uint64_append_array(&triggerids, timer_triggerids.values,
2944 timer_triggerids.values_num);
2945 do
2946 {
2947 DBbegin();
2948
2949 recalculate_triggers(history, history_num, &timer_triggerids, &trigger_diff);
2950
2951 /* process trigger events generated by recalculate_triggers() */
2952 if (0 != zbx_process_events(&trigger_diff, &triggerids))
2953 zbx_db_save_trigger_changes(&trigger_diff);
2954
2955 if (ZBX_DB_OK == (txn_error = DBcommit()))
2956 {
2957 DCconfig_triggers_apply_changes(&trigger_diff);
2958 DBupdate_itservices(&trigger_diff);
2959 }
2960 else
2961 zbx_clean_events();
...
2964 }
2965 while (ZBX_DB_DOWN == txn_error);
2966 }
...
2969 }

今までの処理が成功していた場合、l.2938 にて現在時刻をベースに処理すべき Timer 系トリガーの取得が行われます。この関数内では上記のトリガーへのロックを考慮しているため、通常処理のトリガーと重複して抽出されることはありません。
l.2941 の history_num は通常のトリガー評価を行うトリガーの数で timers_num は 30 秒おきにトリガー評価を行うトリガーの数です。
要は、今回の処理で行うべきトリガーがあればこのブロックに入ります。
l.2943 にて二つのトリガー群をマージして、以降のトリガー評価、イベントの生成、キャッシュへのトリガーの反映、同様に DB への反映を行います。

ソースコードを読んでみれば、結構単純な仕組みとなっていることがわかります。

従来と比較するために、4.0.0 での処理順序を記します。(DB への更新基準です。)

  • 監視データの DB への書き出し
  • アイテム情報の更新
  • トレンドデータの更新
  • トリガー評価
  • イベントの生成

なお、従来は毎分 0 秒と 30 秒に Timer 系トリガーの評価は行われていました。
4.0.0 からは、分散されることとなりました。

src/libs/zbxdbcache/dbconfig.c :

4408 static int dc_timer_calculate_nextcheck(time_t now, zbx_uint64_t seed)
4409 {
4410 int nextcheck;
4411
4412 nextcheck = ZBX_TIMER_DELAY * (int)(now / (time_t)ZBX_TIMER_DELAY) +
4413 (int)(seed % (zbx_uint64_t)ZBX_TIMER_DELAY);
4414
4415 while (nextcheck <= now)
4416 nextcheck += ZBX_TIMER_DELAY;
4417
4418 return nextcheck;
4419 }

この関数を呼び出すときの seed には、トリガー ID が渡されます。トリガー ID と 30 の剰余が 0 秒、30 秒からのオフセット時間となります。

以上となります。

関連記事

Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 1
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 2
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 3
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 4
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 5
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 6
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 7
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 8
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 9
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 10
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 11
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 12
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 13
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 14
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 15
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 16
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 17
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 18
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 19
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 20
Zabbix 3.2 以降の新機能解説(Zabbix 4.0 を見据えて) その 21

注意事項

  • 本ドキュメントの内容は、予告なしに変更される場合があります。
  • 本ドキュメントは、限られた評価環境における検証結果をもとに作成しており、全ての環境での動作を保証するものではありません。
  • 本ドキュメントの内容に基づき、導入、設定、運用を行なったことにより損害が生じた場合でも、当社はその損害についての責任を負いません。あくまでお客さまのご判断にてご使用ください。
CentOS 7 延長サポートサービス
デジタルトランスフォーメーションのための電子認証基盤 iTrust
SSL/TLS サーバー証明書 SureServer Prime