I have something like this:
private final Cache<Long, BlockingDeque<Peer>> peers = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
public class Peer {
public void hanleRequest(String request) { ... }
//....
}
Cache provides only two policies: expiredAfterWrite and expireAfterAccess. Either the first nor the second is suitable for me.
I want BlockingDeque<Peer> entity expires in 10 minutes after last invocation of Peer#handleRequest() method on one of Peer objects that belongs to that BlockingDeque. Means Peer#handleRequest() resets the expiration counter.
I want Any of other methods of Peer object doesn't reset counter.
I want peers.get(key) also doesn't reset counter.
Example
peers.getIfPresent(key); // doesn't reset counter
peers.getIfPresent(key).add(new Peer()); // doesn't reset counter
peers.getIfPresent(key).remove(peer); //doesn't reset counter
peers.getIfPresent(key).handleRequest(request); // RESET counter!
Questions
Is that possible with help of Guava Cache, ExpiringMap, MapMaker or any other Guava map?
If asnwer to the first question is NO. Can I just customize one of the Guava elements to have no need to implement all from scratch?
If answer to the second question is NO. What is the better way to implement that by my own? At the moment I suppose it'll be ConcurrentHashMap with daemon thread in addition. That thread will be iterate throught the whole map each 5-15 seconds and check if any entity is expired
Updated: Is that a good solution? As I suppose, handleRequest is a operation which will be performed on each user request, so it performance stays on the first place. Approximate BlockingDeque objects in peers cache is near 10, approximate number of Peer object in one deque is 2.
private final Cache<Long, BlockingDeque<Peer>> peers = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) //CHANGE TO WRITE POLICY
.build();
public class Peer {
public void hanleRequest(String request) {
BlockingDeque<Peer> deque = peers.getIfPresent(key);
peers.invalidate(key);
peers.put(key, deque);
//...
}
//....
}
First a remark: The kind of question you ask smells like a XY problem, see:
https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem
So maybe some background what you really want to achieve would be good.
Taking the question literally, I would do the following:
Use a second cache without expiration for the "don't reset counter" accesses. Add a removal listener to the peers cache, to remove the value from the second cache. Maybe just a HashMap is fine, too. The resource usage is actually controlled by the peers cache.
#cruftex's suggestion of using a second cache is fine.
Regarding your updated question, you don't need to invalidate before "updating" the value, just update it:
public class Peer {
public void handleRequest(String request) {
BlockingDeque<Peer> deque = peers.getIfPresent(key);
if (deque != null) {
peers.put(key, deque);
}
//...
}
//....
}
Related
Is there a way to populate a Map once from the DB (through Mongo repository) data and reuse it when required from multiple classes instead of hitting the Database through the repository.
As per your comment, what you are looking for is a Caching mechanism. Caches are components which allow data to live in memory, as opposed to files, databases or other mediums so as to allow for the fast retrieval of information (against a higher memory footprint).
There are probably various tutorials online, but usually caches all have the following behaviour:
1. They are key-value pair structures.
2. Each entity living in the cache also has a Time To Live, that is, how long will it considered to be valid.
You can implement this in the repository layer, so the cache mechanism will be transparent to the rest of your application (but you might want to consider exposing functionality that allows to clear/invalidate part or all the cache).
So basically, when a query comes to your repository layer, check in the cache. If it exists in there, check the time to live. If it is still valid, return that.
If the key does not exist or the TTL has expired, you add/overwrite the data in the cache. Keep in mind that when updating the data model yourself, you also invalidate the cache accordingly so that new/fresh data will be pulled from the DB on the next call.
You can declare the map field as public static and this would allow application wide access to hit via ClassLoadingData.mapField
I think a better solution, if I understood the problem would be a memoized function, that is a function storing the value of its call. Here is a sketch of how this could be done (note this does not handle possible synchronization problem in a multi threaded environment):
class ClassLoadingData {
private static Map<KeyType,ValueType> memoizedValues = new HashMap<>();
public Map<KeyType,ValueType> getMyData() {
if (memoizedData.isEmpty()) { // you can use more complex if to handle data refresh
populateData(memoizedData);
} else {
return memoizedData;
}
}
private void populateData() {
// do your query, and assign result to memoizedData
}
}
Premise: I suggest you to use an object-relational mapping tool like Hibernate on your java project to map the object-oriented
domain model to a relational database and let the tool handle the
cache mechanism implicitally. Hibernate specifically implements a multi-level
caching scheme ( take a look at the following link to get more
informations:
https://www.tutorialspoint.com/hibernate/hibernate_caching.htm )
Regardless my suggestion on premise you can also manually create a singleton class that will be used from every class in the project that goes to interact with the DB:
public class MongoDBConnector {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBConnector.class);
private static MongoDBConnector instance;
//Cache period in seconds
public static int DB_ELEMENTS_CACHE_PERIOD = 30;
//Latest cache update time
private DateTime latestUpdateTime;
//The cache data layer from DB
private Map<KType,VType> elements;
private MongoDBConnector() {
}
public static synchronized MongoDBConnector getInstance() {
if (instance == null) {
instance = new MongoDBConnector();
}
return instance;
}
}
Here you can define then a load method that goes to update the map with values stored on the DB and also a write method that instead goes to write values on the DB with the following characteristics:
1- These methods should be synchronized in order to avoid issues if multiple calls are performed.
2- The load method should apply a cache period logic ( maybe with period configurable ) to avoid to load for each method call the data from the DB.
Example: Suppose your cache period is 30s. This means that if 10 read are performed from different points of the code within 30s you
will load data from DB only on the first call while others will read
from cached map improving the performance.
Note: The greater is the cache period the more is the performance of your code but if the DB is managed you'll create inconsistency
with cache if an insertion is performed externally ( from another tool
or manually ). So choose the best value for you.
public synchronized Map<KType, VType> getElements() throws ConnectorException {
final DateTime currentTime = new DateTime();
if (latestUpdateTime == null || (Seconds.secondsBetween(latestUpdateTime, currentTime).getSeconds() > DB_ELEMENTS_CACHE_PERIOD)) {
LOGGER.debug("Cache is expired. Reading values from DB");
//Read from DB and update cache
//....
sampleTime = currentTime;
}
return elements;
}
3- The store method should automatically update the cache if insert is performed correctly regardless the cache period is expired:
public synchronized void storeElement(final VType object) throws ConnectorException {
//Insert object on DB ( throws a ConnectorException if insert fails )
//...
//Update cache regardless the cache period
loadElementsIgnoreCachePeriod();
}
Then you can get elements from every point in your code as follow:
Map<KType,VType> liveElements = MongoDBConnector.getElements();
So my application would call below expensive HTTP service multiple times (simultaneously by multiple threads with same as well as different Ids for every client request to my application).
Mono<Foo> response = myService.fetch(id);
I would like to cache the response (in-memory) for few hours, and then only on next client request make only one call to refresh the cache.
Approach 1:
Mono<Foo> cachedResponse = Mono.empty();
public Mono<Foo> get(String id){
return cachedResponse.switchIfEmpty(Mono.defer(()->
{
cachedResponse = myService.fetch(id).cache(Duration.ofHours(4));
return cachedResponse;
}));
}
is following approach OK? Specifically since multiple threads could call get method with same id. Also, when the cache is invalidated after 4 hours, would it make cachedResponse Mono empty for switchIfEmpty to work correctly?
Approach 2:
I could use some caching solution to store cache for few hours. e.g.
Foo getFromCacheSolution(String id);
and then,
public Mono<Foo> get(String id){
Foo cachedFoo = getFromCacheSolution(id);
if(cachedFoo != null){
return Mono.just(cachedFoo);
}
else{
return myService.fetch(id).doOnNext(value->storeToCacheSolution(id, value)); //line 7
}
}
The problem with this solution is that line 7 would be called multiple times resulting in multiple calls to expensive fetch service (for example if 3 threads enter into get method with id 123 and cachedFoo is null). Making method synchronized may not help as line 7 would complete instantaneously.
One work-around would be to store Mono in the cache solution instead of Foo (not sure if that's a good idea or not):
Mono<Foo> getFromCacheSolution(String id); //returns cached or empty Mono
and then,
public Mono<Foo> get(String id){
return getFromCacheSolution(id).switchIfEmpty(Mono.defer(()->
{
cachedResponse = myService.fetch(id).doOnNext(value->storeToCacheSolution(id, value));
return cachedResponse;
}));
}
Any recommendations or better alternatives?
Your question consists of two parts: about caching and about exclusive locking for calls with same parameters.
Caching.
Your second approach is good for in-memory cache. Alternatively you could use CacheMono from the reactor-extra
Mono<Foo> myFoo =
CacheMono.lookup(key -> Mono.justOrEmpty(myCache.getIfPresent(key))
.map(Signal::next), id)
.onCacheMissResume(() -> myService.fetch(id))
.andWriteWith((key, signal) -> Mono.fromRunnable(() ->
Optional.ofNullable(signal.get())
.ifPresent(value -> myCache.put(key, value))));
Exclusive locking for calls with same parameters.
Usually we should avoid any locking in the reactive world. But if you really need it, your lock should be nonblocking. I don't know any library, but you could find some ideas and links with examples in this question thread
I am using EhCache core 3.0. It internally uses BaseExpiry and Eh107Expiry class to check whether cache is expired or not. These classes implement Expiry interface. My query is, can we extend methods which are used to check whether cache is expired or not. I don't want to expire contents of the cache even if time is elapsed if my method is using some data from that cache.
Have a look at the dedicated section on Expiry in the documentation. It will help you understand what you can do and how to do it.
If that does not help you, please expand your question as suggested in comments.
If you add time-to-idle in xml or override getExpiryForAccess from Expiry interface,then your entries will not delete when you are accessing them.Below is the code to build Eh cache with custom Expire.This blog will help you for other properties with explanation.
CacheConfigurationBuilder<Integer,String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder();
cacheConfigurationBuilder.withExpiry(new Expiry() {
#Override
public Duration getExpiryForCreation(Object key, Object value) {
return new Duration(120, TimeUnit.SECONDS);
}
#Override
public Duration getExpiryForAccess(Object key, Object value) {
return new Duration(120, TimeUnit.SECONDS);
}
#Override
public Duration getExpiryForUpdate(Object key, Object oldValue, Object newValue) {
return null;
}
})
.usingEvictionPrioritizer(Eviction.Prioritizer.LFU)
.withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder().heap(200, EntryUnit.ENTRIES))
// adding defaultSerializer config service to configuration
.add(new DefaultSerializerConfiguration(CompactJavaSerializer.class, SerializerConfiguration.Type.KEY))
.buildConfig(Integer.class, String.class);
I guess you can use an ehcache decorator and reimplement isExpiry to add your own conditions. Please refer to https://www.ehcache.org/documentation/2.8/apis/cache-decorators.html.
I had the problem, that every time i retrieved a collection from the gwt request factory, there was the "findEntity()"-method called for every entity in that collection. And this "findEntity()"-method calls the SQL-Database.
I found out that this happens because request factory checks the "liveness" of every entity in the "ServiceLayerDecorator.isLive()"-method (also described here: requestfactory and findEntity method in GWT)
So i provided my own RequestFactoryServlet:
public class MyCustomRequestFactoryServlet extends RequestFactoryServlet {
public MyCustomRequestFactoryServlet() {
super(new DefaultExceptionHandler(), new MyCustomServiceLayerDecorator());
}
}
And my own ServiceLayerDecorator:
public class MyCustomServiceLayerDecorator extends ServiceLayerDecorator {
/**
* This check does normally a lookup against the db for every element in a collection
* -> Therefore overridden
*/
#Override
public boolean isLive(Object domainObject) {
return true;
}
}
This works so far and I don't get this massive amount of queries against the database.
Now I am wondering if I will get some other issues with that? Or is there a better way to solve this?
RequestFactory expects a session-per-request pattern with the session guaranteeing a single instance per entity (i.e. using a cache).
The proper fix is to have isLive hit that cache, not the database. If you use JPA or JDO, they should do that for you for free. What matters is what "the request" thinks about it (if you issued a delete request, isLive should return false), not really what's exactly stored in the DB, taking into account what other users could have done concurrently.
That being said, isLive is only used for driving EntityProxyChange events on the client side, so if you don't use them, it shouldn't cause any problem unconditionally returning true like you do.
Two things I really like about Guava 11's CacheLoader (thanks, Google!) are loadAll(), which allows me to load multiple keys at once, and reload(), which allows me to reload a key asynchronously when it's "stale" but an old value exists. I'm curious as to how they play together, since reload() operates on but a single key.
Concretely, extending the example from CachesExplained:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return getGraphFromDatabase(key);
}
public Map<Key, Graph> loadAll(Iterable<? extends K> keys) {
return getAllGraphsFromDatabase(keys);
}
public ListenableFuture<Graph> reload(final Key key, Graph prevGraph) {
if (neverNeedsRefresh(key)) {
return Futures.immediateFuture(prevGraph);
} else {
// asynchronous!
return ListenableFutureTask.create(new Callable<Graph>() {
public Graph call() {
return getGraphFromDatabase(key);
}
});
}
}
});
...where "getAllGraphsFromDatabase()" does an aggregate database query rather than length(keys) individual queries.
How do these two components of a LoadingCache play together? If some keys in my request to getAll() aren't present in the cache, they are loaded as a group with loadAll(), but if some need refreshing, do they get reloaded individually with load()? If so, are there plans to support a reloadAll()?
Here's how refreshing works.
Refreshing on a cache entry can be triggered in two ways:
Explicitly, with cache.refresh(key).
Implicitly, if the cache is configured with refreshAfterWrite and the entry is queried after the specified amount of time after it was written.
If an entry that is eligible for reload is queried, then the old value is returned, and a (possibly asynchronous) refresh is triggered. The cache will continue to return the old value for the key while the refresh is in progress. (So if some keys in a getAll request are eligible for refresh, their old values will be returned, but the values for those keys will be (possibly asynchronously) reloaded.)
The default implementation of CacheLoader.reload(key, oldValue) just returns Futures.immediateFuture(load(key)), which (synchronously) recomputes the value. More sophisticated, asynchronous implementations are recommended if you expect to be doing cache refreshes.
I don't think we're inclined to provide reloadAll at the moment. I suspect it's possible, but things are complicated enough as it is, and I think we're inclined to wait until we see specific demand for such a thing.