PHPセッションをmemcachedで管理する件

デフォルトのファイルベースでのセッション管理と比べて
複数のWEBサーバ間でセッション共有してくれたり、今更感はあるけど
幸せになれそうなのでやってみます。
ちなみにNginx+FastCGI環境です。

# 導入
## インストール

PHPがインストールされている前提です。
PHPのモジュールにはmemcachedを使用します。memcache（激似）というものもありますが、[有志の比較](http://d.hatena.ne.jp/masayuki14/20120710)によると現状こちらの方が早いとの事なので。絶賛開発合戦中なので将来的にはわかりませんが。

```bash
# yum install memcached php-pecl-memcached
```

## 起動

```bash
# chkconfig memcached on
# service memcached start
```

## PHP設定変更

スクリプト側で変更も可能なのでそういう用途にする場合は飛ばしてください。
設定ファイルを変更すればアプリの修正が不要だったり他の開発者さんに意識せずにmemcachedを使ってもらう事が可能ですね。（切替時に一旦セッションが破棄されるとは思いますが）

ファイルへのセッション保存設定をコメントアウトし、
新たにmemcachedを使用する設定を追加します。

```bash
# vi /etc/php.ini
; session.save_handler = files // コメントアウト
; session.save_path = "/var/lib/php/session" // コメントアウト

# vi /etc/php.d/memcached.ini
session.save_handler = memcached // コメントアウトをはずす
session.save_path = "localhost:11211" // 追記
```

WEBサーバが増えた場合はカンマ区切り追記していきます。
session.save_path = "localhost:11211,otherserver.com:11211"

ちなみに自分の環境はphp-fpmなのでmemcache.iniの部分と手順が違いました。

```bash
# vi /etc/php-fpm.d/www.conf
; php_value[session.save_handler] = files //コメントアウト
; php_value[session.save_path] = /var/lib/php/session //コメントアウト
php_value[session.save_handler] = memcached //追記
php_value[session.save_path] = "localhost:11211" //追記

# service php-fpm reload
```

これでデフォルトでセッション管理がmemcachedになります。


## PHPでmemcached使用設定

上記デフォルト設定を行わなかった場合は以下の様にスクリプト上で設定を変更します。

```php
$session_save_path = 'localhost:11211';
if (ini_set('session.save_handler', 'memcached') && ini_set('session.save_path', $session_save_path)){
    session_start();
}
```

## 確認

memcachedと同時にインストールされているmemcached-toolで現在のcacheデータを確認できます。

```
# memcached-tool localhost:11211 dump
```

また、`/var/lib/php/session/`を監視するなり、memcachedを停止して挙動を見て正しく動作しているか確認してみるのも良いでしょう。
もしうまく動作していない場合、セッション開始時にPHPのエラーログが吐かれるはずなので確認してみましょう。


## ポートを開放
ロードバランシングする際はmemcachedが使用する11211番ポートを他のWEBサーバに対して開放する事


# 実用編

## キャッシュサイズを変更する
memcachedのキャッシュ領域はデフォルトで64MB確保されます。サービス運用でこのサイズでは足りない場合等はこのサイズを変更する必要がありますね。
まずは現在の設定を確認する方法です。

ファイルを1つアップするだけでapcの様にGUIでキャッシュ状況を表示できるようなツールも公開されています。
[memcache.php stats like apc.php](http://livebookmark.net/journal/2008/05/21/memcachephp-stats-like-apcphp/)

here is the the source code.の部分からダウンロードして、以下の部分をサーバに合わせて修正してアップするだけとお手頃です。

```php:
define('ADMIN_USERNAME','memcached'); 	// Admin Username
define('ADMIN_PASSWORD','password');  	// Admin Password
$MEMCACHE_SERVERS[] = 'mymemcache-server1:11211';
```

![こんな感じ](https://qiita-image-store.s3.amazonaws.com/0/15518/9f45d7cc-191e-10b5-d588-21b1b4ed7769.png)

わざわざこんな面倒な事したくなーいって方は以下の様にtelnetでも確認できます。

```
# telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
stats // これを入力します
quit // これで終了します
```

すると現在の動作状態が出力されます。
limit_maxbytesの部分が現在のキャッシュサイズですね。byte単位で表記されています。
設定を変更するには

```
# vi /etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64" //ここを変更します（MB）
OPTIONS=""

# service memcached restart
```

## 有効期限を変更する
memcachedを使っている場合ってガベージコレクションで削除されないから肥大化しない？
みたいな疑問もあるかと思いますが、そこは安心memcachedの有効期限で管理されます。
この有効期限、セッションではphp.iniのsession.gc_maxlifetimeが使用されるようになっています。
デフォルトだと1440秒（24分）です。
検証はしてませんが通常のガベージコレクションだと100アクセスに1回呼ばれる形になる部分はmemcachedだとないのでは？と思うので少し短い気もしますね。
変更したい場合はsession_start前に以下の様に設定を変更しましょう。

```
ini_set('session.gc_maxlifetime', 3600);
```


## セッション管理以外で使用
セッション管理以外でもmemcachedを使いたいぜ！って事もあるかと思います。
set()で値をキャッシュ、get()で取得します。

```php:
$m = new Memcached();
$m->addServer('localhost', 11211);
$m->set('foo','var',60); // 60秒からfooというキーでvarという値をキャッシュします
var_dump($m->get('foo'));
$m->add('hoge','fuga',60); // set同様に値をキャッシュします
$m->add('hoge','piyo',60); // addは同一キーが存在する場合失敗するので次のvar_dumpではfugaのままです
var_dump($m->get('hoge'));
$m->flush(); // 全てのキャッシュデータを削除します
var_dump($m->get('foo')); // 削除されているためFALSEが返ります
```

add()とset()は似ていますが、既に同じキーがキャッシュされている場合add()は失敗します。

さてここで問題が一つ発生します。flush()をしてしまうと、なんとセッションまで消えてしまうではないですか。
というわけでセッション以外のキャッシュのみ削除するという処理も書いておきます。
getAllKeys()で全てのキーを取得し、セッションデータに付与される接頭語を判別、なければ削除という動きです。接頭語は[マニュアル](http://php.net/manual/ja/memcached.sessions.php)にある通り' memc.sess.key.'となりますゆえ、これを先頭一致で判別します。

```php:
$m = new Memcached();
$cachekeys = $m->getAllKeys();
foreach ($cachekeys as $key) {
    $prefix = 'memc.sess.key.';
    if (strpos($key, $prefix, 0) !== 0) {
        $m->delete($key);
    }
}
```

なのでセッションと他のデータを同時にmemcachedで管理する場合は、キーにprefixを付けていく事を意識した方が良さそうですね。