こんにちは、SD部の小松です。

社内でソフトウェア開発やミドルウェアのパラメータ設計などを担当しています。
開発では Tomcat を使用することが多いですが、最近は GlassFish を使用する機会を得てサーバー構築に向けて設計ドキュメントを作成しています。


はじめに

性能目標に掲げたリクエスト数を処理するためにシステムを設計しても、リクエスト数が処理能力を超えるとサービスの動作が不安定になることがあります。内部的にはリクエストの滞留が起こっており、次のリクエストを受けることができない状況になっています。リクエストの滞留を防ぐためにも適切に流量制御を行うことでシステムの負荷を軽減できます。本稿では基本となる3層クライアント・サーバシステムを題材に流量制御について考えてみたいと思います。


流量制御とは

次々飛んでくる全てのリクエストを処理の対象にするのではなく、同時に処理することが出来ないリクエストは諦めて「 HTTP 503 Service Temporarily Unavailable 」を返して処理の滞留を抑制させます。これによりシステムで受け付けるリクエスト数の上限を区切ることが可能となり、リソース待ちによる処理の滞留を抑えることを可能にします。


同時接続数

一般にWebシステムにおける同時接続数とはWebサーバーへの接続数です。流量制御の観点では TPS(1秒あたり何件同時にリクエストを捌けるか)となります。接続数の内訳はHTML等の静的なコンテンツへのアクセスとAPサーバーの実行スレッド上で処理する数と2つに分類することができます。この2つの接続を含めたものが同時接続数です。

同時接続数 = 静的コンテンツアクセス数 + 実行スレッド数

実行スレッド数

実行スレッド数については、アプリケーションのスレッド占有時間とTPSを掛けたものとなり、1秒あたり同時に何件リクエストを実行スレッドで捌くかという計算となります。

実行スレッド数= TPS × アプリケーションのスレッド占有時間

DBコネクションプール数

1スレッドあたり1コネクションを占有する様に、APサーバの同時実行スレッド数とDBコネクションプールの数を合わせます。

DBコネクションプール数 = 実行スレッド数

タイムアウト設計

一定時間を経過しても応答がない処理については処理の滞留とみなしてリソースの占有を中止させる等、リソースの占有期限を設計します。クライアント側からDBサーバーに向かって小く設計しましょう。


No. 設計対象 概要
1  Webプロキシータイムアウト  バックエンドから応答を待つ待機期限
2  アプリケーションタイムアウト  アプリケーションのリソース占有期限
3  トランザクションタイムアウト  アプリケーションにおけるトランザクション開始から終了までを待つ期限

Tomcatの流量制御

Tomcatで実現する方法について、以下の制約のシステムを構築する例で考えてみます。
「実行スレッド数= TPS × アプリケーションのスレッド占有時間」なので20本程度スレッドを用意することになります。
同時に20件以上のリクエストが発生した場合は、超た分をSERVER_BUSY(HTTP 503)として処理する必要があります。

<制約>
・アプリケーションのスレッド占有時間が1秒以内
・20TPSの処理性能


SemaphoreValve(Tomcat6,7)

TomcatにはSemaphoreValveと言うリクエストを制限する機能が備わっており、これを使うと簡単に制限をかけることが
出来ますが、今回はこれを使わずに流量制御して見たいと思います。

<Valve className="org.apache.catalina.valves.SemaphoreValve" block="true" concurrency="20" />

server.xml(Tomcat6,7)

「実行スレッド数= TPS × アプリケーションのスレッド占有時間」なので20本程度スレッドを用意する必要があります。
しかし、Tomcatの実行スレッドには業務処理に割り当てられないAcceptスレッドがいて、この数も考慮してスレッド数を計算します。このため 「maxThreads=”21″」として設定します。
TomcatにはAccceptされていない接続をキューイングするための backlog と言うパラメータがあります。流量制御を行うためにはbacklog へのキューイングは避けなければなりません。設定可能な最小値「 backlog=”1″ 」とします。
また、Tomcat側の接続キューを設定可能な最小値「acceptCount=”1″」を設定します。

<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3"
           maxKeepAliveRequests="1"
           backlog="1"
           maxThreads="21"
           minSpareThreads="21"
           acceptCount="1"
/>

proxy_ajp.conf(httpd2.2)

1秒以内にWebサーバとTomcatのコネクションに空きがない場合は、SERVER_BUSY(HTTP 503)として処理させるため「acquire=1」を設定します。また、mod_proxy内部のコネクションの再利用を抑止するために「disablereuse=On」を設定します。mod_proxyのコンテンツキャッシュを無効化するために「retry=0」を設定します。

<Location /APP_NAME>
    ProxyPass ajp://127.0.0.1:8009/APP_NAME max=10 acquire=1 disablereuse=On retry=0
    SetEnv proxy-nokeepalive 1
</Location>

Apache実行時の環境変数

mod_proxy_ajp を使う場合はMPMをworkerへの変更が必須となります。
デフォルトの prefork では mod_proxy_ajp を使った流量制御は出来ないためです。

# /etc/sysconfig/httpd
HTTPD=/usr/sbin/httpd.worker

httpd.conf

簡易的に準備した worker の設定です。
StartServersが 1件なので、mpm_winnt と似た動作になると思います。

<IfModule worker.c>
StartServers         1
MaxClients          80
MinSpareThreads     40
MaxSpareThreads     40
ThreadsPerChild     40
MaxRequestsPerChild  0
</IfModule>

おわりに

ここまで、Tomcatを例に説明させて頂きました。
概念的なことは他のAPサーバーでも通じる部分もあると思います。
次回は実際に測定した結果や他のAPサーバーを例に続編が書ければと考えています。
本稿が、流量制御について知るきっかけとなれば幸いです。