CodeIgniterでmemcachedに持続的接続する

CodeIgniterのmemcachedドライバを改造して、memcachedに持続的接続するというお話。

環境

CodeIgniterのバージョンは下記のとおりです。

CodeIgniter 2.1.3
普通にmemcachedドライバを使う

普通にmemcachedドライバを使うと非持続的接続になります。
確認のため、memcachedに接続するだけの簡単なコントローラを書いてみます。

application/config/memcached.php

<?php
$config['memcached'] = array(
    'hostname' => '127.0.0.1',
    'port'     => 11211,
    'weight'   => 1
);

application/controllers/test.php

<?php
class Test extends CI_Controller
{

    public function index()
    {
        $this->load->driver('cache');
        $this->cache->memcached->save('foo', 'bar', 10);
        echo $this->cache->memcached->get('foo');
    }
}

index.php/testに10回リクエストを送信し、netstatでWebサーバからmemcachedへのコネクションを確認してみます。

$ ab -n 10 -c 1 http://$HOSTNAME/index.php/test > /dev/null 2>&1; netstat | awk '{print $5,$6}' | grep 11211
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT
localhost.11211 TIME_WAIT

リクエストのたびにmemcachedに接続しているため、コネクションが10個張られています。
memcachedとの通信はすで終了しているので、コネクションの状態はTIME_WAIT(終了確認待ち)になっています。

何が問題?

TIME_WAIT状態のコネクションは一定時間経つと消えるため、通常はこれで問題ありません。
しかし、アクセス数が非常に多いサイトなどの場合、短時間にアクセスが集中することで、TIME_WAIT状態のコネクションが大量発生してポートを使い切ってしまい、それ以上新しいコネクションを張れない状態になってしまうことがあります。(結果、TIME_WAITが消えてポートに空きが出るまで待つことになり、memcachedのレスポンスタイムが急激に悪化します。)

持続的接続する

そこで、system/libraries/Cache/drivers/Cache_memcached.phpを改造します。

まず、165行目を下記のように変更します。

-$this->_memcached = new Memcached();
+$this->_memcached = new Memcached('memcached_pool');

次に、167〜187行目を下記のようにif文で囲みます。

-foreach ($this->_memcache_conf as $name => $cache_server)
-{
-	...
-}
+if (!count($this->_memcached->getServerList()))
+{
+	foreach ($this->_memcache_conf as $name => $cache_server)
+	{
+		...
+	}
+}

最終形です。

<?php
...
$this->_memcached = new Memcached('memcached_pool');

if (!count($this->_memcached->getServerList()))
{
	foreach ($this->_memcache_conf as $name => $cache_server)
	{
		if ( ! array_key_exists('hostname', $cache_server))
		{
			$cache_server['hostname'] = $this->_default_options['default_host'];
		}

		if ( ! array_key_exists('port', $cache_server))
		{
			$cache_server['port'] = $this->_default_options['default_port'];
		}

		if ( ! array_key_exists('weight', $cache_server))
		{
			$cache_server['weight'] = $this->_default_options['default_weight'];
		}

		$this->_memcached->addServer(
				$cache_server['hostname'], $cache_server['port'], $cache_server['weight']
		);
	}
}
...

これでmemcachedに持続的接続することが可能になり、同じサーバプロセスが受け付けたリクエストであればコネクションを共有できるようになります。
つまり、memcachedへの接続に必要なポート数が高々サーバプロセス数になります。

確認しやすくするため、httpd.confに下記の1行を追記し、Apacheのサーバプロセス数を1に設定します。

ServerLimit 1

同じようにindex.php/testに10回リクエストを送信し、netstatしてみます。

$ ab -n 10 -c 1 http://$HOSTNAME/index.php/test > /dev/null 2>&1; netstat | awk '{print $5,$6}' | grep 11211
localhost.11211 ESTABLISHED

今度はコネクションが1個しか張られないようになっています。
コネクションを張りっぱなしにして再利用するため、コネクションの状態はESTABLISHED(接続確立)のままになっています。

まとめ

CodeIgniterでmemcachedを使った大規模サイトを開発する場合には、memcachedドライバを改造して持続的接続するといいかもしれません、というお話でした。(最初から設定ファイルで接続方法を変更できるようになっているといいんですけどねー。)