バージョンアップ情報
Spring Session情報
Spring Sessionとは
Spring Sessionは、ユーザーのセッション情報を管理するためのAPIと実装を提供すると同時に、アプリケーションコンテナー固有のソリューションに縛られることなくクラスター化されたセッションを簡単にサポートできるようにします。また、以下との透過的な統合も提供します。
- HttpSession : アプリケーションコンテナーに依存しない方法でHttpSessionを置き換えることができ、RESTful APIと連携するためにヘッダーでセッションIDを提供するためのサポートがあります。
- WebSocket : WebSocketメッセージを受信したときにHttpSessionを存続させる機能を提供します。z
- WebSession : Spring WebFluxのWebSessionをアプリケーションコンテナーに依存しない方法で置き換えることができます。
Spring Sessionは、次の5つのモジュールで構成されています。
- Spring Session Core - Spring Sessionのコア機能とAPIを提供します。
- Spring Session Data Redis - Redis用のSessionRepositoryおよびReactiveSessionRepositoryの実装と設定のサポートを提供します。
- Spring Session JDBC - リレーショナルデータベース用のSessionRepositoryおよびReactiveSessionRepositoryの実装と設定のサポートを提供します。
- Spring Session Hazelcast - Hazelcast用のSessionRepositoryの実装と設定のサポートを提供します。
- Spring Session MongoDB – MongoDB用のSessionRepositoryの実装と設定のサポートを提供します。
Spring Sessionを利用したシステムの構成例を以下に示します。
この例では、ロードバランサーで2つのSpring Bootアプリケーションにリクエストを振り分けています。どちらのアプリケーションもSpring Sessionによりセッションが生成・管理され、Spring Date Redisを経由して、Redisに永続化されます。セッションに関する情報はDBを介して2つのアプリケーションサーバー間で共有されることになり、一方で障害が発生した場合でももう一方のアプリケーションサーバーが動作していれば、継続してアプリケーションを利用できることになります。
導入事例
Springアプリ使用時にOracleの一時表領域が枯渇する問題の調査と対策
この問題は、以下のようなものでした。
- Springベースのアプリを長時間稼働していると、Oracleの一時表領域が枯渇してSQLException (ORA-01652) がスローされる
- 一時表領域を確認すると、大量のLOBが含まれており、APサーバを再起動するまでは開放されない
- Spring SessionのGitHub Issueに同様の問題があった
問題が解決するまでは、APサーバの定期的な再起動などで回避してもらうことにして、調査を開始します。 調べてみると、Spring Sessionが使用するSPRING_SESSION_ATTRIBUTESテーブルのBLOB型のATTRIBUTE_BYTES列があり、 このテーブルへのINSERTのたびにこのテーブルだけでなく、一時表領域も増えていくことが分かりました。 Oracleのドキュメントを見ると、以下のような記述があります。
一時LOBは、一時データの格納に使用できます。データは、通常の表領域ではなく、一時表領域に格納されます。不要になった一時LOBは解放する必要があります。
一時表領域に登録された「一時LOB」を解放するには、java.sql.Blobのfree()メソッドを呼ぶ必要がりますが、呼ばれていない可能性が高そうです。
ということで、ソースコードをチェックします。SPRING_SESSION_ATTRIBUTESテーブルにINSERTしているのはこのメソッドです。
private void insertSessionAttributes(JdbcSession session, List attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2,
serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
});
}
}
このコードのgetLobHandler().getLobCreator()で取得されるTemporaryLobCreatorには以下のclose()メソッドがありましたが、 これを呼び出している個所はinsertSessionAttributes()メソッドにはありません。
@Override
public void close() {
for (Blob blob : this.temporaryBlobs) {
try {
blob.free();
}
catch (SQLException ex) {
logger.warn("Could not free BLOB", ex);
}
}
for (Clob clob : this.temporaryClobs) {
try {
clob.free();
}
catch (SQLException ex) {
logger.warn("Could not free CLOB", ex);
}
}
}
TemporaryLobCreatorはjava.io.Closeableを継承するLobCreatorを実装し、一時LOBを解放するclose()メソッドを持っていますが、 TemporaryLobCreatorはtry-with-resourcesステートメントなしで生成されるため、close()メソッドが呼び出されず、一時LOBは解放されないと考えられます。
try-with-resourcesステートメントを使用すると、ステートメント内の処理終了後に自動的にjava.io.Closeableを実装しているオブジェクトのclose()メソッドが呼び出されます。
ということで、バグの可能性が高いことを顧客に伝え、検証のために簡単なサンプルアプリをつくってみることにしました。Spring Sessionを利用してOracleにセッション情報を保存するSpring Bootアプリです。Oracle DBのインストールに時間を奪われつつも、再現環境を構築し、検証です。
JMeterでアプリケーションにアクセスし続けると、ORA-01652が発生し、SQLExceptionがスローされます。再現しました。そして、以下のSQLを発行すると、
select * from V$TEMPORARY_LOBS;
CACHE_LOBSが増え続けているのが分かります。
SID CACHE_LOBS NOCACHE_LOBS ABSTRACT_LOBS CON_ID
---------- ---------- ------------ ------------- ----------
18 371 0 0 0
23 0 0 0 0
25 229 0 0 0
173 144 0 0 0
178 723 0 0 0
346 533 0 0 0
では、以下のようにtry-with-resourcesステートメント(try (LobCreator lobCreator = lobHandler.getLobCreator()) {・・・})を付加してから、同じことをしてみましょう。
private void insertSessionAttributes(JdbcSession session, List attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
try (LobCreator lobCreator = lobHandler.getLobCreator()) {
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
lobCreator.setBlobAsBytes(ps, 2,
serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
} else {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
lobCreator.setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
});
}
}
}
SQLExceptionがスローされず、CACHE_LOBSも増えていません。
SID CACHE_LOBS NOCACHE_LOBS ABSTRACT_LOBS CON_ID
---------- ---------- ------------ ------------- ----------
23 0 0 0 0
Spring Sessionプロジェクトにもプルリクエストを提出しました。
レビューに時間はかかりましたが、修正が正しいと判断され、マージされました。
類似プロダクト
ユーザーのセッション情報の管理に焦点を当てたOSSで、Spring Sessionと同等程度の知名度があるものはありません。TomcatなどのJavaコンテナーでもセッション情報の管理はできますが、Spring Sessionの方がより高機能です。特にSpring Frameworkベースにするアプリケーションでセッション管理をしたい場合は、Spring Sessionを導入するのが当然と言えます。
Spring Sessionのライセンス
Spring Sessionのライセンスは、「Apacheライセンスバージョン2」(Apache License version2)というライセンスに基づいて公開され、営利、非営利を問わず、誰でも自由かつ無償で利用・改変・再配布できるようになっています。
動作環境
- Java17以上
- サーブレットコンテナで動作させる場合はServlet3.1以上
- 他のSpringライブラリを使用する場合はSpring6.0.x以上
- "@EnableRedisHttpSession"の使用にはRedis2.8以上
- "@EnebleHazelcastHttpSession"の使用にはHazelcast3.6以上
オープンソース年間サポートサービス
OpenStandiaではOSSを安心してご利用いただけるように、オープンソース年間サポートサービスをご提供しております。
サポートしているOSSは下記ページをご参照ください。
関連OSS
-
サポート対象
Apache Struts
アパッチストラッツ。Java言語を用いてWebアプリケーションを開発するためのフレームワークです。
-
Apache Wicket
アパッチウィケット。Javaで実装されたコンポーネントベースのWebアプリケーションフレームワークです。
-
サポート対象
MyBatis
マイバティス。iBATISの後継プロジェクトとして開発され、Javaならびに.NET Frameworkプラットフォームが対象のデータマッパーフレームワークです。