大規模なサイトだけでなく、中小規模のサイトにおいても、Webや電子メールなど、増大する一途のサービスや要求に耐え、可用性を高めるため、 負荷分散装置(ロードバランサー)を導入するのが一般的になってきています。 しかし、ベンダー各社から専用システムが販売されていますが、どれも高価です。

オープンソースで開発されているL4負荷分散システムとして、Linuxカーネル上に実装されているLVS(Linux Virtual Server)があります。 LVSは専用システムに比べてローコストであり、Linux上で動作するので、 トラブル時のパケット解析などが比較的容易にできるなどの特徴があります。

LVSの性能はどの位なのでしょうか? 残念ながら、LVSの性能を評価したものは、インターネット上には見当たりませんでした。そこで今回は、LVSの基本性能について調べるために実験をしてみました。

そもそもLVSの性能とは何でしょうか? 例えば、Webのサービスであれば、その性能の目安として、 以下のものがあります。

  • コネクション速度 (単位:コネクション数/秒)
  • データレート、スループット、転送速度 (単位:バイト/秒)
  • 応答速度 (単位:秒)
  • 同時アクセス(コネクション)数

今回はWeb(HTTP)に限定し、WebクライアントからWebサーバーに負荷をかけ、

  • LVSを介する場合とそうでない場合とで、性能差が発生するか
  • LVSの処理はそのマシンにとってどのくらいの負荷なのか
  • LVSがシステム全体のボトルネックにならないのか

などについて実験してみました。

実験環境

ネットワークの各種実験、評価用として購入した
PC サーバー HP ML115(AMD Athlon2.2GHz)を使用しました。

  • Web Server役:
    • HP ML115(AMD Athlon 3500+ 2.2GHz)
    • eth0:Broadcom BCM5721 (内蔵)
    • Linuxカーネル 2.6.18-5-486
    • Apache/2.2.3, apache2-mpm-worker 2.2.3-4+etch4
  • LVS-NATまたはIPルーター役:
    • HP ML115(AMD Athlon 3500+ 2.2GHz)
    • eth0:Broadcom BCM5721 (内蔵)
    • eth1:VIA VT6120/VT6121/VT6122(増設)
    • Linuxカーネル 2.6.24.2
    • 同一のマシンをLVS-NATあるいはIPルーターとして構成しました
  • Webクライアント役:
    • HP ML115(AMD Athlon 3500+ 2.2GHz)
    • eth0:Broadcom BCM5721 (内蔵)
    • Linuxカーネル 2.6.23.11

アライドテレシスL2スイッチGS908Mを使ってVLANを切り、 二つのIPネットワークとして構成しました。 二つのIPネットワークを跨ぐ場所に、 IPルーター役またはLVS-NAT役として 構成するPCサーバー(NICは二つ)を接続しました。

Image
図1: 実験環境概要

実験方法

WEBクライアント役のマシンから、WEBサーバに負荷をかけ、 スループット(KB/s)、 単位時間当たりのコネクション数、 mpstatコマンドによるCPU Idle Time等を測定しました。

負荷を与えるプログラムとして、以下の三つを用意しました。

  • ab(Apache Benchmark)
  • 周期的にTCPのOpen/Closeだけを行なうプログラムA
  • 周期的にHTTP GETを行なうプログラムB

abは最善努力により通信を行ないます。 最大(限界)性能を測定することができます。 Webサーバー、途中のネットワーク、Webクライアントのいずれかが 飽和するまで「ブン回り」ます。 ただし、この方法では任意の負荷を生成することはできません。

「TCPコネクションの開設要求」はLVS-NAT にとって 負荷が大きい処理と思われます。 このときの性能を調べるため、 TCPコネクションを任意の周期で Open/CloseするだけのプログラムAを作成しました。 abでは任意の負荷を生成できません。 そこで任意の周期でHTTP GETを行なうプログラムBを作成しました。

実験1

次の三つの条件おいて、 abを使ってファイルサイズを変えながら負荷を与えました。 abはどこかが飽和するまで全力で動作します(最善努力方式)。

  • Webサーバーとクライアントを直結した場合
  • Webサーバーとクライアント間にIPルーターを挟んだ場合
  • Webサーバーとクライアント間にLVS-NATを挟んだ場合

ファイルサイズとデータレート(KB/s)の関係について図2に示します。 横軸はファイルサイズ(対数)、縦軸はデータレート(KB/s)です。

  • Webサーバーとクライアントを直結(赤)
  • IPルーターを経由(緑)
  • LVS-NATを経由(青)
Image
図2: abによる限界性能測定(データレート)

この実験から次のことがわかりました。

  • ダウンロードするファイルサイズが大きくなるにしたがって、 データレートも上昇しますが、55MB/sec付近で頭打ちになります。
  • サーバとクライアントを直結した場合でも、 IPルーターやLVS-NATを経由した場合でも、同様な傾向が見られます。
  • 頭打ちの原因はサーバかクライアントの処理能力の限界による ものだと考えられます。
  • つまり、 途中に介在するLVS-NATやIPルーターがボトルネックになっていない と思われます。

同じ実験において、コネクション数についても調べてみました。 横軸はファイルサイズ(byte,対数)、 縦軸は一秒あたりのコネクション数を示しています。

Image
図3: abによる限界性能測定(コネクション数)

この実験から次のことがわかりました。

  • サーバとクライアントを直結した場合でも、 LVS-NATやIPルーターを介した場合でも、 同じ傾向を示しています。
  • グラフの右側においては、ファイルサイズが大きくなるにしたがって、 秒間のコネクション数が小さくなっていきます。 これは、データレートが頭打ちになることと同じ原因と考えられます。
  • その一方で、グラフの左側においては、ファイルサイズが変化しても、 秒間コネクション数は変化していません。 およそ9,500で頭打ちになっています。 原因は、TCP通信、HTTP通信オーバーヘッドなどの影響だと思われます。

実験2

abによる最善努力方式の負荷テスト中において、 LVS-NATまたはIPルーター上のマシンのCPU Idle Time(%)についても 測定してみました。 横軸はデータサイズ(byte, 対数)、縦軸はCPU Idle Time(%)です。

Image
図4: CPU Idle Time, IP Router vs. LVS-NAT

この実験から次のことがわかりました。

  • abにより負荷を与え飽和している状況において、 CPU Idle Timeファイルサイズに依存していませんでした。
  • IPルーターとして構成したときの平均は60%でした。
  • ~ LVS-NATとして構成したときの平均は47%でした。
  • LVS-NATとIPルーターとを比べると、常にLVS-NAT の方が CPU Idle時間が少なくなっていました。

IPルーターは、IP層において中継を行ないます。 IPヘッダーのチェックサムの検算、TTLの減算、 それに伴うチェックサムの再計算などを処理が発生しています。 LVS-NATでは上記に加えて、 TCPヘッダーのチェックサムの検算、 ポート番号の変更とそれに伴うチェックサムの再計算などの処理があります。 この処理の多さが上記結果に現れているのだと思われます。

実験3

TCPのOpen/Closeだけを行なうプログラムAを使って、 コネクション数/秒を変化させた場合において、 LVS-NAT役のCPU Idle Timeを測定してみました。 さらに、最善努力を行なうabではなく、 任意の周期でHTTPでGETするプログラムBを使って ファイルサイズを変えながら、 LVS-NAT役のCPU Idle Timeを測定してみました。 これらの二つのプログラムでは、 サーバー、クライアント、ネットワーク、LVS-NATが飽和しない 範囲内であれば、任意の負荷を安定して生成することができます。 上記の二つの結果を同じグラフ上に描きました(図5)。 横軸はファイルサイズ、縦軸は1秒当たりのコネクション数です。

Image
図5: コネクション数を変化させたときのLVS-NAT役ホストのCPU Idle Time(%)
  • HTMLコンテンツサイズは、1KB、4KB、16KB、64KB、 256KB、512KB、1MBのみ
  • 実測値を点でプロット、ベイズ曲線による補間値を自由曲線で描画
  • 256KB、512KB、1MBについては、グラフの横軸の目盛(5000/マス)の都合上、ほぼ垂直になってしまってます。

この実験から次のことがわかりました。

  • TCPのOpen/Closeだけの場合、約30,000コネクション/秒の負荷を生成できました。
  • そのときのLVS役のマシンのCPU Idle Timeが10%以下でした。 つまりカーネル内の処理が90%以上を占めていることを意味します。
  • 実験中も実験後も、LVS-NATになんら異常はありませんでした。

まとめ

HP ML115(AMD Athlon 3500+ 2.2GHz)を ベースとして環境を構築し実験しました。 今回の実験から次のことがわかりました。

  • HTTPにおいて55MB/sec、 9,500コネクション/秒の負荷を生成することができました。
  • この負荷において、LVS-NATの有無は全体の性能に関係ありませんでした。
  • このとき、IPルーターとして構成した場合のCPU Idle Timeは 平均60%でした。LVS-NATとして構成した場合のCPU Idle Timeは 平均47%でした。
  • TCPのOpen/Closeだけでは、LVS-NATを経由して 30,000コネクション/秒の負荷でも正しく動作しました。 このときの LVS役のマシンのCPU Idle Time は10%以下でした。
  • これらの値は、 LVSの限界ではなく、実験環境の限界を示しています。
  • Webサーバー、ネットワーク、Webクライアントのいずれかが 飽和するまで試すというヘビーな実験にも関わらず、 LVSは途中で落ちることもフリーズすることもなく、 実験中も実験後もなんら異常はみられませんでした。
  • LVS-NATの処理能力は、PCの処理能力に強く依存します。 より高性能なPCを用意すれば、より高い性能(CPU Idle Timeの減少を防止)を得られる可能性があります。
  • LVS役のマシン上で重たいアプリケーション、時間的制約の厳しいアプリケーションを動かさないのであれば、CPU Idle Timeが10%になるような状況でもなんら問題ないでしょう。
  • マルチコア、マルチプロセッサーなマシンをLVS役にすれば、そのような状況でもさらに余力があるのかもしれません。
  • LVS-DSRを使えば、さらに高い性能(CPU Idle Timeの減少を防止) が得られます。 あるいは、LVS-DSRにおいて往路はLVS-DSR役のマシンを経由しないという 方法もあります。 これを使えば、 さらに高い性能(CPU Idle Timeの減少を防止)が得られる可能性があります。 ただしその場合には、LVSに簡易ファイアウォールの機能を期待することはできなくなります。

評価用に作成したプログラム

http://x68000.q-e-d.net/~68user/net/ ネットワークプログラミングの基礎知識 を大いに参考にさせていただきました。 この場を借りてお礼申し上げます。_o_

プログラムA: tcp-client3-timer

周期的にTCPのコネクションをOpen/Closeするだけのプログラムです。 一つのプロセスで生成する負荷が足りなければ、複数のプロセスを起動することもできます。

コンパイル方法( -lrtが必要 ):

$ gcc tcp-client3-timer.c -lrt -o tcp-client3-timer

使用方法: 起動時の引数として Webサーバーのホスト名、ポート番号、1周期内の実行回数、その周期(単位はナノ秒)を 必ず指定してください。 周期とその周期内の実行回数は努力目標値です。 1プロセスで足りなければ、複数プロセスを起動することができます。 サーバー側、途中経路、クライアント側の処理能力が足りなければ、 目標値に達しないこともあります。

$ ./tcp-client3-timer
usage: ./tcp-client3-timer hostname port times nanosec

起動後、 CTRL-Cを入力すると、そのときの平均コネクション数/秒を表示します。 CTRL-Cの都度、カウンターはリセットされます。

$ ./tcp-client3-timer localhost 80 1 10000
1235376513 951280
CTRL-C
     129.1
CTRL-C
      43.1
CTRL-C
     128.3

プログラムを終了したい場合には、CTRL-Zで一時停止させた後で kill %(ジョブ番号)を行なってください。

プログラムB: tcp-client5-timer

周期的にHTTP GETを行なうプログラムです。 一つのプロセスで生成する負荷が足りなければ、複数のプロセスを起動することもできます。 コンパイル方法( -lrtが必要 ):

$ gcc tcp-client5-timer.c -lrt -o tcp-client5-timer

使用方法: 起動時の引数として Webサーバーのホスト名、ポート番号、1周期内の実行回数、その周期(単位はナノ秒)、URIを 必ず指定してください。 周期とその周期内の実行回数は努力目標値です。 1プロセスで足りなければ、複数プロセスを起動することができます。 サーバー側、途中経路、クライアント側の処理能力が足りなければ、 目標値に達しないこともあります。

$ ./tcp-client5-timer
usage: ./tcp-client5-timer hostname port times nanosec URI

CTRL-Cを入力すると、そのときの平均コネクション数/秒を表示します。 CTRL-Cの都度、カウンターはリセットされます。

$ ./tcp-client5-timer localhost 80 1 1000000 index.cgi
1235376914 35682
CTRL-C
      64.0
      41.4 KB/sec
CTRL-C
      46.7
      33.5 KB/sec
CTRL-C
      50.8
      35.8 KB/sec

プログラムを終了したい場合には、CTRL-Zで一時停止させた後で kill %(ジョブ番号)を行なってください。