Skip to content

Concurrent access to ParsedSql cache in NamedParameterJdbcTemplate #24197

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

Closed
wants to merge 1 commit into from
Closed

Concurrent access to ParsedSql cache in NamedParameterJdbcTemplate #24197

wants to merge 1 commit into from

Conversation

benelog
Copy link
Contributor

@benelog benelog commented Dec 12, 2019

NamedParameterJdbcTemplate.getParsedSql(Sting) can be a bottleneck under high load as it holds a global lock by this.parsedSqlCache.

		synchronized (this.parsedSqlCache) {
			ParsedSql parsedSql = this.parsedSqlCache.get(sql);
			if (parsedSql == null) {
				parsedSql = NamedParameterUtils.parseSqlStatement(sql);
				this.parsedSqlCache.put(sql, parsedSql);
			}
			return parsedSql;
		}

This PR tried to improve the performance by the combination of a ConcurrentHashMap and a LinkedHashMap.

I referenced 06c6cbb6b92 and
#7831 (comment)

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 12, 2019
@rengy-github
Copy link

Is it necessary to cache twice
why need to use synchronized when using concurrenthashmap
Can just modify synchronized

		ParsedSql parsedSql = this.parsedSqlCache.get(sql);
		if (parsedSql == null) {
                            synchronized (this.parsedSqlCache) {
			    parsedSql = NamedParameterUtils.parseSqlStatement(sql);
			    this.parsedSqlCache.put(sql, parsedSql);
                           }
		}
		return parsedSql;

@jhoeller jhoeller self-assigned this Feb 14, 2020
@jhoeller jhoeller added type: enhancement A general enhancement in: data Issues in data modules (jdbc, orm, oxm, tx) and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Feb 14, 2020
@jhoeller jhoeller added this to the 5.2.4 milestone Feb 14, 2020
@benelog
Copy link
Contributor Author

benelog commented Feb 17, 2020

@rengy-github
LinkedHashMap.get() is not thread-safe when LinkedHashMap.put() is called from other threads, even if LinkedHashMap.put() is called from synchronized blocks.

@rengy-github
Copy link

@benelog
get doesn't need synchronization
ParsedSql parsedSql = this.parsedSqlCache.get(sql);
if (parsedSql == null) {
synchronized (this.parsedSqlCache) {
parsedSql = this.parsedSqlCache.get(sql);
if(parsedSql==null) {
parsedSql = NamedParameterUtils.parseSqlStatement(sql);
this.parsedSqlCache.put(sql, parsedSql);
}
}
}

@benelog
Copy link
Contributor Author

benelog commented Feb 17, 2020

@rengy-github
Reading the following issue will help you understand my PR.

#7831

@rengy-github
Copy link

I think don't use sync or just put
because There is no problem that SQL is parsed twice occasionally

@rengy-github
Copy link

My puzzle is whether there is problem with the other thread get when one thread puts

Could you send me a link about this, thank you

@benelog
Copy link
Contributor Author

benelog commented Feb 19, 2020

@rengy-github
The Javadoc of LinkedHashMap will be helpful for you.

Note that this implementation is not synchronized. If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.
....
A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with get is a structural modification. )

For the above reason, Collections.synchronizedMap(Map<K,V> m) returns a wrapper to call Map.get(Object key) from a synchronized block.

https://github.com/AdoptOpenJDK/openjdk-jdk/blob/master/src/java.base/share/classes/java/util/Collections.java#L2634

        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

Calling LinkedHashMap.get() from outside of synchronized blocks can cause memory leaks, the problem actually happened in our company.
The following article explains the incident.

https://d2.naver.com/helloworld/1326256
(Unfortunately, it was written in Korean language)

The following artice is about similar issues in HashMap.

https://mailinator.blogspot.com/2009/06/beautiful-race-condition.html

@jhoeller jhoeller modified the milestones: 5.2.4, 5.2.5 Feb 20, 2020
@rengy-github
Copy link

@benelog
thank you very much,Now I understand why use a ConcurrentHashMap and a LinkedHashMap

@jhoeller jhoeller modified the milestones: 5.2.5, 5.2.6 Mar 21, 2020
@jhoeller jhoeller changed the title Performance improvement on NamedParameterJdbcTemplate Concurrent access to ParsedSql cache in NamedParameterJdbcTemplate Apr 25, 2020
@jhoeller jhoeller modified the milestones: 5.2.6, 5.3 M2 Apr 26, 2020
@jhoeller jhoeller modified the milestones: 5.3 M2, 5.3 RC1 Jun 23, 2020
@jhoeller
Copy link
Contributor

jhoeller commented Aug 26, 2020

I've addressed this using a revised version our own ConcurrentLruCache for 5.3 RC1 now, extracted from MimeTypeUtils (where it got introduced in 5.2), also available for reuse in other classes. To be committed ASAP.

@jhoeller jhoeller closed this in d198c44 Aug 26, 2020
@andrei-ivanov
Copy link

now #22789 got fixed too 👍

@benelog benelog deleted the enhance-named-parameter-jdbc-template branch September 2, 2020 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants