supervisorのメッセージキューが溜まる
つい先日, supervisorのメッセージキューが溜まり続けていたので教訓として残す.
start_child, terminate_child, etc...の実装について
start_childを例に取る.
見ての通り, supervisorはgen_server behaviourで定義されており, start_childは内部的にはgen_server:call/3 (timeout: infinity) が呼ばれる.
その後, ここでChildSpecに則り, 起動処理を行う.
起動処理は単純でerlang:apply/3の結果を見て, 成功していればChildSpecを保存・管理する.
gen_serverの起動処理
子の起動処理として以下の関数をここでは以下を想定しよう.
-module(hoge_server).
-behaviour(gen_server).
-compile(export_all).
start_link(Args) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Args, []).
init(Args) ->
{ok, Args}.
%% 以下略
supervisorのdo_start_child内のerlang:apply/3で実行されるのは, hoge_server:start_link/1である.
この中のgen_server:start_linkはinit/1の処理が終わるまで結果を返さない.
(proc_lib:start_linkでsync_waitで待っている)
supervisorのメッセージキューが溜まる条件
以上を踏まると,
Mod:start_link + Mod:initにかかる時間に呼ばれるstart_childの回数が1回を超えるとメッセージキューが溜まることが分かる.
あとは, 処理待ち時間が増えていくのでstart_childが減らない限りは溜まり続けるだけだ.
ちなみに, gen_server behvaiourのterminate処理に時間がかかっても同様の事が実現できる.
つまり, start_link, init, terminate で通信を行うような重い処理をしてはならない.
パーツパーツとしては分かっていたつもりだったが...
- 帯域が埋まり, ラック内のサーバーへのRTTが40msになる.
- start_link, initで行っていた, 分散KVSへのリクエストに時間がかかる.
- supervisorのメッセージキューがたまる.
-
gen_server:call->supervisor:start_childしてたのでtimeoutしまくる.
素晴らしいコンボだった.