unhurried

コンピュータ関連ネタがほとんど、ときどき趣味も…

Play Frameworkのセッション管理

Play Frameworkのセッション管理は、データをCookieに直接埋め込みクライアント側に保存する仕組みになっています。私はJava Servletのセッションに慣れていたので、「セッションデータはサーバーに保存するもの」という考えが定着していて、この発想はとても新鮮に感じました。

せっかくの機会なので、このあたりの考え方の違いについて少し整理してみます。

Java Servletでのセッション管理はおなじみかもしれませんが、こんな感じです。

  • HttpServletRequest#getSessionを呼び出すとセッションIDが発行され(新規セッションの場合)、レスポンスのSet-CookieヘッダにセッションIDが埋め込まれる(JSESSIONID)。
  • (クライアントは以後のリクエストにて、受信したセッションIDをCookieヘッダに埋め込んで送信する。)
  • HttpSession#setAttributeを呼び出すとセッションIDに紐づけてメモリ上にデータが保存される。
  • クライアントからセッションIDが送信されたときにHttpServletRequest#getSessionを呼び出すと、セッションIDに紐づけられたデータがメモリから取り出される。

さらに、Java Servletを使ったフレームワークではセッション生成・破棄のタイミングを自動で制御して(しばしば○○スコープとか名付けられています)、より簡単に扱えるようにしているものもあります。

この仕組みは便利なのですが、アプリケーションサーバーを複数並べるときには少し困ったことになります。
セッションデータはアプリケーションサーバーのメモリ上に格納されるので、フロントにロードバランサーを設置したとき、リクエストがセッションデータを保存していない方に振られてしまうと、セッションデータが取得できないのです。
この問題を解決するのためにたいていのロードバランサーにはSticky Session機能があるのですが、今度はアプリケーションサーバーの問題をフロントのロードバランサーで解決していることになり、互いの結合度が高くなってしまい、システム構成としてはいまいちな感じがします。また、可用性という観点でもアプリケーションサーバーがダウンしたときにセッションデータが消失していまうのは微妙です。

よくよく考えてみると、多くのシステムではアプリケーションサーバーのバックエンドにデータベースなどの永続化ストレージを持っているので、セッションを使う目的は単に複数のリクエスト間でデータを引き継ぎたいだけです。そして、複数のリクエスト間でデータを引き継ぐにはCookie(もしくはクエリパラメータなど)にデータを詰め込めば良いはずです。
しかしながら、このような単純な仕組みになっていないのは、以下の2つの問題があるためです。

  • セッションデータをクライアント側で改ざんできてしまう。
  • Cookieに埋め込めるデータサイズに上限(4KB)がある。

Java Servletではこれらの問題を「CookieにはセッションIDのみを保存し、セッションデータはサーバー側に保管する。」という手段で解決しているということなのだと思います。ただし、保存先はアプリケーションサーバーのメモリという単純な仕組みになっています。複数台構成を考えるのであれば、キャッシュなりストレージなりに逃がすというのがセオリーになるでしょう。 (実際にTomcatには、セッション情報をmemcachedに逃がす3rd Partyライブラリがあるようです。)

一方でPlay Frameworkではこれらの問題に対して異なる考え方を持っています。まず、セッションデータ改ざんに対しては、Cookieに埋め込むデータに署名を付けて改ざんの検知できるようにしています。次に、データサイズ上限に対してですが、こちらに対しては明確な解決手段を提供していません。Play Frameworkは「サーバーアプリケーションはステートレスであるべき」(スケールさせるために)というポリシーだそうで、大きなセッションデータをアプリケーションサーバーに持つべきではないということなのでしょう。こうなると、素直にバックエンドに保存するか、キャッシュという仕組み(Memcachedと連携する機能が提供されている)を利用してサーバー側にデータを保存するかかのどちらかになります。 (Play FrameworkのキャッシュはJava Servletのセッションに近いのですが、「キャッシュ」と名付けるあたりにステートレスを推奨する姿勢が伺えます。)

つらつらと書いてきましたが最後にまとめると、Java Servletの「Sticky Session+アプリケーションサーバーにセッションデータを保存」という仕組みはかなり強力な手段であり、十分スケールします(たいていの場合アプリケーションダウン時の影響は許容できるはず)。とはいえ、バックエンドにNoSQLなどの高速なストレージがあるのであれば、Play Frameworkのようにアプリケーションサーバーはステートレスにしてしまう方がシステム構成はシンプルにできます。また、最近のクラウドっぽく負荷に合わせてアプリケーションサーバーを増減させるというやり方にも合っているので、今後はこちらが主流になっていくのだと思います。