Skip to content

Commit 371045b

Browse files
authored
Merge pull request #3459 from AtlasOfLivingAustralia/feature/issue3458
Smarter stats loading to prevent blocking homepage load #3458
2 parents 95d8dfa + 999d75d commit 371045b

File tree

5 files changed

+67
-22
lines changed

5 files changed

+67
-22
lines changed

grails-app/controllers/au/org/ala/merit/ReportController.groovy

+1-1
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ class ReportController {
486486

487487
def statisticsReport() {
488488
int exclude = session.lastGroup?:-1
489-
def statistics = statisticsFactory.randomGroup(exclude)
489+
def statistics = statisticsFactory.randomGroup(exclude, true)
490490
session.lastGroup = statistics.group
491491
render view:'_statistics', layout:'ajax', model:[statistics:statistics.statistics]
492492
}

grails-app/views/home/public.gsp

+12-3
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,29 @@
5959
<asset:deferredScripts/>
6060
<script>
6161
$(function() {
62-
62+
var working = false;
6363
var url = '${g.createLink(controller:'report', action:'statisticsReport')}';
6464
65-
var working = false;
66-
$('#stats-holder').on('click', '.show-more-stats', function() {
65+
function showMoreStats() {
6766
if (!working) {
6867
working = true;
6968
replaceContentSection('.statistics', url).always(function() { working = false; });
7069
}
70+
}
71+
72+
$('#stats-holder').on('click', '.show-more-stats', function() {
73+
showMoreStats();
7174
});
7275
7376
if ($('#latest-news').height() > 400) {
7477
$('#latest-news').height(400).css('overflow-y', 'scroll');
7578
}
79+
80+
var hasStatistics = ${statistics ? 'true' : 'false'};
81+
if (!hasStatistics) {
82+
showMoreStats();
83+
}
84+
7685
});
7786
7887
</script>

grails-app/views/report/_statistics.gsp

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<div class="statistics">
2+
<g:if test="${statistics}">
23
%{--This section expects exactly 6 statistics to display--}%
34
<div class="row">
45
<div class="col-sm-4 box1">
@@ -22,7 +23,9 @@
2223
<g:render template="/report/statistic" model="${statistics[5]}"/>
2324
</div>
2425
</div>
26+
2527
<div class="row">
2628
<div class="col-sm-12 align-content-center text-center"><a href="#" class="show-more-stats text-dark">Show more stats <i class="fa fa-refresh"></i></a></div>
2729
</div>
30+
</g:if>
2831
</div>

src/integration-test/groovy/au/org/ala/fieldcapture/HomeIndexPageSpec.groovy

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ class HomeIndexPageSpec extends StubbedCasSpec {
3030
to HomePage
3131

3232
then:
33-
waitFor 30,{ at HomePage}
34-
33+
waitFor 60, { // The homepage statistics are loaded via AJAX when the cache is cleared
34+
box1.size() == 1
35+
}
3536
and:
3637
box1[0].statTitle.text() == "THREATENED SPECIES STRATEGY"
3738
box1[0].statValue.text() =="3"

src/main/groovy/au/org/ala/merit/StatisticsFactory.groovy

+48-16
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class StatisticsFactory {
2929
@Autowired
3030
GrailsApplication grailsApplication
3131

32+
/** Lazily initialized in the getConfig method based on the HOME_PAGE_STATISTICS setting */
33+
private Map config
34+
3235
public StatisticsFactory() {}
3336

3437
private String initialize() {
@@ -46,29 +49,52 @@ class StatisticsFactory {
4649
public synchronized void clearConfig() {
4750
Cache cache = grailsCacheManager.getCache(STATISTICS_CACHE_REGION)
4851
cache.clear()
52+
config = null
4953
}
5054

5155
private synchronized Map getConfig() {
52-
String config = grailsCacheManager.getCache(STATISTICS_CACHE_REGION).get(CONFIG_KEY)?.get()
53-
if (!config) {
54-
config = initialize()
55-
grailsCacheManager.getCache(STATISTICS_CACHE_REGION).put(CONFIG_KEY, config)
56+
String configString = grailsCacheManager.getCache(STATISTICS_CACHE_REGION).get(CONFIG_KEY)?.get()
57+
// We check for the config string cache instead of whether this.config is null because
58+
// this allows the generic admin cache page to clear the cache and allow a config update
59+
// without having to know to call StatisticsFactory.clearConfig()
60+
// We can't cache the parsed config directly as it is not serializable.
61+
if (!configString || !this.config) { // Check both as the cache can survive a restart sometimes, but not the field.
62+
configString = initialize()
63+
grailsCacheManager.getCache(STATISTICS_CACHE_REGION).put(CONFIG_KEY, configString)
64+
JsonSlurper jsonSlurper = new JsonSlurper()
65+
this.config = Collections.synchronizedMap(jsonSlurper.parseText(configString))
5666
}
57-
JsonSlurper jsonSlurper = new JsonSlurper()
58-
jsonSlurper.parseText(config)
67+
68+
this.config
5969
}
6070

61-
public synchronized List<Map> getStatisticsGroup(int groupNumber) {
71+
public List<Map> getStatisticsGroupFromCache(int groupNumber) {
72+
fromCache(groupNumber)
73+
}
74+
75+
public List<Map> getStatisticsGroup(int groupNumber) {
6276

6377
Map config = getConfig()
64-
List<Map> statistics = fromCache(groupNumber)
65-
if (!statistics) {
66-
log.info("Cache miss for homepage stats, key: ${groupNumber}")
67-
statistics = this.config.groups[groupNumber].collect { statisticName ->
68-
Map statistic = config.statistics[statisticName]
69-
evaluateStatistic(statistic)
78+
List groupConfig = config.groups[groupNumber]
79+
if (!groupConfig) {
80+
log.error("No configuration found for group number: ${groupNumber}")
81+
return null
82+
}
83+
84+
List<Map> statistics
85+
// Only one thread should recalculate the statistics. The config is a shared
86+
// synchronized instance so is safe to synchronize on.
87+
synchronized (groupConfig) {
88+
statistics = fromCache(groupNumber)
89+
if (!statistics) {
90+
log.info("Cache miss for homepage stats, key: ${groupNumber} - recalculating...")
91+
statistics = groupConfig.collect { statisticName ->
92+
Map statistic = config.statistics[statisticName]
93+
evaluateStatistic(statistic)
94+
}
95+
log.info("Caching homepage stats, key: ${groupNumber}")
96+
cache(groupNumber, statistics)
7097
}
71-
cache(groupNumber, statistics)
7298
}
7399
statistics
74100
}
@@ -87,13 +113,19 @@ class StatisticsFactory {
87113
stats
88114
}
89115

90-
public Map randomGroup(int exclude = -1) {
116+
public Map randomGroup(int exclude = -1, boolean recacluateIfMissing = false) {
91117
int groupCount = getGroupCount()
92118
int group = Math.floor(Math.random()*groupCount)
93119
while (group == exclude) {
94120
group = Math.floor(Math.random()*groupCount)
95121
}
96-
List stats = getStatisticsGroup(group)
122+
List stats
123+
if (recacluateIfMissing) {
124+
stats = getStatisticsGroup(group)
125+
}
126+
else {
127+
stats = getStatisticsGroupFromCache(group)
128+
}
97129

98130
[group:group, statistics:stats]
99131
}

0 commit comments

Comments
 (0)