システムの性能を話題にすると、LatencyとThroughputという2つの言葉がまず出てくる。どちらも「速さ」にまつわる言葉なので、最初のうちは混同しがちだ。かくいう僕も、長らく同じ意味だと思って使っていた。

ところが、両者が見ているものはまるで違う。ざっくり言えば、Latencyは1回の処理にかかる時間で、Throughputは一定時間に処理できる量である。たとえるなら、ラーメン屋で一杯が出てくるまでの待ち時間と、一時間に出せる杯数の違いだ。この二つを分けて考えるだけで、性能改善の議論は存外すっきりする。

1回を待つ時間としてのLatency

Latencyは、処理を始めてから結果が返ってくるまでの時間を表す。APIなら、クライアントがRequestを送り、Serverが処理し、Responseが返ってくる。この往復にかかる時間がLatencyだ。

たとえば GET /users/1 のLatencyが100msなら、そのAPIはおおむね100msで応答している。要するに、1回の処理でどれだけ待たされるかの指標である。カウンターに座ってから丼が出てくるまでの時間、と言い換えてもいい。

Webアプリなら、ボタンを押してから画面に結果が出るまでの時間に直結する。Latencyが大きいほど、ユーザーは「なんか遅いな」と感じやすい。理屈はいらない。待たされた人間は、待たされたことだけをしっかり覚えているのだ。

量を捌く力としてのThroughput

一方のThroughputは、一定時間にどれだけの処理を完了できるかを見る指標である。APIサーバーが1秒間に1,000リクエストを処理できるなら、Throughputは1,000 requests/secになる。

むろん、APIに限った話ではない。メッセージ処理なら50,000 messages/sec、画像処理なら500 images/secのように、単位時間あたりの件数で表せる。つまりThroughputは、システム全体がどれだけの量を捌けるかを見るものだ。店で言えば、一時間に何杯出せるかにあたる。

あるAPIサーバーのLatencyが100msで、Throughputが1,000 req/sだったとする。1回のリクエストはおよそ100msで返り、全体では1秒間に約1,000件を処理できる、という意味だ。どちらも性能の話には違いない。ただ、見ている方向がまるで違うのである。

Latencyが低ければThroughputも高いのか

ここが少々ややこしい。1回が速いならたくさん処理できるはずだ、と感覚的には思う。実際そうなることもある。しかし、Latencyが低いことは、Throughputが高いことを意味しない。

たとえば、1リクエストを100msで処理できるサーバーがあるとする。同時に1件しか扱えないなら、単純計算で1秒間に約10件しか捌けない。逆に、同じ100msの処理を100件同時にこなせるなら、Throughputは約1,000 req/sまで跳ね上がる。一杯を作る速さが同じでも、席と鍋の数で出せる杯数は100倍変わるわけだ。

つまり、Latencyが同じでも、ConcurrencyによってThroughputは変わる。Concurrencyとは、同時に何件処理できるかのことだ。1件にかかる時間がLatency、同時に抱えられる件数がConcurrencyである。その結果として一定時間に終わる件数が、Throughputになる。Latencyだけを眺めて「大量のリクエストにも強い」と言い切るのは、いささか早計である。

Web APIではLatencyが気になる

ECサイトの商品ページを開く場面を考えてみる。ブラウザが GET /products/123 を叩き、APIがレスポンスを返す。これが100msで返るのと3秒かかるのとでは、体感がまるで違う。ユーザーがその場で待つ処理では、Latencyが重要になりやすいのだ。

もっとも、アクセスの多いサービスならThroughputも要る。かといって、Throughputが10,000 req/sあっても、p95 Latencyが5秒ではかなり使いづらい。大量に捌けることと、一人ひとりが快適に使えることは別の話なのである。

バッチ処理ではThroughputが効く

反対に、ユーザーがその場で結果を待たない処理では、Throughputの方が重要になることがある。たとえば、100万枚の画像を変換する処理を考えてみる。開店前にまとめて済ませる、仕込みのような仕事だ。

1枚あたりの時間を100msから90msに縮めても、同時に少ししか処理できないなら、たいした改善にはならないかもしれない。それよりも、100 images/secを1,000 images/secに増やすように、単位時間あたりの量を底上げした方がはるかに効く。

具体的には、Job Queueの後ろにWorker A、B、C、Dを並べ、並列に処理させるイメージになる。1件あたりのLatencyはほとんど変わらない。それでも全体のThroughputは、Workerを増やした分だけ上がるのは理想的な場合だ。実際には、DBや外部API、CPU、ネットワークなど、共有する資源の上限も確かめる必要がある。

IoTでは処理ごとに変わる

IoTでも事情は同じだ。大量のデバイスからテレメトリを受け取る仕組みを考えてみる。データはDeviceからMQTT Brokerを経てConsumerに届き、Databaseに書き込まれる。数万台から定期的にデータが届くなら、1メッセージが20msか30msかより、1秒間に何万件捌けるかの方がはるかに重要になる。この場合に見るべきはThroughputである。

ところが、工場の産業用ロボットや製造ラインの機械に、緊急停止コマンドを送るとなると話が変わる。サーバーから機械へコマンドを1本送るだけの処理だ。1秒間に何万件の緊急停止命令を捌けるかを誇られても困る。気になるのは、送ってからどれくらいで届くか、ただそれだけである。こちらではLatencyが重要になる。

要するに、同じシステムの中でも、テレメトリ受信はThroughput、リアルタイム制御はLatencyというように、重視すべき指標は処理ごとに変わる。「このシステムはLatency重視」と大雑把に括るより、処理ごとに考えた方が自然なことも多い。

限界に近づくとLatencyが崩れる

LatencyとThroughputは別の指標だが、まったく無関係というわけでもない。たとえば、あるサーバーが最大で1,000 req/sくらいまで処理できるとする。そこに500 req/sしか来ていないなら、余裕は十分にある。来たそばから処理できるので、Latencyも安定しやすい。

ところが負荷が増え、900 req/s、950 req/s、990 req/sと限界に近づくと、処理待ちが発生しはじめる。リクエストはサーバーに届く前にQueueに並ぶことになる。サーバーが実際に処理している時間は同じでも、ユーザーから見たLatencyは、Queueで待たされた分だけ悪化する。店の前に行列ができはじめた状態だ。

さらに流入が1,200 req/sに達したとする。毎秒1,200件来るのに、毎秒1,000件しか捌けない。処理待ちは毎秒200件ずつ積み上がっていく。こうなると、Throughputは1,000 req/s付近で頭打ちになり、Queueは伸び続け、Latencyだけがどんどん悪化する。3つの現象が同時に起きるのだ。放っておけば、最後尾に並んだリクエストは、待っている人が定年を迎えても返ってこない。

したがって負荷試験でも、最大で何req/s出せるかだけを見るのでは足りない。負荷を増やしたときにLatencyがどう変化するかも、一緒に見ておくべきなのだ。

平均Latencyの落とし穴

Latencyを平均値だけで見るのも危うい。平均Latencyが100msと聞けば、かなり速そうに思える。ところが実際には、p50が50ms、p95が300ms、p99が2,000msということもありうる。大半のリクエストは速いが、一部は2秒以上かかっているわけだ。平均値だけでは、この遅いリクエストが見事に埋もれてしまう。

そこでAPIのLatencyを見るときは、p50、p95、p99のようなPercentileを使うことが多い。p95が300msなら、リクエストの95%は300ms以内に終わったという意味になる。単純な平均よりも、遅いリクエストがどれくらいあるのかが見えてくる。

どちらを最適化すべきか

これは結局、システムと処理による。傾向としては、おおむね次のように分けられる。

処理重視されやすい指標
Web APILatency
バッチ処理Throughput
大量のメッセージ処理Throughput
IoTテレメトリThroughput
リアルタイム制御Latency

とはいえ、WebだからLatency、バッチだからThroughputと単純に決められるわけでもない。Web APIでも大量アクセスを受けるならThroughputが必要になる。バッチ処理でも、完了時刻に厳しい要件があるかもしれない。

そもそも、どちらを改善すべきかという問いから入るのが筋違いなのだと思う。先に考えるべきは、この処理ではなにが遅いと困るのか、どれくらいの量を捌けないと困るのか、である。p95 Latencyを300ms未満に、Throughputを5,000 req/s超に、と両方に要件が付くのもごく普通のことだ。必要なLatencyを満たしつつ、必要なThroughputも確保する。実際の設計は、往々にしてそこへ落ち着く。

「性能が良い」の曖昧さ

翻って、「このシステムは速い」という表現は、よく考えるとかなり曖昧である。Latencyが低いと言っているのかもしれないし、Throughputが高いと言っているのかもしれない。1件は速いのに大量アクセスにはめっぽう弱いシステムもある。大量のデータは捌けるのに、1件あたりには時間がかかるシステムもある。

だから性能を語るときは、なにが速いのかを分けて考える必要がある。その土台になるのが、LatencyとThroughputなのだ。

以上、LatencyとThroughputの違いと、両者の関係について整理した。Latencyは一杯を待つ時間、Throughputは一時間に出せる杯数である。行列のできる店で「速い」と言うなら、どちらの速さなのかを言わなければならないのだ。