Merge "Issue #1821 make GridInfoCache more elaborate. Change-Id: Ib64a68ab4e898eba5fe186ac5ac01407964e37d9" into omaha_13.3.1
Former-commit-id:6c4f279623
[formerly7992b43a7a
[formerly b0753a969f27d0f7903a42a535066e3ac5b354fd]] Former-commit-id:7992b43a7a
Former-commit-id:2a71b9de1b
This commit is contained in:
commit
b03b4b8f75
2 changed files with 217 additions and 93 deletions
|
@ -31,4 +31,5 @@ Require-Bundle: com.raytheon.uf.common.parameter;bundle-version="1.0.0",
|
||||||
org.springframework;bundle-version="2.5.6",
|
org.springframework;bundle-version="2.5.6",
|
||||||
javax.measure;bundle-version="1.0.0",
|
javax.measure;bundle-version="1.0.0",
|
||||||
com.raytheon.uf.common.status;bundle-version="1.12.1174",
|
com.raytheon.uf.common.status;bundle-version="1.12.1174",
|
||||||
org.apache.commons.logging;bundle-version="1.1.1"
|
org.apache.commons.logging;bundle-version="1.1.1",
|
||||||
|
com.raytheon.uf.common.comm;bundle-version="1.12.1174"
|
||||||
|
|
|
@ -19,16 +19,19 @@
|
||||||
**/
|
**/
|
||||||
package com.raytheon.uf.edex.plugin.grid.dao;
|
package com.raytheon.uf.edex.plugin.grid.dao;
|
||||||
|
|
||||||
import java.lang.ref.SoftReference;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
|
import com.raytheon.uf.common.comm.CommunicationException;
|
||||||
import com.raytheon.uf.common.dataplugin.grid.GridInfoConstants;
|
import com.raytheon.uf.common.dataplugin.grid.GridInfoConstants;
|
||||||
import com.raytheon.uf.common.dataplugin.grid.GridInfoRecord;
|
import com.raytheon.uf.common.dataplugin.grid.GridInfoRecord;
|
||||||
|
import com.raytheon.uf.common.dataplugin.level.LevelFactory;
|
||||||
|
import com.raytheon.uf.common.gridcoverage.lookup.GridCoverageLookup;
|
||||||
|
import com.raytheon.uf.common.parameter.lookup.ParameterLookup;
|
||||||
import com.raytheon.uf.edex.database.DataAccessLayerException;
|
import com.raytheon.uf.edex.database.DataAccessLayerException;
|
||||||
import com.raytheon.uf.edex.database.cluster.ClusterLockUtils;
|
import com.raytheon.uf.edex.database.cluster.ClusterLockUtils;
|
||||||
import com.raytheon.uf.edex.database.cluster.ClusterLockUtils.LockState;
|
import com.raytheon.uf.edex.database.cluster.ClusterLockUtils.LockState;
|
||||||
|
@ -47,7 +50,7 @@ import com.raytheon.uf.edex.database.query.DatabaseQuery;
|
||||||
* Date Ticket# Engineer Description
|
* Date Ticket# Engineer Description
|
||||||
* ------------ ---------- ----------- --------------------------
|
* ------------ ---------- ----------- --------------------------
|
||||||
* May 21, 2012 bsteffen Initial creation
|
* May 21, 2012 bsteffen Initial creation
|
||||||
* Mar 27, 2013 1821 bsteffen Speed up GridInfoCache.
|
* Mar 27, 2013 1821 bsteffen Speed up GridInfoCache.
|
||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
|
@ -57,110 +60,41 @@ import com.raytheon.uf.edex.database.query.DatabaseQuery;
|
||||||
|
|
||||||
public class GridInfoCache {
|
public class GridInfoCache {
|
||||||
|
|
||||||
|
// 6 hours
|
||||||
|
private static final int ROTATION_INTERVAL = 1 * 1 * 60 * 1000;
|
||||||
|
|
||||||
private static GridInfoCache instance = new GridInfoCache();
|
private static GridInfoCache instance = new GridInfoCache();
|
||||||
|
|
||||||
public static GridInfoCache getInstance() {
|
public static GridInfoCache getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CoreDao dao;
|
private static final CoreDao dao = new CoreDao(
|
||||||
|
DaoConfig.forClass(GridInfoRecord.class));
|
||||||
|
|
||||||
// A weak hashmap of soft references is used as a SoftSet.
|
private Map<String, DatasetCache> cache = null;
|
||||||
private Map<GridInfoRecord, SoftReference<GridInfoRecord>> cache = null;
|
|
||||||
|
private long lastRotationTime;
|
||||||
|
|
||||||
private GridInfoCache() {
|
private GridInfoCache() {
|
||||||
cache = Collections
|
cache = Collections
|
||||||
.synchronizedMap(new WeakHashMap<GridInfoRecord, SoftReference<GridInfoRecord>>());
|
.synchronizedMap(new HashMap<String, DatasetCache>());
|
||||||
dao = new CoreDao(DaoConfig.forClass(GridInfoRecord.class));
|
lastRotationTime = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GridInfoRecord getGridInfo(GridInfoRecord record)
|
public GridInfoRecord getGridInfo(GridInfoRecord record)
|
||||||
throws DataAccessLayerException {
|
throws DataAccessLayerException {
|
||||||
GridInfoRecord result = checkLocalCache(record);
|
DatasetCache dCache = cache.get(record.getDatasetId());
|
||||||
if (result == null) {
|
if (dCache == null) {
|
||||||
result = query(record);
|
dCache = createDatasetCache(record.getDatasetId());
|
||||||
if (result == null) {
|
}
|
||||||
result = insert(record);
|
GridInfoRecord result = dCache.getGridInfo(record);
|
||||||
}
|
if (System.currentTimeMillis() > lastRotationTime + ROTATION_INTERVAL) {
|
||||||
|
rotateCache();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GridInfoRecord checkLocalCache(GridInfoRecord record) {
|
|
||||||
GridInfoRecord result = null;
|
|
||||||
SoftReference<GridInfoRecord> ref = cache.get(record);
|
|
||||||
if (ref != null) {
|
|
||||||
result = ref.get();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query the database for a record, if a record is found then it will be
|
|
||||||
* added to the cache and returned.
|
|
||||||
*
|
|
||||||
* @param record
|
|
||||||
* @return
|
|
||||||
* @throws DataAccessLayerException
|
|
||||||
*/
|
|
||||||
private GridInfoRecord query(GridInfoRecord record)
|
|
||||||
throws DataAccessLayerException {
|
|
||||||
// It is possible that this query will return multiple
|
|
||||||
// results, for example in the case of models with secondary ids. In
|
|
||||||
// general these cases should be rare and small. So we handle it by
|
|
||||||
// caching everything returned and then double checking the cache.
|
|
||||||
DatabaseQuery query = new DatabaseQuery(GridInfoRecord.class);
|
|
||||||
query.addQueryParam(GridInfoConstants.DATASET_ID, record.getDatasetId());
|
|
||||||
query.addQueryParam(GridInfoConstants.PARAMETER_ABBREVIATION, record
|
|
||||||
.getParameter().getAbbreviation());
|
|
||||||
query.addQueryParam(GridInfoConstants.LEVEL_ID, record.getLevel()
|
|
||||||
.getId());
|
|
||||||
query.addQueryParam(GridInfoConstants.LOCATION_ID, record.getLocation()
|
|
||||||
.getId());
|
|
||||||
List<?> dbList = dao.queryByCriteria(query);
|
|
||||||
|
|
||||||
if (dbList != null && !dbList.isEmpty()) {
|
|
||||||
for (Object pdo : dbList) {
|
|
||||||
GridInfoRecord gir = (GridInfoRecord) pdo;
|
|
||||||
// if we don't remove then when an entry exists already the key
|
|
||||||
// and value become references to different objects which is not
|
|
||||||
// what we want.
|
|
||||||
cache.remove(gir);
|
|
||||||
cache.put(gir, new SoftReference<GridInfoRecord>(gir));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return checkLocalCache(record);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert the record into the database if there is no current record that
|
|
||||||
* equals this one. This method uses a fairly broad cluster lock so only one
|
|
||||||
* thread at a time across all clustered edices can insert at a time. This
|
|
||||||
* method should not be used much on running systems since gridded models
|
|
||||||
* maintain fairly consistent info records over time.
|
|
||||||
*
|
|
||||||
* @param record
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private GridInfoRecord insert(GridInfoRecord record)
|
|
||||||
throws DataAccessLayerException {
|
|
||||||
ClusterTask ct = null;
|
|
||||||
do {
|
|
||||||
ct = ClusterLockUtils.lock("grid_info", "newEntry", 30000, true);
|
|
||||||
} while (!LockState.SUCCESSFUL.equals(ct.getLockState()));
|
|
||||||
try {
|
|
||||||
GridInfoRecord existing = query(record);
|
|
||||||
if (existing != null) {
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
dao.saveOrUpdate(record);
|
|
||||||
} finally {
|
|
||||||
ClusterLockUtils.unlock(ct, false);
|
|
||||||
}
|
|
||||||
cache.put(record, new SoftReference<GridInfoRecord>(record));
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the info records with the specified ids from the cache.
|
* Remove the info records with the specified ids from the cache.
|
||||||
*
|
*
|
||||||
|
@ -168,14 +102,203 @@ public class GridInfoCache {
|
||||||
*/
|
*/
|
||||||
public void purgeCache(Collection<Integer> infoKeys) {
|
public void purgeCache(Collection<Integer> infoKeys) {
|
||||||
synchronized (cache) {
|
synchronized (cache) {
|
||||||
Iterator<GridInfoRecord> it = cache.keySet().iterator();
|
Iterator<DatasetCache> it = cache.values().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
GridInfoRecord next = it.next();
|
DatasetCache next = it.next();
|
||||||
if (infoKeys.contains(next.getId())) {
|
next.purgeCache(infoKeys);
|
||||||
|
if (next.isEmpty()) {
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DatasetCache createDatasetCache(String datasetId)
|
||||||
|
throws DataAccessLayerException {
|
||||||
|
synchronized (cache) {
|
||||||
|
DatasetCache dCache = cache.get(datasetId);
|
||||||
|
if (dCache == null) {
|
||||||
|
dCache = new DatasetCache(datasetId);
|
||||||
|
cache.put(datasetId, dCache);
|
||||||
|
}
|
||||||
|
return dCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotateCache() {
|
||||||
|
synchronized (cache) {
|
||||||
|
if (System.currentTimeMillis() > lastRotationTime
|
||||||
|
+ ROTATION_INTERVAL) {
|
||||||
|
Iterator<DatasetCache> it = cache.values().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
DatasetCache next = it.next();
|
||||||
|
next.rotateCache();
|
||||||
|
if (next.isEmpty()) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastRotationTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* A second chance cache for all GridInfoRecords for a single datasetid.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static class DatasetCache {
|
||||||
|
|
||||||
|
private Map<GridInfoRecord, GridInfoRecord> primaryCache;
|
||||||
|
|
||||||
|
private Map<GridInfoRecord, GridInfoRecord> secondChanceCache;
|
||||||
|
|
||||||
|
public DatasetCache(String datasetid) throws DataAccessLayerException {
|
||||||
|
primaryCache = Collections
|
||||||
|
.synchronizedMap(new HashMap<GridInfoRecord, GridInfoRecord>());
|
||||||
|
secondChanceCache = Collections
|
||||||
|
.synchronizedMap(new HashMap<GridInfoRecord, GridInfoRecord>());
|
||||||
|
DatabaseQuery query = new DatabaseQuery(GridInfoRecord.class);
|
||||||
|
query.addQueryParam(GridInfoConstants.DATASET_ID, datasetid);
|
||||||
|
queryAndAdd(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridInfoRecord getGridInfo(GridInfoRecord record)
|
||||||
|
throws DataAccessLayerException {
|
||||||
|
GridInfoRecord result = checkLocalCache(record);
|
||||||
|
if (result == null) {
|
||||||
|
result = query(record);
|
||||||
|
if (result == null) {
|
||||||
|
result = insert(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void purgeCache(Collection<Integer> infoKeys) {
|
||||||
|
purgeCache(infoKeys, primaryCache);
|
||||||
|
purgeCache(infoKeys, secondChanceCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotateCache() {
|
||||||
|
secondChanceCache = primaryCache;
|
||||||
|
primaryCache = Collections
|
||||||
|
.synchronizedMap(new HashMap<GridInfoRecord, GridInfoRecord>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return primaryCache.isEmpty() && secondChanceCache.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private GridInfoRecord checkLocalCache(GridInfoRecord record) {
|
||||||
|
GridInfoRecord result = primaryCache.get(record);
|
||||||
|
if (result == null) {
|
||||||
|
result = secondChanceCache.get(record);
|
||||||
|
if (result != null) {
|
||||||
|
addToCache(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the database for a record, if a record is found then it will be
|
||||||
|
* added to the cache and returned.
|
||||||
|
*
|
||||||
|
* @param record
|
||||||
|
* @return
|
||||||
|
* @throws DataAccessLayerException
|
||||||
|
*/
|
||||||
|
private GridInfoRecord query(GridInfoRecord record)
|
||||||
|
throws DataAccessLayerException {
|
||||||
|
DatabaseQuery query = new DatabaseQuery(GridInfoRecord.class);
|
||||||
|
query.addQueryParam(GridInfoConstants.DATASET_ID,
|
||||||
|
record.getDatasetId());
|
||||||
|
query.addQueryParam(GridInfoConstants.PARAMETER_ABBREVIATION,
|
||||||
|
record.getParameter().getAbbreviation());
|
||||||
|
query.addQueryParam(GridInfoConstants.LEVEL_ID, record.getLevel()
|
||||||
|
.getId());
|
||||||
|
query.addQueryParam(GridInfoConstants.LOCATION_ID, record
|
||||||
|
.getLocation().getId());
|
||||||
|
queryAndAdd(query);
|
||||||
|
return checkLocalCache(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queryAndAdd(DatabaseQuery query)
|
||||||
|
throws DataAccessLayerException {
|
||||||
|
List<?> dbList = dao.queryByCriteria(query);
|
||||||
|
if (dbList != null && !dbList.isEmpty()) {
|
||||||
|
for (Object pdo : dbList) {
|
||||||
|
addToCache((GridInfoRecord) pdo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace several fields with cached versions to save memory and then
|
||||||
|
* add to the primaryCache.
|
||||||
|
*
|
||||||
|
* @param record
|
||||||
|
*/
|
||||||
|
private void addToCache(GridInfoRecord record) {
|
||||||
|
record.setLocation(GridCoverageLookup.getInstance().getCoverage(
|
||||||
|
record.getLocation().getId()));
|
||||||
|
record.setParameter(ParameterLookup.getInstance().getParameter(
|
||||||
|
record.getParameter().getAbbreviation()));
|
||||||
|
try {
|
||||||
|
record.setLevel(LevelFactory.getInstance().getLevel(
|
||||||
|
record.getLevel().getId()));
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
// This should never hit and if it does ignore it, the only side
|
||||||
|
// affect is thatthe level in the record will not be the same as
|
||||||
|
// the other records on the same level.
|
||||||
|
}
|
||||||
|
primaryCache.put(record, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert the record into the database if there is no current record
|
||||||
|
* that equals this one. This method uses a fairly broad cluster lock so
|
||||||
|
* only one thread at a time across all clustered edices can insert at a
|
||||||
|
* time. This method should not be used much on running systems since
|
||||||
|
* gridded models maintain fairly consistent info records over time.
|
||||||
|
*
|
||||||
|
* @param record
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private GridInfoRecord insert(GridInfoRecord record)
|
||||||
|
throws DataAccessLayerException {
|
||||||
|
ClusterTask ct = null;
|
||||||
|
do {
|
||||||
|
ct = ClusterLockUtils.lock("grid_info_create",
|
||||||
|
record.getDatasetId(), 30000, true);
|
||||||
|
} while (!LockState.SUCCESSFUL.equals(ct.getLockState()));
|
||||||
|
try {
|
||||||
|
GridInfoRecord existing = query(record);
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
dao.saveOrUpdate(record);
|
||||||
|
} finally {
|
||||||
|
ClusterLockUtils.unlock(ct, false);
|
||||||
|
}
|
||||||
|
addToCache(record);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void purgeCache(Collection<Integer> infoKeys,
|
||||||
|
Map<GridInfoRecord, GridInfoRecord> cache) {
|
||||||
|
synchronized (cache) {
|
||||||
|
Iterator<GridInfoRecord> it = cache.keySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
GridInfoRecord next = it.next();
|
||||||
|
if (infoKeys.contains(next.getId())) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue