HHH-13527 Fix contention in StatisticsImpl#getDomainDataRegionStatistics()

This commit is contained in:
Sanne Grinovero 2019-08-06 09:17:53 +01:00
parent 7cb828f4e7
commit b07e4b1ba3
2 changed files with 111 additions and 26 deletions

View File

@ -6,8 +6,6 @@
*/
package org.hibernate.stat.internal;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
@ -16,8 +14,6 @@ import org.hibernate.cache.spi.QueryResultsRegion;
import org.hibernate.cache.spi.Region;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.Service;
@ -31,6 +27,7 @@ import static org.hibernate.internal.CoreLogging.messageLogger;
* Implementation of {@link org.hibernate.stat.Statistics} based on the {@link java.util.concurrent} package.
*
* @author Alex Snaps
* @author Sanne Grinovero
*/
@SuppressWarnings({ "unchecked" })
public class StatisticsImpl implements StatisticsImplementor, Service, Manageable {
@ -91,21 +88,21 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
private final LongAdder optimisticFailureCount = new LongAdder();
private final ConcurrentMap<String,EntityStatisticsImpl> entityStatsMap = new ConcurrentHashMap();
private final ConcurrentMap<String,NaturalIdStatisticsImpl> naturalIdQueryStatsMap = new ConcurrentHashMap();
private final ConcurrentMap<String,CollectionStatisticsImpl> collectionStatsMap = new ConcurrentHashMap();
private final StatsNamedContainer<EntityStatisticsImpl> entityStatsMap = new StatsNamedContainer();
private final StatsNamedContainer<NaturalIdStatisticsImpl> naturalIdQueryStatsMap = new StatsNamedContainer();
private final StatsNamedContainer<CollectionStatisticsImpl> collectionStatsMap = new StatsNamedContainer();
/**
* Keyed by query string
*/
private final BoundedConcurrentHashMap<String, QueryStatisticsImpl> queryStatsMap;
private final StatsNamedContainer<QueryStatisticsImpl> queryStatsMap;
/**
* Keyed by region name
*/
private final ConcurrentMap<String,CacheRegionStatisticsImpl> l2CacheStatsMap = new ConcurrentHashMap<>();
private final StatsNamedContainer<CacheRegionStatisticsImpl> l2CacheStatsMap = new StatsNamedContainer<>();
private final ConcurrentMap<String,DeprecatedNaturalIdCacheStatisticsImpl> deprecatedNaturalIdStatsMap = new ConcurrentHashMap();
private final StatsNamedContainer<DeprecatedNaturalIdCacheStatisticsImpl> deprecatedNaturalIdStatsMap = new StatsNamedContainer();
@SuppressWarnings({ "UnusedDeclaration" })
@ -115,12 +112,11 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
public StatisticsImpl(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;
this.queryStatsMap = new BoundedConcurrentHashMap(
this.queryStatsMap = new StatsNamedContainer(
sessionFactory != null ?
sessionFactory.getSessionFactoryOptions().getQueryStatisticsMaxSize() :
Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE,
20,
BoundedConcurrentHashMap.Eviction.LRU
20
);
clear();
}
@ -213,7 +209,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
@Override
public String[] getEntityNames() {
if ( sessionFactory == null ) {
return ArrayHelper.toStringArray( entityStatsMap.keySet() );
return entityStatsMap.keysAsArray();
}
else {
return sessionFactory.getMetamodel().getAllEntityNames();
@ -226,7 +222,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
return null;
}
return entityStatsMap.computeIfAbsent(
return entityStatsMap.getOrCompute(
entityName,
s -> new EntityStatisticsImpl( sessionFactory.getMetamodel().entityPersister( entityName ) )
);
@ -326,7 +322,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
@Override
public String[] getCollectionRoleNames() {
if ( sessionFactory == null ) {
return ArrayHelper.toStringArray( collectionStatsMap.keySet() );
return collectionStatsMap.keysAsArray();
}
else {
return sessionFactory.getMetamodel().getAllCollectionRoles();
@ -339,7 +335,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
return null;
}
return collectionStatsMap.computeIfAbsent(
return collectionStatsMap.getOrCompute(
role,
s -> new CollectionStatisticsImpl( sessionFactory.getMetamodel().collectionPersister( role ) )
);
@ -431,7 +427,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
return null;
}
return naturalIdQueryStatsMap.computeIfAbsent(
return naturalIdQueryStatsMap.getOrCompute(
rootEntityName,
s -> {
final EntityPersister entityDescriptor = sessionFactory.getMetamodel().entityPersister( rootEntityName );
@ -445,8 +441,9 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
@Override
public DeprecatedNaturalIdCacheStatisticsImpl getNaturalIdCacheStatistics(String regionName) {
return deprecatedNaturalIdStatsMap.computeIfAbsent(
sessionFactory.getCache().unqualifyRegionName( regionName ),
final String key = sessionFactory.getCache().unqualifyRegionName( regionName );
return deprecatedNaturalIdStatsMap.getOrCompute(
key,
unqualifiedRegionName -> new DeprecatedNaturalIdCacheStatisticsImpl(
unqualifiedRegionName,
sessionFactory.getCache().getNaturalIdAccessesInRegion( unqualifiedRegionName )
@ -585,7 +582,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
return null;
}
return l2CacheStatsMap.computeIfAbsent(
return l2CacheStatsMap.getOrCompute(
regionName,
s -> {
final Region region = sessionFactory.getCache().getRegion( regionName );
@ -622,7 +619,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
return null;
}
return l2CacheStatsMap.computeIfAbsent(
return l2CacheStatsMap.getOrCompute(
regionName,
s -> new CacheRegionStatisticsImpl( regionAccess.getRegion() )
);
@ -638,7 +635,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
return null;
}
return l2CacheStatsMap.computeIfAbsent(
return l2CacheStatsMap.getOrCompute(
regionName,
s -> {
Region region = sessionFactory.getCache().getRegion( regionName );
@ -718,12 +715,12 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
@Override
public String[] getQueries() {
return ArrayHelper.toStringArray( queryStatsMap.keySet() );
return queryStatsMap.keysAsArray();
}
@Override
public QueryStatisticsImpl getQueryStatistics(String queryString) {
return queryStatsMap.computeIfAbsent(
return queryStatsMap.getOrCompute(
queryString,
s -> new QueryStatisticsImpl( queryString )
);
@ -850,7 +847,7 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
}
private CacheRegionStatisticsImpl getQueryRegionStats(String regionName) {
return l2CacheStatsMap.computeIfAbsent(
return l2CacheStatsMap.getOrCompute(
regionName,
s -> new CacheRegionStatisticsImpl( sessionFactory.getCache().getQueryResultsCache( regionName ).getRegion() )
);

View File

@ -0,0 +1,88 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.stat.internal;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
/**
* Decorates a ConcurrentHashMap implementation to make sure the methods are being
* used correctly for the purpose of Hibernate's statistics.
* In particular, we do like the semantics of ConcurrentHashMap#computeIfAbsent
* but not how it performs.
* See also:
* - http://jsr166-concurrency.10961.n7.nabble.com/Re-ConcurrentHashMap-computeIfAbsent-td11687.html
* - https://hibernate.atlassian.net/browse/HHH-13527
*
* @author Sanne Grinovero
*/
final class StatsNamedContainer<V> {
private final ConcurrentMap<String,V> map;
/**
* Creates a bounded container - based on BoundedConcurrentHashMap
*/
StatsNamedContainer(int capacity, int concurrencyLevel) {
this.map = new BoundedConcurrentHashMap( capacity, concurrencyLevel, BoundedConcurrentHashMap.Eviction.LRU );
}
/**
* Creates an unbounded container - based on ConcurrentHashMap
*/
StatsNamedContainer() {
this.map = new ConcurrentHashMap<>( );
}
public void clear() {
map.clear();
}
/**
* This method is inherently racy and expensive. Only use on non-hot paths, and
* only to get a recent snapshot.
* @return all keys in string form.
*/
public String[] keysAsArray() {
return map.keySet().toArray( new String[0] );
}
/**
* Similar semantics as you'd get by invoking {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)},
* but we check for the key existence first.
* This is technically a redundant check, but it has been shown to perform better when the key existing is very likely,
* as in our case.
* Most notably, the ConcurrentHashMap implementation might block other accesses for the sake of making
* sure the function is invoked at most once: we don't need this guarantee, and prefer to reduce risk of blocking.
*/
public V getOrCompute(final String key, final Function<String, V> function) {
final V v1 = map.get( key );
if ( v1 != null ) {
return v1;
}
else {
final V v2 = function.apply( key );
final V v3 = map.putIfAbsent( key, v2 );
if ( v3 == null ) {
return v2;
}
else {
return v3;
}
}
}
public V get(final String key) {
return map.get( key );
}
}