Tomcat 5.5 で session persistence (using JDBCStore)

tomcat5.5において、セッション情報をRDBMS(PostgreSQL)に格納するということがしたくて休みの間ゴソゴソ試してました。負荷分散したいけど stickysession が使えないという状況下、クラスタ機能を使わずとも、セッションの格納場所をDBに一元化することでどこまで対応できるのかなぁというアタリを付けてみたかったからです。とりあえず下記のような設定でDBへの格納自体はうまくいったのでメモ代わりに記しておきます。

1. セッション情報格納に使うユーザ情報やテーブルを作る

CREATE USER tomcat WITH PASSWORD 'tomcat';
CREATE DATABASE tomcat WITH OWNER = tomcat ENCODING = 'UTF8';
CREATE TABLE tomcat_sessions
(
  session_id varchar(100) NOT NULL,
  valid_session char(1) NOT NULL,
  max_inactive int4 NOT NULL,
  last_access int8 NOT NULL,
  app_name varchar(255),
  session_data bytea,
  PRIMARY KEY (session_id)
);
ALTER TABLE tomcat_sessions OWNER TO tomcat;
CREATE INDEX idx_tomcat_sessions_app_name ON tomcat_sessions(app_name);

2. Context設定を記述するxml(server.xmlとか)にPersistentManagerとJDBCStoreの設定を追加

<Context docBase="test"
    path="/test"
    reloadable="true"
    source="org.eclipse.jst.j2ee.server:test"
    backgroundProcessorDelay="1" >
  <Manager className="org.apache.catalina.session.PersistentManager"
      distributable="true"
      duplicates="0"
      saveOnRestart="false"
      maxActive="0"
      maxActiveSessions="0"
      minIdleSwap="0"
      maxIdleSwap="0"
      maxIdleBackup="0"
      maxInactiveInterval="0" 
      sessionCounter="0" >
    <Store className="org.apache.catalina.session.JDBCStore"
        checkInterval="1"
        connectionURL="jdbc:postgresql://127.0.0.1:5432/tomcat?user=tomcat&amp;password=tomcat"
        driverName="org.postgresql.Driver"
        sessionAppCol="app_name"
        sessionDataCol="session_data"
        sessionIdCol="session_id"
        sessionLastAccessedCol="last_access"
        sessionMaxInactiveCol="max_inactive"
        sessionTable="tomcat_sessions"
        sessionValidCol="valid_session" />
  </Manager>
</Context>

コンテキスト名なんかは適当に置き換えてください。
属性の意味は公式のドキュメント(ContextタグManagerタグ)とか、このあたりとかを参照してください。
あと、${tomcat_home}/common/lib にJDBCドライバのjar放り込むのを忘れないでください。
WEB-INF/lib では駄目です。

3. 確認

3.1. ポート番号だけ変えてtomcatを二つ上げてみる。(8080番と18080番など)
3.2. こんなjspを置く
<%@ page language="java" contentType="text/html; charset=windows-31j"
    pageEncoding="windows-31j"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-31j">
<title>Insert title here</title>
</head>
<body>
session id = <%= session.getId() %>
<br>
<%	Integer counter = (Integer)session.getAttribute("counter");
	counter = ( counter == null )
		? new Integer(1) : new Integer(counter.intValue() + 1);
	session.setAttribute("counter",counter);
%>
counter = <%= session.getAttribute("counter") %>
</body>
</html>

本来ならservletをこさえるべきなのでしょうが、どうせ実験だし息をするのも面倒くさいのでjspで済ませました。

3.3. 8080番のURLにアクセス

URL: http://127.0.0.1:8080/test/jsp/index.jsp

session id = 5650C2836B97DDC865C859799715CD87
counter = 1
3.4. psqlコマンドでDBの中を確認
tomcat=> select session_id,last_access from tomcat_sessions;
            session_id            |  last_access
----------------------------------+---------------
 5650C2836B97DDC865C859799715CD87 | 1167686511328
(1 row)

tomcat=>

なんか入りますた。

3.5. 数秒置いて 18080番のURLにアクセス

URL: http://127.0.0.1:18080/test/jsp/index.jsp;jsessionid=5650C2836B97DDC865C859799715CD87

session id = 5650C2836B97DDC865C859799715CD87
counter = 2

";jsessionid=ほげほげ" と付けて無理やりセッションIDを指定してやると、ちゃんとDBまで値を読みにいってくれます。
本当のシステム構成ではリバースプロキシとかを使い、ちゃんとcookieヘッダが飛ぶようにする必要があります。

3.6. 数秒置いて また8080番のURLにアクセス

URL: http://127.0.0.1:8080/test/jsp/index.jsp;jsessionid=5650C2836B97DDC865C859799715CD87

session id = 5650C2836B97DDC865C859799715CD87
counter = 3

18080番の方が書き込んだ値が反映されてます。適当に繰り返してみてください。
tomcatを落としたり上げたりしても、セッション自体の有効期限が切れない限り、値は維持されます。

4. ハマッた点、注意点

DBへのセッション書き込み自体は比較的すぐにできるようになりましたが、最初はキャッシュ有効数や期限をどんなに短く設定しても、一分周期くらいでしかDBへ反映が行われなくて頭を抱えました。Webで検索して出てきたまんまの設定しても全然駄目。5.0から5.5にバージョン上がった際、ManagerタグからcheckInterval属性が消えてるからその影響? つか検索して出てくるの4.x時代のばっかだよ! ウボァー。そんなこんなで半日くらいtomcatのソースを読みつつ、公式ドキュメントを目を皿のようにして見ていって、あーでもないこーでもないと色々やっていたら、Contextタグに backgroundProcessorDelay なる不審な属性を発見。ソースを見ると、確かにこの値が1以上だとThread起こしてManagerインターフェースの差分反映メソッドを周期的に叩くようになってます。
果たして、backgroundProcessorDelay="1" としてみたら……HttpSession#setAttribute が呼ばれた数秒後、無事DBの値が更新されたのでした!イヤッホウ! でもこれでもセッション情報の更新から2-3秒くらいのdelayがあるので、stickysession無しでバンバンリクエスト散らすと、sessionへの書き込みが多いアプリでは大変な事になりそう……。tomcatの前には大抵apacheさんがいらっしゃるので、mod_proxy_balancerさんあたりにでも宜しくやってもらうというのが、現実的な落としどころでしょうか。DBへのセッション保存はtomcatが落ちてしまったときの保険というわけですね。しかしそうなると今度はapacheさん自体の負荷分散は……><