🔩 ねじき教室 Go と Web の教室

速くしても全体はそこまで速くならない

読了目安 約3分

アムダールの法則で改善の上限を見積もり、ボトルネックが順に移動していく理由を理解します。

この章の目次

いちばん遅いクエリを 10 倍速くしても、全体は 10 倍になりません。 どこまで速くなるかは、そのクエリが全体の何割を占めるかで決まります。

改善の上限

全体のうち一部だけを速くしたとき、全体が何倍速くなるかを表す式があります。

S=1(1f)+fr S = \frac{1}{(1-f) + \dfrac{f}{r}}
  • ff :速くする部分が、全体の時間に占める割合
  • rr :その部分を何倍速くするか
  • SS :その結果、全体が何倍速くなるか

全体の 60 パーセントを占めるクエリを 10 倍速くするなら、 f=0.6f = 0.6r=10r = 10 です。

S=1(10.6)+0.610=10.4+0.062.17 S = \frac{1}{(1-0.6) + \dfrac{0.6}{10}} = \frac{1}{0.4 + 0.06} \approx 2.17

これがアムダールの法則です。 残りの 40 パーセントは 1 ミリ秒も減らないので、そこが天井を作ります。

ff (占める割合)r=2r=2r=10r=10r=r=\infty
0.21.111.221.25
0.61.432.172.50
0.91.825.2610.0

占める割合が小さいものは、無限に速くしても意味がありません。

だから合計時間の順に見る

ここから、計測結果の読み方が決まります。 1 回が最も遅いクエリではなく、呼ばれた回数を掛けた合計時間が最大のものから直します。

1 回 500 ミリ秒でも 1 分に 1 回しか呼ばれないクエリより、1 回 5 ミリ秒でも毎秒 200 回呼ばれるクエリのほうが、全体に占める割合は大きくなります。 alp とスロークエリログを合計時間順に並べるのは、f の大きいものを先に見つけるためです。

例外は、規定の応答時間を超えて失格や減点になる場合です。合計時間が小さくても、規定を破っているものは先に直します。

犯人は動く

f の大きいものを直すと、その部分の割合は小さくなります。 すると次に大きいものが繰り上がり、ボトルネックが移動します。

典型的な移り方です。

  1. 最初はアプリケーションの CPU
  2. インデックスと N+1 を解消すると、データベースとの往復そのものが支配的になる
  3. データベースを別のホストに分けると、今度はそのホストの CPU が先に飽和する

大事なのは、移動したあとの構成では以前の計測が無効になっていることです。 1 つ直したら測り直します。 前のラウンドの犯人を追い続けると、すでに f が 5 パーセントしかないものを磨くことになります。

直列部分は消えない

rr を無限大にしたときの上限は 11f\dfrac{1}{1-f} です。 どれだけ手を尽くしても、直列に残る部分より速くはなりません。

ISUCON では、次のようなものが直列部分として残ります。

  • パスワードのハッシュ計算のような、仕様上省けない計算
  • 1 つのロックで守られていて同時に 1 つしか動けない区間
  • 依存関係があり、前の結果が出ないと始められない処理

前の 2 つは削れない固定費として受け入れます。 3 つ目は、依存していないなら並べれば消えるので、真っ先に疑う価値があります。

試してみよう

前章の計測の続きです。 データベースのサービス需要 9 ミリ秒の内訳が、次のようだったとします。

中身1 件あたり
インデックスが無く全件走査しているクエリ6 ミリ秒
その他のクエリ3 ミリ秒
  1. 全件走査のクエリが占める割合 ff はいくつですか
  2. そのクエリをインデックスで 20 倍速くしたとき、データベース全体は何倍速くなりますか
  3. 上限件数は 222 件/秒からいくつに動きますか
答えを見る

ff は 0.67。 S=1/(0.33+0.67/20)2.7S = 1 / (0.33 + 0.67/20) \approx 2.7 倍。 DD は 9 ミリ秒から 3.3 ミリ秒に下がるので、上限は 222 件/秒から約 600 件/秒へ動きます。

20 倍速くしても全体は 2.7 倍です。 それでも上限が 3 倍近くへ動くのは、そこが全体の 3 分の 2 を占めていたからです。