/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.addthis.hydra.job.store; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.Weigher; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CachedSpawnDataStore implements SpawnDataStore { private static Pair<String, String> defaultKey(String path) { return ImmutablePair.of(path, null); } private static final Logger log = LoggerFactory.getLogger(CachedSpawnDataStore.class); private final SpawnDataStore dataStore; private final LoadingCache<Pair<String, String>, String> cache; public CachedSpawnDataStore(SpawnDataStore dataStore, long dataStoreCacheSize) { this.dataStore = dataStore; this.cache = CacheBuilder.newBuilder() .weigher(new Weigher<Pair<String, String>, String>() { @Override public int weigh(Pair<String, String> key, String value) { int leftWeight = key.getLeft() != null ? key.getLeft().length() : 0; int rightWeight = key.getRight() != null ? key.getRight().length() : 0; // Multiply strlen by 2 (full width characters in java return 2 * (value.length() + leftWeight + rightWeight); } }) .maximumWeight(dataStoreCacheSize) .build(new CacheLoader<Pair<String, String>, String>() { @Override public String load(Pair<String, String> key) throws Exception { String path = key.getLeft(); String childId = key.getRight(); if (childId == null) { return CachedSpawnDataStore.this.dataStore.get(path); } else { return CachedSpawnDataStore.this.dataStore.getChild(path, childId); } } }); } @Override public String getDescription() { return dataStore.getDescription(); } @Override public String get(String path) { try { return cache.get(defaultKey(path)); } catch (ExecutionException | CacheLoader.InvalidCacheLoadException e) { log.error("failed to execute get from cache", e); return null; } } @Override public Map<String, String> get(String[] paths) { List<String> notCached = new ArrayList<>(); Map<String, String> results = new TreeMap<>(); // Load every path from the cache that we can. For all the paths that do not exist in the cache, continue to // do the batched query logic which should be implemented in the underlying DataStore's `get(String[])` for (String path : paths) { String result = cache.getIfPresent(defaultKey(path)); if (result == null) { notCached.add(path); } else { results.put(path, result); } } String[] remainingPaths = new String[notCached.size()]; remainingPaths = notCached.toArray(remainingPaths); Map<String, String> resultsFromDB = dataStore.get(remainingPaths); for (Map.Entry<String, String> entry : resultsFromDB.entrySet()) { String path = entry.getKey(); cache.put(defaultKey(path), entry.getValue()); } results.putAll(resultsFromDB); return results; } @Override public void put(String path, String value) throws Exception { dataStore.put(path, value); cache.put(defaultKey(path), value); } @Override public void putAsChild(String parent, String childId, String value) throws Exception { dataStore.putAsChild(parent, childId, value); cache.put(ImmutablePair.of(parent, childId), value); } @Override public String getChild(String parent, String childId) throws Exception { return cache.get(ImmutablePair.of(parent, childId)); } @Override public void deleteChild(String parent, String childId) { dataStore.deleteChild(parent, childId); cache.invalidate(ImmutablePair.of(parent, childId)); } @Override public void delete(String path) { dataStore.delete(path); cache.invalidate(defaultKey(path)); } @Override public List<String> getChildrenNames(String path) { return dataStore.getChildrenNames(path); } @Override public Map<String, String> getAllChildren(String path) { return dataStore.getAllChildren(path); } @Override public void close() { dataStore.close(); } }