Skip to content

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