Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guava cache - How do I loadAll on any miss?

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;
    }
}
like image 728
Steven Hood Avatar asked Dec 27 '22 05:12

Steven Hood


1 Answers

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);
  }
}
like image 133
luke Avatar answered Jan 12 '23 00:01

luke