Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I store a Map in a Guava Cache

I had a Map<Range<Double>, String> that checks where a particular Double value (score) is mapped to a String (level). The end users want to be able to dynamically change this mapping, in the long term we would like for there to be a web based GUI where they control this but for the short term they're happy for a file to be in S3 and to editing that whenever a change is needed. I don't want to hit S3 for each request and want to cache this as it doesn't change too frequently(Once a week or so). I don't want to have to make a code change and bounce my service either.

Here is what I have come up with -

public class Mapper() {
    private LoadingCache<Score, String> scoreToLevelCache;

public Mapper() {
    scoreToLevelCache = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(new CacheLoader<Score, String>() {
                public String load(Score score) {
                    Map<Range<Double>, String> scoreToLevelMap = readMappingFromS3(); //readMappingFromS3 omitted for brevity
                    for(Range<Double> key : scoreToLevelMap.keySet()) {
                        if(key.contains(score.getCount())) { return scoreToLevelMap.get(key); }
                    }
                    throw new IllegalArgumentException("The score couldn't be mapped to a level. Either the score passed in was incorrect or the mapping is incorrect");
                }
            }); 
}

public String getContentLevelForScore(Score Score) {
    try {
        return scoreToLevelCache.get(Score);
    } catch (ExecutionException e) { throw new InternalServerException(e); }
  } 
}

The obvious problem with this approach is in the load method when I do Map<Range<Double>, String> scoreToLevelMap = readMappingFromS3(); For each key I'm loading the entire map over and over. This isn't a performance issue but it could become one when the size increases, in any case this is not an efficient approach.

I think that keeping this entire map in the cache would be better, but I'm not sure how to do that here. Can anyone help with this or suggest a more elegant way of achieving this.

like image 438
nikhil Avatar asked Feb 26 '15 23:02

nikhil


People also ask

Where is Guava cache stored?

Guava Caches store values in RAM.

Is Guava cache thread safe?

Cache entries are manually added using get(Object, Callable) or put(Object, Object) , and are stored in the cache until either evicted or manually invalidated. Implementations of this interface are expected to be thread-safe, and can be safely accessed by multiple concurrent threads.

What is Java cache map?

A CacheMap is a Map that supports caching. This interface will be eventually replaced by the javax. cache.


2 Answers

Guava has a different mechanism for "a cache that only ever contains one value"; it's called Suppliers.memoizeWithExpiration.

private Supplier<Map<Range<Double>, String> cachedMap = 
    Suppliers.memoizeWithExpiration(
        new Supplier<Map<Range<Double>, String>() {
            public Map<Range<Double>, String> get() {
                return readMappingFromS3();
            }
        }, 10, TimeUnit.MINUTES);

public String getContentLevelForScore(Score score) {
    Map<Range<Double>, String> scoreMap = cachedMap.get();
    // etc.
}
like image 200
David Avatar answered Sep 18 '22 12:09

David


Do not mix caching and business logic. Unless your score mapping is huge AND you can load individual pieces, e.g. using readMappingFromS3(Double d) - simply cache the whole map.

    public static final String MAGIC_WORD = "oh please please give me my data!!!";
    private final LoadingCache<String, Map<Range<Double>, String>> scoreToLevelCache;


    public Mapper() {
        scoreToLevelCache = CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, Map<Range<Double>, String>>() {
                    public Map<Range<Double>, String> load(String score) {
                        return readMappingFromS3(); //readMappingFromS3 omitted for brevity
                    }
                });
    }

    public Map<Range<Double>, String> getScoreMap() {
        try {
            return scoreToLevelCache.get(MAGIC_WORD);
        } catch (ExecutionException e) {
            throw new InternalServerException(e);
        }
    }

Fetch level name like this

    public String findLevel(final Double score) {
        final Map<Range<Double>, String> scoreMap = getScoreMap();
        for (final Range<Double> key : scoreMap.keySet()) {
            if (key.contains(score)) {
                return scoreMap.get(key);
            }
        }
        ...
    }
like image 21
harshtuna Avatar answered Sep 16 '22 12:09

harshtuna