PHPにおけるセッション管理について

# 概要

[TechTrain](https://techbowl.co.jp/techtrain)のサービスを作って、メンターやってますスーです。
今回は、セッション管理についてわからないことをまとめてみました。

# そもそもセッションとは

セッションとはWebサイトにアクセスして行う一連の行動を指す。
Webサイトにアクセスしてそのサイトから出て行くかブラウザを閉じるまでが1セッション。

Webサーバはセッションごとに一意のIDを持ち、
これを使用することで複数ページにまたがるWebシステムであっても
ユーザごとの情報を保存し使い続けることができる。

このセッションを用いる方法で最も典型的なのがWebページで
最もよく用いられるのがログイン・ログアウトの機能。

ログインをしてからログアウトをするまでを一つのセッションと考え
その間ユーザーIDやパスワードなどのステータスを保存する。

# PHPにおけるデフォルトの設定

サーバーへのファイル保存。Cookieにサーバーに保存したファイルに対する鍵のような値(ID)を持たせ、自分のSessionを問い合わせる。

# デフォルト以外で取れる手法(メリット・デメリット)

ちなみにセッションの管理方法はいくつかありますが、主な方法は以下の通り。

## Cookie

Railsのデフォルトのように、クライアントのクッキーに全てのデータを保存してしまう方法。

### メリット

- サーバにデータをため込まないので、サーバサイドの負荷を気にしないで良い
- アプリケーションサーバを分散させても一貫性を持たせられる

### デメリット

- ユーザ側にデータがあるため、改ざんリスクが高まる（Railsは暗号化によって対策してますが）
- Cookieの仕様上、4KBの容量制限がある
- Cookieを乗っ取られた場合等でもサーバサイドからセッションを破棄する手段がない
- ユーザがcookieを有効にしていない場合、ログイン情報などがうまく保存されない

## ファイル

フレームワークでいうと、LaravelやCakePHPのデフォルトで、CookieにセッションIDだけ持たせて、サーバサイドに実データをファイルとして置く方法。

### メリット

- サーバサイドにデータがあるので改ざんを回避できる
- データ容量制限がない
- サーバサイドでセッションの破棄ができる

### デメリット

- アプリケーションサーバを分散させた場合、一貫性が確保できない
- 1セッションにつき1ファイル生成されるので、大量にセッションファイルが出来てファイルシステムに負担が掛かる

## RDB

CookieにセッションIDだけ持たせて、MySQLなど、サーバサイドのRDBに実データを置く方法。

### メリット

- サーバサイドにデータがあるので改ざんを回避できる
- データ容量制限がない
- サーバサイドでセッションの破棄ができる
- アプリケーションサーバを分散させても一貫性を持たせられる

### デメリット

- RDBは大量の同時I/Oに弱いので、アクセスが増えるとデータベースが一気にパンクする。しかもスケールしづらい。

## KVS（NoSQL）

CookieにセッションIDだけ持たせて、Memcached、RedisなどサーバサイドのKVSに実データを置く方法。

### メリット

- サーバサイドにデータがあるので改ざんを回避できる
- データ容量制限がない
- サーバサイドでセッションの破棄ができる
- アプリケーションサーバを分散させても一貫性を持たせられる
- 大量の同時I/Oに強く、アクセスが増大しても大丈夫！

### デメリット

- セッション専用にひとつデータベースが追加されるのでコストが増


## メモリ






# 上記手法の一般的な実装方法

## Cookie

ブラウザが閉じた後にこのセッションクッキーをくっつけたい場合は、session_start（）の前に呼び出すsession_set_cookie_params（）を使用する必要がある。

また以下の標準関数を利用するとファイルを利用するPHPデフォルトの方法と同じようにcookieにセッションを保存できる。

[session_get_cookie_params](http://php.net/manual/ja/function.session-get-cookie-params.php)
[session.cookie_lifetime](http://php.net/manual/ja/session.configuration.php#ini.session.cookie-lifetime)
[session.cookie_path](http://php.net/manual/ja/session.configuration.php#ini.session.cookie-path)
[session.cookie_domain](http://php.net/manual/ja/session.configuration.php#ini.session.cookie-domain)
[session.cookie_secure](http://php.net/manual/ja/session.configuration.php#ini.session.cookie-secure)
[session.cookie_httponly](http://php.net/manual/ja/session.configuration.php#ini.session.cookie-httponly)
[session_get_cookie_params](http://php.net/manual/ja/function.session-get-cookie-params.php)

## ファイル

これはPHPのデフォルト。何も考えずに、標準関数 session_start を利用したのであれば、session.save_path(デフォルトは/tmp)にファイルとして保存される。

## RDB

### セッション保存用データベースを作成

テーブル構成としては多分こんな感じ

|カラム名|型|内容
|:---:|:---:|:---:|
id|int|セッションID。cookieのPHPSESSIDの値。
data|str | セッションデータ。ここにセッションのデータが保存されます。
created| date | セッション作成日時。データが更新されるたびにここの日付も更新。

### セッションクラスを作成する

今度は作成したテーブルにセッション情報を保存するPHPのクラスを作成。
DBに処理を寄せたので、MVCでいうとモデルに処理を寄せるのが良いかもしれない。

セッションクラスに必要な関数は次の６つ。

- open
- close
- read
- write
- destroy
- gc

PHP5.4以降ではセッションクラスにSessionHandlerInterfaceを使用することで、簡単に実装することができる。また、php5.5.1以降ではsession_set_save_handlerの７番目の引数にcreate_sidコールバックを指定できる。これを使って独自のセッションIDを発行するように変更可能だが、セッションハイジャックに注意が必要。

## 設計の際の注意点

### InnoDB を使う

これはmust。InnoDB でなくてもいいが、「行ロック」をサポートしていることが重要。

1. insert/update が多いため、MyISAM だとテーブルロックが多発する。
2. 上記の処理はwrite ロック がかかるので、read 処理もブロックされる。
3. 行ロックなのでinsert やupdate とdelete が競合することがない。
4. 当然、select とは競合しない。
5. delete と競合しないというのは削除処理を行う上で重要。
6. mysqldump 時にテーブルをロックする必要がない。

6. はサービスをとめないという観点において結構重要。

--single-transaction オプション
行ロック機構を使うには、プライマリーキーかユニーク制約を持つカラムが必要

### セッション専用のDB サーバを設置

マスターDB 内にセッションデータがあると、
他のデータにも負荷の影響が及ぶ可能性があるため
セッションについてはレプリケーションはしてない。
した方がより良いと考えている(後述)

## KVS（NoSQL）

これはPHPだとPHPRedisなどを使って割と簡単に実装可能。
(ただしPHPRedisもあくまで選択肢の一つ)
[PHPセッションをPhpRedisに保存する](https://qiita.com/zurazurataicho/items/a71437412410955afd44) が簡単な実装の参考として良かった。
ありがとうございます。


## メモリ

PHP5.5以降であれば、Apcu、それ以前であれば、apcを該当サーバーにインストールしてapcu(apc)関数系の関数を利用した情報の保存などを行えば良い。

実際やってみるときは、以下を簡単な参考として利用させていただいた。ありがとうございます。
[セッションをメモリに保存する](https://qiita.com/rana_kualu/items/45afbb591eb8195d2518)

# 一般論として

セッションをRDBで管理しているケースは割と多いとのこと。
トラフィックの多いサイトの場合セッションの管理は非常にマシンパワーを食う。
KVS(NoSQL)の利用はお手軽に大きな効果が期待出来るとのこと。
→ここのあたりはお金と人のコストのバランスを見て変更するのが良い。

# 余談

## ファイルキャッシュを使用している時のファイル共有

### 前提条件

1. ファイルキャッシュを使用している
2. WEBサーバーを多重化している
3. 保存したファイルキャッシュのディレクトリをGlusterFSで複数サーバーに対して共有している

３については、GlusterFSでなくとも、共有されているのであれば、同条件と考えていただいて問題ない。

### 読み込みの負荷が高まる可能性

上記前提条件が揃うと、各WEBサーバーが一つのディレクトリに対して過剰な読み込みと書き込みを実行することで、その処理部分がボトルネックとなり、動作が重くなることがある。

技術負債が大量すぎるといった事情がない限り、早めにKVSなどを使った仕組みに変更するなどの工夫をした方が無難。

# そのほか参考

[いえらぶ Group - PHPのセッション情報をデータベース（MySQL）に保存するぜ！](http://www.ielove-group.jp/blog/23)
[Slow Dance - データベースを用いたセッションデータ管理について](http://d.hatena.ne.jp/LukeSilvia/20090523/p1)
[zend-sessionでデータベースにセッション情報を保存する](http://sawara.me/php/2774/)
[PHPセッションをPhpRedisに保存する](https://qiita.com/zurazurataicho/items/a71437412410955afd44)
[APC User Cache - PHP公式ドキュメント](http://php.net/manual/ja/book.apcu.php)
[PHPの振る舞いの変更 - PHP公式ドキュメント](http://php.net/manual/ja/refs.basic.php.php)
[APCがAPCuとZend OPcacheに取って代わられたワケ](https://qiita.com/nntsugu/items/63099b829befb57859d7)