ロードバランサは Least Connections 戦略にしておけ
ロードバランサにおいて Least Connections 戦略は、接続中の TCP コネクションが少ないバックエンドを優先的に選択するアルゴリズムです。 Least Connections を使うと、単純なラウンドロビンと比べて以下のようなケースでより安定します。
- 一部のバックエンドが重くなったとき
- TCP コネクションを長く張り続けるサービスがスケールアウトするとき
1 の「重くなる」とは、レスポンスタイムが悪化することとします。一部のバックエンドが重くなるとコネクションが開放されるのが遅れるため、Least Connections 戦略を使うと新しく来たリクエストがそのバックエンドに割り当てられなくなります。クライアントから見ると、重くないバックエンドに優先的につながるので、クライアントに対して重くなっているのを隠蔽できます。起動直後のパフォーマンスが悪いサービス (JVM で動くサービスや、ローカルキャッシュがあるサービス) が水平オートスケールでスケールアウトしたときの不安定さも緩和されます。
2 は WebSocket や HTTP/2 を使うサービスが該当します。例えばバックエンドが 2 台に冗長化されていて、それぞれ 50 本ずつ、合計 100 本の WebSocket のコネクションを捌いているとします。ある時そこに新たに 100 本のコネクションを求めるバースト負荷がかかり、オートスケールで 2 台のバックエンドが追加されたとします。すると、もしラウンドロビンを使っていると、新しいコネクションは 4 台に均等に分配されるので、各バックエンドのコネクション数は 125, 125, 25, 25 と不均等になります。Least Connections だとこうはならず、どれだけスピーディーにインスタンスが増えるかにもよりますが、概ね均等になります。クライアント側で TCP コネクションの最長寿命を設定して一定時間ごとに張り直したりという回避策もありますが、それはクライアントの責務を逸脱しているので、できることならサーバー側でやるべきです。
ちなみに LB での Least Connections の実装については、各バックエンドのコネクション数のテーブルから適当に探索すると O(n) や O(log n) になるので、若干遅そうです。Envoy ではランダムに 2 つ選んだうちから小さい方を取るという少し賢いアルゴリズムが使われいるらしく、こうすると O(1) かつ実用的に十分な挙動になるそうです。
Supported load balancers — envoy tag-v1.23.0 documentation
しかし現実的にほぼ全てのサービスは Least Connections で困らないので Istio で使っている Envoy の config のデフォルトがしばらく前に Round Robin から Least Connections (LEAST_REQUEST) に変更されたらしいです。
Nginx とか枯れたミドルウェアを LB として使ってると思考停止 Round Robin にしがちですが、良くないので気をつけなければいけませんね。