Spring Session 導入事例・サポートバージョン

Spring Session

Spring Session

NRIのOpenStandiaが提供するSpring Session最新情報

OSSの保守サポートサービスの内容はこちら OSSの導入については、こちらのフォームからお問い合わせ下さい OSS全般の事例紹介はこちら

Spring Sessionのサポート対応事例

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プロジェクトにもプルリクエストを提出しました。

レビューに時間はかかりましたが、修正が正しいと判断され、マージされました。

TOPに戻る

OpenStandiaに関する
資料請求・お問い合わせはこちら

  • 資料請求
  • お問い合わせ

※定期的にメンテナンスを実施しておりますが、一部情報が古い場合がございます。ご了承ください。

  • OpenStandiaサポート対象オープンソース|50種類以上のOSSのサポートをご提供します。
  • 人気midPoint
  • 人気Keycloak
  • 注目MongoDB
  • ForgeRock AM(OpenAM)
  • ForgeRock IDM(OpenIDM)
  • MongoDB
  • Postfix
  • Apache HTTP Server
  • ZABBIX
  • PostgreSQL
  • Apache Struts
  • Apache Kafka
  • Apache Hadoop
  • Apache Spark
  • Spring Framework
  • Apache Tomcat
  • Solr
  • iBATIS
  • DRBD
  • MySQL
  • JBoss
  • Ruby on Rails
  • Jaspersoft
  • OpenLDAP
  • Apache log4j
  • Apache Subversion
  • ForgeRock DS(OpenDJ)
  • Pacemaker
  • Samba
  • Red Hat Enterprise Linux
  • Nginx
  • BIND
  • Dovecot
  • Pentaho
  • sendmail
  • Courier-IMAP
  • ForgeRock DS(OpenDJ)
  • Heartbeat
  • Hibernate
  • Hinemos
  • MyBatis
  • MySQL Cluster
  • Apache Axis2
  • Squid
  • OpenSSO