Skip to content

Commit

Permalink
Caches are now all sized based on entries, due to the following issue.
Browse files Browse the repository at this point in the history
ehcache/ehcache3#3008
Cache sizes are determined when they are initialized, except for the 2 bill caches, which remain in app.properties.
  • Loading branch information
JacobKeegan committed Apr 19, 2022
1 parent f1dd4da commit 0e76c19
Show file tree
Hide file tree
Showing 14 changed files with 83 additions and 213 deletions.
6 changes: 0 additions & 6 deletions src/main/java/gov/nysenate/openleg/api/admin/CacheCtrl.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import gov.nysenate.openleg.legislation.committee.CommitteeSessionId;
import gov.nysenate.openleg.legislation.law.LawVersionId;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -40,8 +38,6 @@
@RestController
@RequestMapping(value = BASE_ADMIN_API_PATH + "/cache")
public class CacheCtrl extends BaseCtrl {
private static final Logger logger = LoggerFactory.getLogger(CacheCtrl.class);

@Autowired
private EventBus eventBus;

Expand Down Expand Up @@ -186,8 +182,6 @@ private Object getContentId(CacheType targetCache, WebRequest request)
case API_USER:
requireParameters(request, "key", "string");
return request.getParameter("key");
case NOTIFICATION:
return "all subscriptions";
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class CachedApiUserService extends CachingService<String, ApiUser> implements ApiUserService {
Expand All @@ -48,11 +50,9 @@ protected CacheType cacheType() {
/*** --- CachingService Implementation --- */

@Override
public void warmCaches() {
evictCache();
logger.info("Warming up API User Cache");
// Feed in all the api users from the database into the cache
apiUserDao.getAllUsers().forEach(user -> putCacheEntry(user.getApiKey(), user));
public Map<String, ApiUser> initialEntries() {
return apiUserDao.getAllUsers().stream()
.collect(Collectors.toMap(ApiUser::getApiKey, user -> user));
}

/** --- ApiUserService Implementation --- */
Expand Down
36 changes: 2 additions & 34 deletions src/main/java/gov/nysenate/openleg/legislation/CacheType.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,6 @@
* up upon request.
*/
public enum CacheType {
AGENDA(true),
BILL(true, false),
BILL_INFO(true, false),
CALENDAR(true),
LAW(true),
SESSION_MEMBER(true),
SHORTNAME(true),

API_USER(false, false),
COMMITTEE(false),
FULL_MEMBER(false),
NOTIFICATION(false),
SHIRO(false);

// Whether the number of units refers to entries (true) or megabytes (false).
private final boolean isSizedByEntries;
private final boolean warmOnStart;

CacheType(boolean isSizedByEntries) {
this(isSizedByEntries, true);
}

CacheType(boolean isSizedByEntries, boolean warmOnStart) {
this.isSizedByEntries = isSizedByEntries;
this.warmOnStart = warmOnStart;
}

public boolean isSizedByEntries() {
return isSizedByEntries;
}

public boolean isWarmOnStart() {
return warmOnStart;
}
AGENDA, API_USER, BILL, BILL_INFO, CALENDAR, COMMITTEE, FULL_MEMBER, LAW,
SESSION_MEMBER, SHORTNAME
}
51 changes: 28 additions & 23 deletions src/main/java/gov/nysenate/openleg/legislation/CachingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.internal.statistics.DefaultStatisticsService;
import org.ehcache.core.spi.service.StatisticsService;
import org.ehcache.core.statistics.CacheStatistics;
Expand All @@ -26,15 +25,18 @@
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Objects;
import java.util.EnumMap;
import java.util.Map;
import java.util.Set;

@Service
public abstract class CachingService<Key, Value> {
private static final Logger logger = LoggerFactory.getLogger(CachingService.class);
private static final StatisticsService statisticsService = new DefaultStatisticsService();
private static final CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.using(statisticsService).build(true);
// TODO: add this
private final static EnumMap<CacheType, Tuple<Class<?>, Class<?>>> classMap = new EnumMap<>(CacheType.class);

@Autowired
private Environment environment;
Expand All @@ -60,29 +62,31 @@ protected EvictionAdvisor<Key, Value> evictionAdvisor() {
@SuppressWarnings("unchecked")
@PostConstruct
protected void init() {
var type = cacheType();
var classes = ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments();
Class<Key> keyClass = (Class<Key>) classes[0];
Class<Value> valueClass = (Class<Value>) classes[1];
var keyClass = (Class<Key>) classes[0];
var valueClass = (Class<Value>) classes[1];
var type = cacheType();
var currCache = cacheManager.getCache(type.name().toLowerCase(), keyClass, valueClass);
if (currCache != null) {
this.cache = currCache;
return;
}
var initialEntries = initialEntries();
String propertyStr = type.name().toLowerCase() + ".cache." +
(type.isSizedByEntries() ? "entries" : "heap") + ".size";
int numUnits = Integer.parseInt(Objects.requireNonNull(environment.getProperty(propertyStr)));
int numEntries = (int) (initialEntries.size() * 1.2);
if (numEntries == 0) {
String size = environment.getProperty(type.name().toLowerCase() + ".cache.size");
numEntries = size == null ? 50 : Integer.parseInt(size);
}
var resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(numUnits, type.isSizedByEntries() ? EntryUnit.ENTRIES : MemoryUnit.MB);
.heap(numEntries, EntryUnit.ENTRIES);
var config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(keyClass, valueClass, resourcePoolsBuilder)
.withSizeOfMaxObjectGraph(type.isSizedByEntries() ? 100000 : 5000)
.withExpiry(ExpiryPolicy.NO_EXPIRY)
.withSizeOfMaxObjectGraph(100000).withExpiry(ExpiryPolicy.NO_EXPIRY)
.withEvictionAdvisor(evictionAdvisor());
this.cache = cacheManager.createCache(type.name(), config);
eventBus.register(this);
handleCacheWarmEvent(new CacheWarmEvent(Set.of(type)));
}

@PreDestroy
Expand All @@ -100,8 +104,8 @@ protected void putCacheEntry(Key key, Value value) {
cache.put(key, value);
}

public List<Tuple<Key, Value>> initialEntries() {
return List.of();
public Map<Key, Value> initialEntries() {
return Map.of();
}

/**
Expand All @@ -127,8 +131,9 @@ public void evictCache() {
*/
@Subscribe
public void handleCacheEvictEvent(CacheEvictEvent evictEvent) {
if (evictEvent.affects(cacheType()))
if (evictEvent.affects(cacheType())) {
evictCache();
}
}

/**
Expand All @@ -138,15 +143,11 @@ public void handleCacheEvictEvent(CacheEvictEvent evictEvent) {
*/
@Subscribe
public void handleCacheEvictIdEvent(CacheEvictIdEvent<Key> evictIdEvent) {
if (evictIdEvent.affects(cacheType()))
if (evictIdEvent.affects(cacheType())) {
evictContent(evictIdEvent.getContentId());
}
}

/**
* Pre-fetch a subset of currently active data and store it in the cache.
*/
public void warmCaches() {}

/**
* If a CacheWarmEvent is sent out on the event bus, the caching service
* should check to if it has any affected caches and warm them.
Expand All @@ -155,7 +156,11 @@ public void warmCaches() {}
*/
@Subscribe
public synchronized void handleCacheWarmEvent(CacheWarmEvent warmEvent) {
if (warmEvent.affects(cacheType()))
warmCaches();
if (warmEvent.affects(cacheType())) {
evictCache();
for (var entry : initialEntries().entrySet()) {
cache.put(entry.getKey(), entry.getValue());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class CachedAgendaDataService extends CachingService<AgendaId, Agenda> implements AgendaDataService {
Expand All @@ -30,17 +32,10 @@ protected CacheType cacheType() {
return CacheType.AGENDA;
}

/**
* Clears out the cache, then fills it with the 8 most recent agendas from this year.
*/
public void warmCaches() {
evictCache();
logger.info("Warming up agenda cache.");
List<AgendaId> ids = getAgendaIds(LocalDate.now().getYear(), SortOrder.DESC);
// Adds up to the most recent 8 agendas.
for (int i = 0; i < Math.min(7, ids.size()); i++)
getAgenda(ids.get(i));
logger.info("Done warming up agenda cache.");
@Override
public Map<AgendaId, Agenda> initialEntries() {
return getAgendaIds(LocalDate.now().getYear(), SortOrder.DESC).stream()
.limit(8).collect(Collectors.toMap(id -> id, id -> agendaDao.getAgenda(id)));
}

/** {@inheritDoc} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class CachedBillDataService extends CachingService<BaseBillId, Bill> impl
private CachedBillInfoDataService billInfoDataService;

@Service
public static class CachedBillInfoDataService extends CachingService<BaseBillId, BillInfo> {
static class CachedBillInfoDataService extends CachingService<BaseBillId, BillInfo> {
@Override
protected CacheType cacheType() {
return CacheType.BILL_INFO;
Expand Down Expand Up @@ -71,34 +71,6 @@ protected EvictionAdvisor<BaseBillId, Bill> evictionAdvisor() {
value.isPublished();
}

/**
* Pre-load the bill caches by clearing out each of their contents and then loading:
* Bill Cache - Current session year bills only
* Bill Info Cache - Bill Infos from all available session years.
*/
@Override
public void warmCaches() {
evictCache();
logger.info("Warming up bill cache.");
Optional<Range<SessionYear>> sessionRange = activeSessionRange();
if (sessionRange.isPresent()) {
SessionYear sessionYear = sessionRange.get().lowerEndpoint();
while (sessionYear.compareTo(sessionRange.get().upperEndpoint()) <= 0) {
if (sessionYear.equals(SessionYear.current())) {
logger.info("Caching Bill instances for current session year: {}", sessionYear);
// Don't load any text because that is not cached.
getBillIds(sessionYear, LimitOffset.ALL).forEach(this::getBill);
}
else {
logger.info("Caching Bill Info instances for session year: {}", sessionYear);
getBillIds(sessionYear, LimitOffset.ALL).forEach(this::getBillInfo);
}
sessionYear = sessionYear.nextSessionYear();
}
}
logger.info("Done warming up bill cache.");
}

/** {@inheritDoc} */
@Override
public void evictContent(BaseBillId baseBillId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

Expand All @@ -34,11 +35,10 @@ protected CacheType cacheType() {
return CacheType.CALENDAR;
}

/** {@inheritDoc} */
@Override
public void warmCaches() {
evictCache();
getCalendars(LocalDate.now().getYear(), SortOrder.ASC, LimitOffset.ALL);
public Map<CalendarId, Calendar> initialEntries() {
return calendarDao.getCalendarIds(LocalDate.now().getYear(), SortOrder.ASC, LimitOffset.ALL)
.stream().collect(Collectors.toMap(id -> id, id -> calendarDao.getCalendar(id)));
}

/** --- CalendarDataService implementation --- */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class CachedCommitteeDataService
Expand Down Expand Up @@ -52,14 +54,10 @@ protected EvictionAdvisor<CommitteeSessionId, CommitteeList> evictionAdvisor() {
value.stream().anyMatch(Committee::isCurrent);
}

/** {@inheritDoc} */
@Override
public void warmCaches() {
evictCache();
logger.info("Warming up committee cache.");
getCommitteeList(Chamber.SENATE, LimitOffset.ALL);
getCommitteeList(Chamber.ASSEMBLY, LimitOffset.ALL);
logger.info("Done warming up committee cache.");
public Map<CommitteeSessionId, CommitteeList> initialEntries() {
return committeeDao.getAllSessionIds().stream().collect(Collectors.toMap(id -> id,
id -> new CommitteeList(committeeDao.getCommitteeHistory(id))));
}

/** --- Committee Data Services --- */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package gov.nysenate.openleg.legislation.committee.dao;

import gov.nysenate.openleg.legislation.committee.*;
import gov.nysenate.openleg.common.dao.LimitOffset;
import gov.nysenate.openleg.common.dao.SortOrder;
import gov.nysenate.openleg.legislation.SessionYear;
import gov.nysenate.openleg.legislation.committee.*;
import gov.nysenate.openleg.processors.bill.LegDataFragment;

import java.util.List;
Expand Down Expand Up @@ -56,17 +56,6 @@ default Committee getCommittee(CommitteeId committeeId) throws CommitteeNotFound
*/
List<Committee> getCommitteeList(Chamber chamber, SessionYear sessionYear, LimitOffset limitOffset);

/**
* A convenient overload that gets the current committee list for the current session year
* @see #getCommitteeList(Chamber, SessionYear, LimitOffset)
* @param chamber
* @param limitOffset
* @return
*/
default List<Committee> getCommitteeList(Chamber chamber, LimitOffset limitOffset) {
return getCommitteeList(chamber, SessionYear.current(), limitOffset);
}

/**
* Gets the total number of committees for the given chamber for the given session year
* @param chamber
Expand Down
Loading

0 comments on commit 0e76c19

Please sign in to comment.