Skip to content

Provide database specific JdbcIndexedSessionRepository customizers #1726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 27, 2020

Conversation

vpavic
Copy link
Contributor

@vpavic vpavic commented Oct 31, 2020

This PR addresses several JdbcIndexedSessionRepository concerns.

This first commit of this PR provides JdbcIndexedSessionRepository customizers for the following SQL dialects:

  • PostgreSQL
  • MySQL (also used by MariaDB)
  • SQL Server
  • IBM DB2
  • Oracle

These customizers are intended to address the concurrency issues occurring on insert of new session attribute by applying SQL dialect specific SQL upsert/merge statement instead of a generic insert.

This closes #1213.

The second commit optimizes the SQL statement used to insert session attribute so that it doesn't perform a nested select statement.

This closes #1550.

Final commit reduces the JDBC integration tests to only single (latest) version per RDBMS vendor, due to a growing number of integration tests. Additionally, the configuration of most containers is simplified due to improved defaults within the Testcontainers library.

@vpavic vpavic added type: enhancement A general enhancement in: jdbc labels Oct 31, 2020
@vpavic vpavic added this to the 2.5.x milestone Oct 31, 2020
@vpavic vpavic self-assigned this Oct 31, 2020
@vpavic vpavic force-pushed the gh-1213 branch 3 times, most recently from 502333b to e4862f2 Compare November 1, 2020 17:27
@vpavic vpavic changed the title [WIP] Provide database specific JdbcIndexedSessionRepository customizers Provide database specific JdbcIndexedSessionRepository customizers Nov 1, 2020
@vpavic vpavic marked this pull request as draft November 2, 2020 06:40
@vpavic vpavic modified the milestones: 2.5.x, 2.5.0-M1 Nov 11, 2020
@vpavic vpavic marked this pull request as ready for review November 11, 2020 00:07
@vpavic vpavic requested a review from rwinch November 11, 2020 00:07
@vpavic
Copy link
Contributor Author

vpavic commented Nov 24, 2020

@jkuipers, since you also went at depth with this issue, I'd appreciate your feedback on the changes proposed by this PR?

This commit provides JdbcIndexedSessionRepository customizers for the following SQL dialects:

- PostgreSQL
- MySQL (also used by MariaDB)
- SQL Server
- IBM DB2
- Oracle

These customizers are intended to address the concurrency issues occurring on insert of new session attribute by applying SQL dialect specific SQL upsert/merge statement instead of a generic insert.

Closes: spring-projects#1213
At present, the SQL statement used to insert a session attribute record contains a nested select statement that verifies the existence of parent record in the session table. Such approach can be susceptible to deadlocks on certain RDMBSs.

This commit optimizes the SQL statement used to insert session attribute so that it doesn't perform a nested select statement.

Closes: spring-projects#1550
This commit reduces the JDBC integration tests to only single (latest) version per RDBMS vendor, due to a growing number of integration tests. Additionally, the configuration of most containers is simplified due to improved defaults within the Testcontainers library.
@vpavic vpavic merged commit 2aae51b into spring-projects:master Nov 27, 2020
@vpavic vpavic deleted the gh-1213 branch November 27, 2020 23:44
@jkuipers
Copy link
Contributor

jkuipers commented Dec 1, 2020

@jkuipers, since you also went at depth with this issue, I'd appreciate your feedback on the changes proposed by this PR?

Hey Vedran,

I just had a look (sorry, wasn't able to get to this earlier). I like the approach that there can now be different SQL for different DBMSs. I also noticed a commit that makes the code use the session's primary key again instead of a nested select by session ID, which I think is the right approach.
What is still missing is that the new SQL to upsert the attributes is only used for session creation but not updating. That means that two requests adding the same session attribute still causes errors. I believe that this code should use the same query: it's an upsert, so it works for both new AND existing sessions. That fixes another long-standing problem, then.
I ran my tests from https://github.com/jkuipers/spring-session-jdbc-sql-server-bugs against the latest snapshot, and they clearly show that the session update is still an issue.
Scratch all that, I forgot to add the SqlServerJdbcIndexedSessionRepositoryCustomizer as a bean.

On another note: the new MS SQL Server SQL is I think semantically equivalent to what I came up with in my own fork. Both my version and the one currently on master still sporadically cause deadlocks in SQL Server, so even though it's a big improvement over the old code, the problem isn't solved 100% yet.
If I come up with a fix for that I'll send it a merge request.

@vpavic
Copy link
Contributor Author

vpavic commented Dec 1, 2020

Thanks for following up on this @jkuipers, good to hear you're seeing these changes as an improvement in practice. 👍

Regarding the remaining reasons for deadlocks, they are IMO due to expired session clean-up job. See #838 - this is something that to my knowledge affects MySQL/MariaDB and SQL Server users.

I opened #1739 yesterday to take a closer look at improving the clean-up job, so feel free to track that one as well.

@jkuipers
Copy link
Contributor

jkuipers commented Dec 2, 2020

Using my tests, after updating to a snapshot of Spring Session and registering the SqlServerJdbcIndexedSessionRepositoryCustomizer as a bean, I can trigger the deadlock by simply creating several sessions in parallel still. I therefore do not think that this is related to the cleanup, I don't think that cleanup ever runs during these tests.
My customer, who's using my fork that includes a MERGE statement for upserting the session attributes that's almost the same as what you have on master right now, ran into this problem in production yesterday when their application was under heavy load.

I've examined the problem yesterday evening. It appears that for some reason, the combination of INSERTing a new session and then using the MERGE statement to insert the corresponding session attributes can trigger deadlocks in MS SQL Server. I tried several things first like making the primary key clustered, changing isolation levels and some other things that didn't help.
Then I updated the code to only use the MERGE statement when updating the attributes of an existing session, and to use a simple INSERT for attributes of a newly created session as there's no actual need for an upsert there.
For my test, this completely eliminated the problem.

@vpavic
Copy link
Contributor Author

vpavic commented Dec 2, 2020

That's interesting, thanks for sharing @jkuipers.
I'll reopen #1550 to take another look at that. Limiting the use of upsert to existing sessions does make sense.

@dlamblin
Copy link

Pardon which release was this merged into? I'd like to try the MySQL specific customizations, but can't quite see whether a version is out that includes them.

I'm confused a bit because while I used the mavenBom 'org.springframework.session:spring-session-bom:2020.0.3' I'm not exactly sure how that relates to Spring or Spring Boot releases much less spring-session releases. Thank you.

--
For context, when I'm using spring-session-jdbc with an aws aurora mysql 5.7 single instance RDS which is connected by a spring.datasource.url=jdbc:mariadb://... string to my Spring Boot 2.3.9.RELEASE app, I can't complete a single request to a static resource, nor a simple controller that returns "hello"; they hang and eventually time out.

The clean up job deadlocks, like #838, but requests timeout for a lock wait on updating the SPRING_SESSION too, which I think is related to the clean up deadlocking.

HTTP Status 500 – Internal Server Error
Type Exception Report

Message PreparedStatementCallback; SQL [UPDATE SPRING_SESSION SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? WHERE PRIMARY_ID = ?]; (conn=426748) Lock wait timeout exceeded; try restarting transaction; nested exception is java.sql.SQLTransientConnectionException: (conn=426748) Lock wait timeout exceeded; try restarting transaction

Description The server encountered an unexpected condition that prevented it from fulfilling the request.

Exception

org.springframework.dao.CannotAcquireLockException: PreparedStatementCallback; SQL [UPDATE SPRING_SESSION SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? WHERE PRIMARY_ID = ?]; (conn=426748) Lock wait timeout exceeded; try restarting transaction; nested exception is java.sql.SQLTransientConnectionException: (conn=426748) Lock wait timeout exceeded; try restarting transaction
	org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:267)
	org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
	org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1443)
	org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:633)
	org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:862)
	org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:917)
	org.springframework.session.jdbc.JdbcIndexedSessionRepository$JdbcSession.lambda$save$10(JdbcIndexedSessionRepository.java:807)
	org.springframework.transaction.support.TransactionOperations.lambda$executeWithoutResult$0(TransactionOperations.java:68)
	org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
	org.springframework.transaction.support.TransactionOperations.executeWithoutResult(TransactionOperations.java:67)
	org.springframework.session.jdbc.JdbcIndexedSessionRepository$JdbcSession.save(JdbcIndexedSessionRepository.java:802)
	org.springframework.session.jdbc.JdbcIndexedSessionRepository$JdbcSession.access$200(JdbcIndexedSessionRepository.java:634)
	org.springframework.session.jdbc.JdbcIndexedSessionRepository.save(JdbcIndexedSessionRepository.java:410)
	org.springframework.session.jdbc.JdbcIndexedSessionRepository.save(JdbcIndexedSessionRepository.java:131)
	org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:225)
	org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:192)
	org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:144)
	org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)
	org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)

@vpavic
Copy link
Contributor Author

vpavic commented May 14, 2021

@dlamblin This will be a part of upcoming Spring Session 2.5.0, which in turn is a part of Spring Session BOM 2021.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: jdbc type: enhancement A general enhancement
Projects
None yet
3 participants