三つの p95 の平均はどの p95 でもない
一言でいうと
パーセンタイルは、計算方法が1つではなく、合算できません。区間ごとのp95を平均した値は、どの区間のp95でも、全体のp95でもありません。
なぜ必要なのか
パフォーマンスの会議で、こんな表がよく上がります。シャードAのp95は92ミリ秒、シャードBは171ミリ秒、シャードCは818ミリ秒。誰かが3つを平均して、「全体のp95は360ミリ秒」と書きます。その数字は間違いです。同じデータで元のデータを合わせて測り直すと、253ミリ秒です。今度は別の人が、トラフィックの比率で加重平均を出します。189ミリ秒。これも間違いで、今度は反対方向に間違っています。
もう1つ、ずれる場所があります。同じサンプルに同じ「p95」を尋ねても、ツールごとに違う値を出します。最近傍順位(nearest-rank)を使うツールは、サンプルに実際にある値を返し、線形補間を使うツールは、2つのサンプルの間の値を作り出します。サンプルが20個しかなければ、その差は140ミリ秒と178ミリ秒ほど開きます。昨日使っていたツールと今日使うツールが違うと、何も変わっていないのに、p95が悪化したように見えます。
この2つのずれは、ダッシュボードで静かに起きます。誰もエラーを見られず、数字はもっともらしく、SLOの判定はその数字で下されます。
どう動くのか
パーセンタイルは順序統計量です。ソートしたサンプルから特定の位置の値を取り出す作業なので、「位置をどう決めるか」で方法が分かれます。最近傍順位は、k = ceil(q x n)番目の値をそのまま使います。線形補間は、h = (n - 1) x qという実数の位置を作り、前後のサンプルの間を比例で分けます。サンプルが多ければ2つの差は消え、サンプルが少なかったり、裾にはずれ値があったりすると、大きく開きます。
合算できない理由は、もっと根本的です。平均は合計と個数でできているので、部分の合計を足せば全体になりますが、パーセンタイルはソートされた位置なので、部分の位置を足しても、全体の位置になりません。Prometheusのドキュメントも、同じことを言っています。あらかじめ計算された分位数は互いに合算できず、合算できるのはバケットだと。
そのため、正しい道は2つだけです。1つは、元の観測値をすべて1つの桶に注ぎ込んで、ソートし直すこと。正確ですが、観測値をすべて持ち歩く必要があります。もう1つは、ヒストグラムのバケットを足すこと。区間ごとに「50ミリ秒以下が何件、100ミリ秒以下が何件」を数えておけば、その個数はただ足せます。足した表から、あらためてパーセンタイルを推定すればよいのです。その代わり、値ではなくバケットで答えるので、境界が誤差を決めます。境界が粗ければ、推定値がバケットの中のどこかに潰れ、細かければ、正確になる代わりに時系列が増えます。HdrHistogramのようなデータ構造が存在する理由が、このトレードオフです。
もう1つ、合算したパーセンタイルには、守られる枠があります。全体のp95は、区間ごとのp95の最小値と最大値の間に必ず収まります。どの区間でも95%がその値以下なら、混ぜても95%は最大値以下だからです。そのため、「合算したら、すべての区間より大きく出た」という報告は、データではなく、計算を疑う必要があります。
現場での姿
最もよくある事故は、ダッシュボードが、区間ごとのp95を平均して1本の線で描いてくれる場合です。トラフィックの10%しかない遅い区間が、全体のp95を2倍に引き上げているのに、平均線はその事実を隠します。逆に加重平均を使うと、遅い区間が消されて、実際より楽観的な絵になります。どちらも「平均を使えば安全な側に間違う」という直感を裏切ります。
負荷テストでも、同じことが起きます。1回で回すのは負担の大きいテストを、3回に分けて回し、各実行のp95を平均してレポートに書く習慣があります。実行ごとに対象が違ったり、負荷が違ったりすれば、その平均は何も測っていません。正しい方法は、実行ごとに元の応答時間を残しておいて、合わせて再計算することで、そのため、ツールの元の出力(hey -o csv、k6の結果ファイル)を捨てない習慣が必要です。
時間軸でも、同じ落とし穴があります。1分ごとに計算しておいたp95を、1時間のグラフとして描きながら、平均線を載せるダッシュボードがよくありますが、その線は、1時間のp95ではありません。1分のp95の平均は、トラフィックの少ない分と多い分を同じ重みで数えるので、事故がトラフィックの集中した時間に起きたほど、実際より低く出ます。1時間のp95が必要なら、その1時間のバケットを足して、求め直す必要があります。
ツールを変えるときにも、確認すべきことがあります。計算方法をドキュメントに書いておくツールもあれば、書かないツールもあります。同じデータで2つのツールを一度回してみて、値が分かれるかをまず見れば、あとで「先週よりp95が悪化した」という報告が、ツールのせいなのかサービスのせいなのかを、区別できます。サンプルが数万件なら、2つの方法の差は消えるので、確認は、サンプルが少ない側(短いウィンドウ、トラフィックの少ない区間)で行って初めて意味があります。
次のラボですること
固定シードで作ってある3つの区間のレイテンシのサンプルを使って、パーセンタイルの計算方法2つを手で実装して、値が分かれることを確認します。そのあと、区間ごとのp95の単純平均と加重平均を、全体のp95と比べて、2方向に間違うことを見て、全体のp95が、区間ごとのp95の最小値と最大値の間にあるという枠を確認します。続いて、ヒストグラムのバケットを足して合算する方法を実装し、境界を粗いものと細かいものに変えて誤差を測ります。最後に、heyを2回回して、元のデータを合わせた値と、p95を平均した値の差を測り、複数の実行を正しく合算するツールを作って提出します。