I have a use case where the method to load my cache's data is a bulk call, but I'm never going to use getAll to get data from the cache. Is there a way to have multiple concurrent gets all block on a single loadAll? I don't want individual gets on different keys to result in multiple calls to the data source.
cache.get(key1); // missing entry, starts refresh
cache.get(key2); // separate thread, already getting reloaded due to key1 miss
I think I'll have to implement my own synchronization after looking into the LocalCache, using something like a local cache in my data accessor that only allows a call through every so many units of time. When a call does go through, update the local copy with a single assignment statement.
Am I missing something from Guava's cache library?
Edit:
I'm considering something like the following. However, it could potentially continue returning stale data while loadAll finishes. I'd prefer everything blocks at load, and only the first request causes loadAll to proceed.
public class DataCacheLoader extends CacheLoader<String, Double>
{
private final Cache<String, Double> cache;
private ConcurrentMap<String, Double> currentData;
private final AtomicBoolean isloading;
public DataCacheLoader( final Cache<String, Double> cache )
{
this.cache = cache;
isLoading = new AtomicBoolean( false );
}
#Override
public Double load( final String key ) throws Exception
{
if ( isLoading.compareAndSet( false, true ) )
{
cache.putAll( loadAll( Lists.newArrayList( key ) ) ) );
}
return currentData.get( key );
}
#Override
public Map<String, Double> loadAll(Iterable<? extends String> keys) throws Exception
{
currentData = source.getAllData();
return currentData;
}
}
Here's a solution that should do the trick. The idea is that instead of caching each individual key you cache the whole map with a single fixed key. The one drawback is that you won't be able to expire individual parts of the underlying map (at least not easily), but that may not be a requirement.
class MyCache {
private static final Object KEY = new Object();
private final LoadingCache<Object, Map<String, Double>> delegate =
new CacheBuilder()
// configure cache
.build(new CacheLoader<Object, Map<String, Double>>() {
public Map<String, Double> load(Object key) {
return source.load();
}
};
double get(String key) {
return cache.get(KEY).get(key);
}
}
Related
I am using Guava to handle caching in my web application; I want to auto refresh the existing elements in my cache every 10 minutes.
this is my snippet code:
private Cache<String, Integer> cache = CacheBuilder.newBuilder.build();
//my main method
public Integer load(String key){
Integer value = cache.getIfPresent(key)
if(value == null){
value = getKeyFromServer(key);
//insert in my cache
cache.put(key, value);
}
return value;
}
I want to enhance the above code in order to refresh the elements gathered in my cache map as bellow:
//1. iterate over cache map
for(e in cache){
//2. get new element value from the server
value = getKeyFromServer(e.getKey());
//3. update the cache
cache.put(key, value);
}
You have to use Guava Cache a bit more. There is no need to call getIfPresent or put as the mechanism is handled automatically.
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<String, Integer>() {
#Override
public Integer load(Key key) throws Exception {
return getKeyFromServer(key);
}
});
Source: https://github.com/google/guava/wiki/CachesExplained
Please note that Guava Cache is deprecated in Spring 5: https://stackoverflow.com/a/44218055/8230378 (you labeled the question with spring-boot).
I am developing a metrics store (Map) , which basically collects metrics about some operations such as
mix
max
counter
timeElapsed[] etc
Here Key is the name of the method and value are metrics about it.
Spring can help me create singleton object of MetricStore, i am using ConcurrentHashMap to avoid race condition when multiple REST request comes in parallel.
My query
1- Do i need to make MetricStore variable store volatile? to improve the visibility among multiple requests. 2- I am using Map as the base class and ConcurrentHashMap as Implemetnation, does it affect as Map is not ThreadSafe.
-
#Component
class MetricStore{
public Map<String, Metric> store = new ConcurrentHashMap<>();
//OR public volatile Map<String, Metric> store = new ConcurrentHashMap<>();
}
#RestController
class MetricController{
#Autowired
private MetricStore metricStore;
#PostMapping(name="put")
public void putData(String key, Metric metricData) {
if(metricStore.store.containsKey(key)) {
// udpate data
}
else {
metricStore.store.put(key, metricData);
}
}
#PostMapping(name="remove")
public void removeData(String key) {
if(metricStore.store.containsKey(key)) {
metricStore.store.remove(key);
}
}
}
Do i need to make MetricStore variable store volatile?
No, because you are not changing the value of store (i.e. store can be marked as final and the code should still compile).
I am using Map as the base class and ConcurrentHashMap as Implemetnation, does it affect as Map is not ThreadSafe
Because you're using a ConcurrentHashMap as the implementation of Map, it is thread-safe. If you want the declared type to be more specific, Map can be changed to ConcurrentMap.
The bigger issue here is that you're using containsKey before calling put and remove when you should be using compute and computeIfPresent, which are atomic operations:
#PostMapping(name="put")
public void putData(String key, Metric metricData) {
metricStore.store.compute(key, (k, v) -> {
if (v == null) {
return metricData;
}
// update data
});
}
#PostMapping(name="remove")
public void removeData(String key) {
metricStore.store.computeIfPresent(key, (k, v) -> null);
}
In my application, I build a Guava Cache object by CacheBuilder.newBuilder() method, and now I need to dynamically adjust some initialization parameters for it.
As I don't find any rebuild method for a guava cache, I have to rebuild a new one.
My question is :
Anybody teach me how to release the old one ? I don't find any useful method either.I just call cache.invalidateAll() for the old one to invalidate all the keys. Is there any risk for OOM ?
As the cache maybe used in multi-threads, is it necessary to declare the cache as volatile ?
my codes is as belows:
private volatile LoadingCache<Long, String> cache = null;
private volatile LoadingCache<Long, String> oldCache = null;
public void rebuildCache(int cacheSize, int expireSeconds) {
logger.info("rebuildCache start: cacheSize: {}, expireSeconds: {}", cacheSize, expireSeconds);
oldCache = cache;
cache = CacheBuilder.newBuilder()
.maximumSize(cacheSize)
.recordStats()
.expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
.build(
new CacheLoader<Long, String>() {
#Override
public String load(Long id) {
// some codes here
}
}
);
if (oldCache != null) {
oldCache.invalidateAll();
}
logger.info("rebuildCache end");
}
public String getByCache(Long id) throws ExecutionException {
return cache.get(id);
}
You don't need to do anything special to release the old one; it'll get garbage collected like any other object. You probably should mark the cache as volatile, or even better, an AtomicReference so multiple threads don't replace the cache at the same time. That said, oldCache should be a variable inside the method, not the class.
I am working on measuing my application metrics using below class in which I increment and decrement metrics.
public class AppMetrics {
private final AtomicLongMap<String> metricCounter = AtomicLongMap.create();
private static class Holder {
private static final AppMetrics INSTANCE = new AppMetrics();
}
public static AppMetrics getInstance() {
return Holder.INSTANCE;
}
private AppMetrics() {}
public void increment(String name) {
metricCounter.getAndIncrement(name);
}
public AtomicLongMap<String> getMetricCounter() {
return metricCounter;
}
}
I am calling increment method of AppMetrics class from multithreaded code to increment the metrics by passing the metric name.
Problem Statement:
Now I want to have metricCounter for each clientId which is a String. That means we can also get same clientId multiple times and sometimes it will be a new clientId, so somehow then I need to extract the metricCounter map for that clientId and increment metrics on that particular map (which is what I am not sure how to do that).
What is the right way to do that keeping in mind it has to be thread safe and have to perform atomic operations. I was thinking to make a map like that instead:
private final Map<String, AtomicLongMap<String>> clientIdMetricCounterHolder = Maps.newConcurrentMap();
Is this the right way? If yes then how can I populate this map by passing clientId as it's key and it's value will be the counter map for each metric.
I am on Java 7.
If you use a map then you'll need to synchronize on the creation of new AtomicLongMap instances. I would recommend using a LoadingCache instead. You might not end up using any of the actual "caching" features but the "loading" feature is extremely helpful as it will synchronizing creation of AtomicLongMap instances for you. e.g.:
LoadingCache<String, AtomicLongMap<String>> clientIdMetricCounterCache =
CacheBuilder.newBuilder().build(new CacheLoader<String, AtomicLongMap<String>>() {
#Override
public AtomicLongMap<String> load(String key) throws Exception {
return AtomicLongMap.create();
}
});
Now you can safely start update metric counts for any client without worrying about whether the client is new or not. e.g.
clientIdMetricCounterCache.get(clientId).incrementAndGet(metricName);
A Map<String, Map<String, T>> is just a Map<Pair<String, String>, T> in disguise. Create a MultiKey class:
class MultiKey {
public String clientId;
public String name;
// be sure to add hashCode and equals
}
Then just use an AtomicLongMap<MultiKey>.
Edited:
Provided the set of metrics is well defined, it wouldn't be too hard to use this data structure to view metrics for one client:
Set<String> possibleMetrics = // all the possible values for "name"
Map<String, Long> getMetricsForClient(String client) {
return Maps.asMap(possibleMetrics, m -> metrics.get(new MultiKey(client, m));
}
The returned map will be a live unmodifiable view. It might be a bit more verbose if you're using an older Java version, but it's still possible.
Hey All so using google's CacheBuilder / CacheLoader:
CacheBuilder.newBuilder().maximumSize(MAX_SIZE).expireAfterAccess()
.build(new CacheLoader<String, Object>() {
#Override
public Object load(String key) throws Exception {
return Query(key);
}
});
Pretty straight forward. Thing is the key is used in the query for loading when not in cache and can actually be pretty big. I'd like to actually store the hash of the key in the cache and so when a lookup in the cache is actually done it uses the hash of the key but when the load is done the key is actually the full query.
Basically I would want my program to use the cache by sending the query to get function but just before key lookup or cache storage I wonder if there is a hook function I can override to hash the key for final cache storage and lookup?
Thanks in advance
Update:
So I figured out a solution using callable version of get. If I wrap the cache in my own container:
class WrappedCache {
private Cache<String, Object> mInnerCache;
public Object get(String key) {
String hashedKey = Hash(key); // get hashed key
return mInnerCache.get(hashedKey, new Callable<Object>() {
return Query(key);
}
}
}
This way the inner cache only deals with hash values but the callable gets the original query if it needs it to perform it's actions.
If I needed a more complex solution I could have made the WrappedCache implement the Cache<K,V> interface but my usage case is a little simpler and I can get away with above.
I don't believe the Guava Cache implementation supports such a thing, but you could create a decorator to do the translation on put and get prior to storing.
Would something like this work?
public class CacheDecorator<K1, K2, V> {
private final Function<K1, K2> keyConversion;
private final LoadingCache<K2, V> cache;
public CacheDecorator(Function<K1, K2> keyConversion, LoadingCache<K2, V> cache) {
this.keyConversion = keyConversion;
this.cache = cache;
}
public V get(K1 key) throws ExecutionException {
return cache.get(keyConversion.apply(key));
}
public void put(K1 key, V value) {
cache.put(keyConversion.apply(key), value);
}
}