水曜日, 17 1 月 2007

Apacheチューニング: ロギングを高速化する この記事(Apacheチューニング: ロギングを高速化する)を「はてなブックマーク」に追加 この記事をクリップ! この記事(Apacheチューニング: ロギングを高速化する)を「del.icio.us」に追加

あまり知られていません(と思われる)がApache2(2.0.41以降)にはアクセスログの書き出しをメモリにバッファリングし高速化させるという機能があります。
今回はその機能を有効にするとどれぐらい速くなるのか調べてみました。

設定方法はhttpd.confに
BufferedLogs On
と追加するだけでログのバッファリングが有効になります。

以下ベンチマークを取った結果です。

バッファリング無効984 Request/Sec
バッファリング有効1033 Request/Sec
(参考)ロギング無し1055 Request/Sec
※小さなhtmlファイルに対してab -c 100 -n 1000を何度か繰り返した結果の平均です。

体感では違いを感じられないとは思いますがベンチを取るとおよそ5%程Request per secondの値が上がっていました。
静的なファイルが中心のリクエスト数の多いWebサーバであればこれを有効にすることによって多少は速くなるのではないでしょうか。
1行追加するだけで(少し)パフォーマンスアップできるのでオススメです。

追記:
BufferedLogsディレクティブは未だにexperimentalなままなので使用には注意が必要です。
Posted by tsujitako at 11:35 午後 in Linux/

金曜日, 12 1 月 2007

memcachedを使ったPHPのシングルトン実装 この記事(memcachedを使ったPHPのシングルトン実装)を「はてなブックマーク」に追加 この記事をクリップ! この記事(memcachedを使ったPHPのシングルトン実装)を「del.icio.us」に追加

PHPのクラスに備わっているstaticはJava(Servlet)のそれとは違いHTTPのリクエストが完了すると破棄されてしまいます。
そのためstaticフィールドを使ったシングルトンの実装を行ったとしてもリクエストがある度にインスタンスが生成され独立したプロセスから同一のインスタンスにアクセスすることは不可能です。

そこで今回memcachedを利用しPHPの各プロセスから同一のインスタンスを参照できるようにしてみたいと思います。
といってもシリアライズさせているので厳密には別のインスタンスになりますが…。

ちなみにmemcachedとはオブジェクトをメモリにキャッシュさせるPHPとは独立したサーバプログラムです。
利用できる言語はPHPだけに限らずPerl、Ruby、Java、Pythonなどにも対応しています。
インストールは./configure && make && make installで簡単ですが、あらかじめlibeventのインストールしておく必要があります。

次にPHPからmemcachedに接続するためのAPIを持つPECL拡張モジュールをインストールします。
# pear install http://pecl.php.net/get/memcache-2.1.0.tgz
それからphp.iniにエクステンションの記述を追加。
extension="(memcahce.soまでのパス)/memcache.so"
これで準備完了です。

今回シングルトンとして作るクラスは設定情報などを保持するものと想定して作りました。
以下がそのクラスのソースです。
class Repository
{
        private static $memcache;

        private $map;
        private $updated;

        public static function getInstance()
        {
                self::$memcache=new Memcache();
                self::$memcache->connect("localhost",11211);

                $repository=self::$memcache->get("repository");
                if(!$repository)
                        $repository=new Repository();

                return $repository;
        }

        private function __construct()
        {
                $this->map=array();
                $this->updated=false;
        }

        public function set($key,$val)
        {
                $this->map[$key]=$val;
                $this->updated=true;
        }

        public function get($key)
        {
                return $this->map[$key];
        }

        public function __destruct()
        {
                if($this->updated)
                {
                        $this->updated=false;
                        self::$memcache->set("repository",$this);
                }
                self::$memcache->close();
        }
}
説明するほどのものでもないですが。まずgetInstance()内でMemcacheオブジェクトを通じてmemcachedに接続し、"repository"というキーでオブジェクトが存在しているか調べます。
存在していればmemcachedから取得したオブジェクトをそのまま返し、存在していなければ新たにインスタンスを生成して返します。
これで永続化されたオブジェクトを取得することが出来るのですが、その後オブジェクトの状態を変更してもJavaのように参照を保持しているわけではないので永続化されたオブジェクトは更新されません。
そこで__destructを使いオブジェクトが破棄されるタイミングでもし更新されていればmemcachedにオブジェクトを書き出す処理を行っています。
$repository=Repository::getInstance();
$repository->set("test",123);
上ような処理だけでリクエストが終了しても状態は保たれたままその後他のプロセスも同じオブジェクトを参照することが出来ます。
しかもネットワーク越しに同一のオブジェクトを共有できるためWebサーバに負荷が増えてきても容易にスケール出来るのはかなりのメリットではないでしょうか。
ただし今回の実装は排他制御を行っていないのでそのままでは使えませんが…。

参考までに他のシングルトンの実装としてオブジェクトをシリアライズしてファイルに保存するパターンとデータベース(MySQL)に保存するパターンを作りベンチマークを取ってみました。

requests/secリモート接続
memcached334
ファイル439×
DB(MySQL)277
※ab -c 100 -n 1000の結果です。
※MySQLはHEAPテーブルを使用しています。

これを見るとローカルからだけの参照ならファイルにシリアライズさせるパターンが一番速いですが、リモートからの参照となるとMySQLよりmemcachedに保存した方が速いようです。
作成したファイル版とデータベース版のソースも一応置いておきます。(→ファイル版、→データベース版)

パフォーマンスは悪くないのでオブジェクトに限らず画像やテキストなどキャッシュさせれば負荷を軽減させるのに役立つのではないでしょうか。slashdotやmixiも使っているようですし。
Posted by tsujitako at 5:42 午前 in PHP/