HHH-14691 Small optimisation for updating Query Cache Statistics

This commit is contained in:
Sanne Grinovero 2021-06-22 11:16:22 +01:00 committed by Sanne Grinovero
parent e91901946a
commit 916849a8af
3 changed files with 82 additions and 27 deletions

View File

@ -563,24 +563,21 @@ public class StatisticsImpl implements StatisticsImplementor, Service, Manageabl
} }
@Override @Override
public CacheRegionStatisticsImpl getQueryRegionStatistics(String regionName) { public CacheRegionStatisticsImpl getQueryRegionStatistics(final String regionName) {
final CacheRegionStatisticsImpl existing = l2CacheStatsMap.get( regionName ); return l2CacheStatsMap.getOrCompute( regionName, this::computeQueryRegionStatistics );
if ( existing != null ) {
return existing;
}
final QueryResultsCache regionAccess = cache
.getQueryResultsCacheStrictly( regionName );
if ( regionAccess == null ) {
return null;
}
return l2CacheStatsMap.getOrCompute(
regionName,
s -> new CacheRegionStatisticsImpl( regionAccess.getRegion() )
);
} }
private CacheRegionStatisticsImpl computeQueryRegionStatistics(final String regionName) {
final QueryResultsCache regionAccess = cache.getQueryResultsCacheStrictly( regionName );
if ( regionAccess == null ) {
return null; //this null value will be cached
}
else {
return new CacheRegionStatisticsImpl( regionAccess.getRegion() );
}
}
@Override @Override
public CacheRegionStatisticsImpl getCacheRegionStatistics(String regionName) { public CacheRegionStatisticsImpl getCacheRegionStatistics(String regionName) {
if ( ! secondLevelCacheEnabled ) { if ( ! secondLevelCacheEnabled ) {

View File

@ -25,7 +25,8 @@ import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
*/ */
final class StatsNamedContainer<V> { final class StatsNamedContainer<V> {
private final ConcurrentMap<String,V> map; private final ConcurrentMap<String,Object> map;
private final static Object NULL_TOKEN = new Object();
/** /**
* Creates a bounded container - based on BoundedConcurrentHashMap * Creates a bounded container - based on BoundedConcurrentHashMap
@ -63,33 +64,39 @@ final class StatsNamedContainer<V> {
* sure the function is invoked at most once: we don't need this guarantee, and prefer to reduce risk of blocking. * 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) { public V getOrCompute(final String key, final Function<String, V> function) {
final V v1 = map.get( key ); final Object v1 = map.get( key );
if ( v1 != null ) { if ( v1 != null ) {
return v1; if ( v1 == NULL_TOKEN ) {
return null;
}
return (V) v1;
} }
else { else {
final V v2 = function.apply( key ); final V v2 = function.apply( key );
//Occasionally a function might return null. We can't store a null in the CHM,
// so a placeholder would be required to implement that, but we prefer to just keep this
// situation as slightly sub-optimal so to not make the code more complex just to handle the exceptional case:
// null values are assumed to be rare enough for this not being worth it.
if ( v2 == null ) { if ( v2 == null ) {
map.put( key, NULL_TOKEN );
return null; return null;
} }
else { else {
final V v3 = map.putIfAbsent( key, v2 ); final Object v3 = map.putIfAbsent( key, v2 );
if ( v3 == null ) { if ( v3 == null ) {
return v2; return v2;
} }
else { else {
return v3; return (V) v3;
} }
} }
} }
} }
public V get(final String key) { public V get(final String key) {
return map.get( key ); final Object o = map.get( key );
if ( o == NULL_TOKEN) {
return null;
}
else {
return (V) o;
}
} }
} }

View File

@ -6,14 +6,21 @@
*/ */
package org.hibernate.stat.internal; package org.hibernate.stat.internal;
import java.util.concurrent.atomic.AtomicInteger;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@TestForIssue(jiraKey = "HHH-13645") @TestForIssue(jiraKey = "HHH-13645")
public class StatsNamedContainerNullComputedValueTest { public class StatsNamedContainerNullComputedValueTest {
private final static AtomicInteger invocationCounterNullProducer = new AtomicInteger();
private final static AtomicInteger invocationCounterValueProducer = new AtomicInteger();
@Test @Test
public void testNullComputedValue() { public void testNullComputedValue() {
final StatsNamedContainer statsNamedContainer = new StatsNamedContainer<Integer>(); final StatsNamedContainer statsNamedContainer = new StatsNamedContainer<Integer>();
@ -27,4 +34,48 @@ public class StatsNamedContainerNullComputedValueTest {
); );
} }
} @Test
public void abletoStoreNullValues() {
final StatsNamedContainer statsNamedContainer = new StatsNamedContainer<Integer>();
Assert.assertEquals( 0, invocationCounterNullProducer.get() );
assertNull( getCacheWithNullValue( statsNamedContainer ) );
Assert.assertEquals( 1, invocationCounterNullProducer.get() );
assertNull( getCacheWithNullValue( statsNamedContainer ) );
Assert.assertEquals( 1, invocationCounterNullProducer.get() );
}
@Test
public void abletoStoreActualValues() {
final StatsNamedContainer statsNamedContainer = new StatsNamedContainer<Integer>();
Assert.assertEquals( 0, invocationCounterValueProducer.get() );
Assert.assertEquals( 5, getCacheWithActualValue( statsNamedContainer ) );
Assert.assertEquals( 1, invocationCounterValueProducer.get() );
Assert.assertEquals( 5, getCacheWithActualValue( statsNamedContainer ) );
Assert.assertEquals( 1, invocationCounterValueProducer.get() );
}
private Object getCacheWithActualValue(StatsNamedContainer statsNamedContainer) {
return statsNamedContainer.getOrCompute(
"key",
StatsNamedContainerNullComputedValueTest::produceValue
);
}
private Object getCacheWithNullValue(StatsNamedContainer statsNamedContainer) {
return statsNamedContainer.getOrCompute(
"key",
StatsNamedContainerNullComputedValueTest::produceNull
);
}
private static Integer produceValue(Object o) {
invocationCounterValueProducer.getAndIncrement();
return Integer.valueOf( 5 );
}
private static Integer produceNull(Object v) {
invocationCounterNullProducer.getAndIncrement();
return null;
}
}