🎈

Apacheの拡張モジュール「mod_qos」でアクセスが殺到しても落ちないwebサイトを作る

はじめに

インターネット上でwebサイトを運用している方であれば、自身の管理するwebサイトに突然アクセスが殺到しwebサイトが高負荷になってヒヤヒヤしたり、実際にダウンさせてしまったという経験があるかもしれません。

アクセス数の見積もりやサーバのサイジングを慎重に行っていたとしても、インターネット上にサーバを公開している以上はアクセスが突如としてスパイクし想定以上の高負荷状態になってしまうことはあり得るかと思います。

アクセス急増時にwebサイトをダウンさせないようにするには、一般的に以下のような方法が考えられるかと思います。

オートスケールする

高負荷となったらオートスケールし負荷に耐えられるようなアーキテクチャを採用する、という考え方が一般的かと思います。ただオートスケールを可能とするには原則的にアプリケーションの設計時に考慮しておく必要があり、設計時にオートスケールの考慮がされていないアプリケーションを後からオートスケールに対応させるのは現実的には難しいのではないでしょうか。

仮想待合室サービスを利用する

あるいは、最近よく見かけるような仮想待合室サービスを利用するということも考えられます。仮想待合室サービスを利用すれば高負荷時にユーザをいったん待合室で待たせることでサイトへの流入を抑えることができますし、仮想待合室サービスを既存のアプリケーションに組み込むのも比較的容易と考えられます。ただし、サービスの利用料が発生するためいつ起こるか分からない(起こらないかもしれない)事態に備えてコストを払い続けるという判断も難しいかもしれません。

mod_qosを利用する

そこで、コストをかけずにダウンさせないようにする仕組みとしてApacheの「mod_qos」モジュールを使用する方法を紹介します。

mod_qosとは

mod_qosはApacheの拡張モジュールで、文字どおり「quality of service」に関する制御、つまり帯域制御や優先制御を行うことができます。

https://mod-qos.sourceforge.net/

公式ページを見るとできることは多そうですが、ここでは簡単に以下のように制御することを考えてみます。

  • ログイン済みユーザに対してCookieを払い出す
  • Apacheの同時接続数が一定以上の場合は、Sorryページのレスポンスを返却する
  • ただし、Cookieを払い出したユーザの場合は、Sorryページのレスポンスではなく通常のレスポンスを返却する

つまり、サーバが高負荷(≒同時接続数が多い)状態の場合、新規ユーザのアクセスは受け付けないがログイン済みユーザは引き続き使用可能となるという、仮想待合室サービスを使うのに近い挙動になります。

ただし、仮想待合室サービスは待っている人が「順番に」アクセスできるのに対し、mod_qosを使う場合は「同時接続数が少なくなった瞬間にアクセスした人」が優先されることになるので公平性はないということになります。

実際にmod_qosを動かしてみる

ここでは、VirtualBoxにUbuntu24.04を入れて、Apacheのインストールからブラウザによる動作確認まで行ってみます。

Apache・mod_qos・PHPのインストール

Ubuntuのターミナルで以下のコマンドを順次実行し、Apacheとmod_qosをインストールします。また動作確認用ページを作成するためにPHPも合わせてインストールします。

# aptをアップデートしておく
$ sudo apt update

# Apacheインストール(apxsを使うためapache2-devもインストール)
$ sudo apt -y install apache2 apache2-dev

# libssl-devのインストール(mod_qosインストールに必要)
$ sudo apt -y install libssl-dev

# mod_qosのダウンロードとインストール(バージョンは2024年11月時点のもの)
# 公式の手順に従いapxsを使用してインストールする
$ wget https://downloads.sourceforge.net/project/mod-qos/mod_qos-11.75.tar.gz
$ tar xvzf mod_qos-11.75.tar.gz
$ cd mod_qos-11.75/apache2
$ sudo apxs -i -c mod_qos.c -lcrypto -lpcre2-8

# mod_qosの有効化
$ sudo nano /etc/apache2/apache2.conf
---以下を末尾に追記
# mod_qosモジュールの読み込み
LoadModule qos_module /usr/lib/apache2/modules/mod_qos.so
---

# PHPインストール
$ sudo apt -y install php
# PHP確認用ページ作成
$ sudo nano /var/www/html/info.php
---以下をコピペ
<?php
  phpinfo();
---

# Apache再起動
$ sudo apachectl -k restart

この時点で以下のURLでページが表示できるはずです。

またmod_qosを有効化しているので、以下のURLでページ下部にmod_qosに関する情報が表示されるはずです。(同時接続数なども表示されます)

同時接続数がしきい値以上ならSorryページを返却する設定

次に、単純に同時接続数がしきい値以上ならSorryページのコンテンツを返却する、という設定をしてみます。
同時接続数をわざとオーバーさせるため一定時間スリープしてレスポンスするsleep.phpとSorryページのコンテンツsorry.phpを作成します。

sleep.phpの作成

$ sudo nano /var/www/html/sleep.php
---以下をコピペ
<?php
sleep(30);  // 30秒スリープ
echo 'sleeped';
---

sorry.phpの作成

$ sudo nano /var/www/html/sorry.php
---以下をコピペ
<?php
echo 'sorry,server is busy';
---

mod_qosで同時接続数の上限を設定

mod_qosの同時接続数の上限とエラーページを定義するためApacheのconfにQS_LocRequestLimitMatchQS_ErrorPageの設定を追加します。
ここでは上限値として3を設定します。

$ sudo nano /etc/apache2/apache2.conf
---以下を末尾に追記
# 全ページを対象として同時接続数の上限を3にする
QS_LocRequestLimitMatch .* 3
# エラーページとしてsorry.phpのコンテンツを返却する
QS_ErrorPage /sorry.php
---
# Apache再起動
$ sudo apachectl -k restart

動作確認

実際に同時接続数が上限に達した状態でSorryページとなるか試してみます。

同時接続数の上限が3なので http://localhost/sleep.php を連続で開くと4回目でSorryページになることが確認できます。

Cookieを発行しCookieを持つユーザはエラーにならないようにする設定

同時接続数の制御はうまくいったので、Cookieでログイン済みユーザか新規ユーザかを判定し、同時接続数がオーバーしていてもログイン済みユーザはSorryページとならない、という挙動になるように設定してみます。

mod_qosでCookieを払い出す設定

HTTPレスポンスに「mod-qos-vip」というレスポンスヘッダが含まれる場合は、VIPユーザとして扱うためのCookieを発行するようにApacheのconfにQS_VipHeaderNameの設定を追加します。

$ sudo nano /etc/apache2/apache2.conf
---以下を追記
# このHTTPヘッダが存在するレスポンスでCookieを付与する
QS_VipHeaderName mod-qos-vip
---
# Apache再起動
$ sudo apachectl -k restart

ログインページの作成

次に、ログインページを模したページlogin.phpを作成し、このページにアクセスしたユーザにCookieを払い出せるように、「mod-qos-vip」ヘッダをレスポンスするようにします。

$ sudo nano /var/www/html/login.php
---以下をコピペ
<?php
header("mod-qos-vip: 1"); // mod_qosにCookieを付与させるためにレスポンスヘッダを設定
echo 'logged in';
---

ここで、プライベートウィンドウなどCookieが空の状態で http://localhost/login.php を開くとCookie「MODQOS」が払い出されることが確認できます。

動作確認

実際に同時接続数が上限に達した状態でログイン済みユーザはアクセスできるが、新規ユーザはSorryページになるかを試してみます。

http://localhost/sleep.php を連続して開き、同時接続数が上限に達した状態を作り

  • login.phpにアクセスしていない、つまりCookieを保持しないブラウザではSorryページとなり
  • login.phpにアクセスした、つまりCookieを保持するブラウザではSorryページとならない

となることが確認できるはずです。

※この動画の例では、以下の操作をして挙動を確認しています。

  • 下側のブラウザ(プライベートウィンドウ)でlogin.phpにアクセスし、ブラウザにCookieを払い出させておく
  • 上側のブラウザで3回連続sleep.phpにアクセスし同時接続数が上限に達した状態を作る
  • 上側のブラウザでトップページ( http://localhost/ )にアクセスするとSorryページになる
  • 下側のブラウザでトップページ( http://localhost/ )にするとSorryページにならない

まとめ

簡単な検証でしたが、Cookieの発行や検証はmod_qos側で行われるためアプリケーション側の改修はレスポンスヘッダの追加程度で済むことが分かります。この程度であれば既存のアプリケーションへmod_qosを導入するのも比較的容易なのではないでしょうか。

webサイトはいったん高負荷になると、「誰もアクセスできない」といった状態になり得ますが、「mod_qos」を使用すればユーザの優先順位付けを行うことで、少なくとも「誰もアクセスできない」という最悪の状況は回避できると考えられます。

アクセスが急増した場合の保険として、知っておくと役に立つかもしれません。

この記事を書いた人

樫木 淳
2019年中途入社
下の子はもうすぐ中学生

FORCIA Tech Blog

Discussion