/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.blur.server.cache;
import static org.apache.blur.metrics.MetricsConstants.COUNT;
import static org.apache.blur.metrics.MetricsConstants.EVICTION;
import static org.apache.blur.metrics.MetricsConstants.HIT;
import static org.apache.blur.metrics.MetricsConstants.MISS;
import static org.apache.blur.metrics.MetricsConstants.ORG_APACHE_BLUR;
import static org.apache.blur.metrics.MetricsConstants.SIZE;
import static org.apache.blur.metrics.MetricsConstants.THRIFT_CACHE;
import static org.apache.blur.metrics.MetricsConstants.THRIFT_CACHE_ATTRIBUTE_MAP;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.blur.log.Log;
import org.apache.blur.log.LogFactory;
import org.apache.blur.thirdparty.thrift_0_9_0.TBase;
import org.apache.blur.thrift.generated.BlurException;
import org.apache.blur.trace.Trace;
import org.apache.blur.trace.Tracer;
import org.apache.blur.user.User;
import org.apache.blur.user.UserContext;
import com.google.common.collect.MapMaker;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.EntryWeigher;
import com.googlecode.concurrentlinkedhashmap.EvictionListener;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Gauge;
import com.yammer.metrics.core.Meter;
import com.yammer.metrics.core.MetricName;
public class ThriftCache {
private static final String KEY = "key";
private static final String CLASS = "class";
private static final String T_KEY = "tKey";
private static final String SHARDS = "shards";
private static final String TABLE = "table";
private static final String THRIFT_CACHE_PUT = "ThriftCache - put";
private static final String THRIFT_CACHE_GET = "ThriftCache - get";
private static final String THRIFT_CACHE_GET_KEY = "ThriftCache - getKey";
private static final Log LOG = LogFactory.getLog(ThriftCache.class);
private final ConcurrentLinkedHashMap<ThriftCacheKey<?>, ThriftCacheValue<?>> _cacheMap;
private final ConcurrentMap<Map<String, String>, Map<String, String>> _attributeKeys;
private final ConcurrentMap<ShardsKey, ShardsKey> _shardsKeys;
private final ConcurrentMap<String, ClassObj<?>> _classObjMap = new ConcurrentHashMap<String, ClassObj<?>>();
private final ConcurrentMap<String, Long> _lastModTimestamps = new ConcurrentHashMap<String, Long>();
private final Meter _hits;
private final Meter _misses;
private final Meter _evictions;
private final AtomicLong _hitsAtomicLong;
private final AtomicLong _missesAtomicLong;
private final AtomicLong _evictionsAtomicLong;
public ThriftCache(long totalNumberOfBytes) {
_attributeKeys = new MapMaker().weakKeys().makeMap();
_shardsKeys = new MapMaker().weakKeys().makeMap();
_hits = Metrics.newMeter(new MetricName(ORG_APACHE_BLUR, THRIFT_CACHE, HIT), HIT, TimeUnit.SECONDS);
_misses = Metrics.newMeter(new MetricName(ORG_APACHE_BLUR, THRIFT_CACHE, MISS), MISS, TimeUnit.SECONDS);
_evictions = Metrics.newMeter(new MetricName(ORG_APACHE_BLUR, THRIFT_CACHE, EVICTION), EVICTION, TimeUnit.SECONDS);
_cacheMap = new ConcurrentLinkedHashMap.Builder<ThriftCacheKey<?>, ThriftCacheValue<?>>()
.weigher(new EntryWeigher<ThriftCacheKey<?>, ThriftCacheValue<?>>() {
@Override
public int weightOf(ThriftCacheKey<?> key, ThriftCacheValue<?> value) {
return key.size() + value.size();
}
}).listener(new EvictionListener<ThriftCacheKey<?>, ThriftCacheValue<?>>() {
@Override
public void onEviction(ThriftCacheKey<?> key, ThriftCacheValue<?> value) {
_evictions.mark();
_evictionsAtomicLong.incrementAndGet();
}
}).maximumWeightedCapacity(totalNumberOfBytes).build();
_hitsAtomicLong = new AtomicLong();
_missesAtomicLong = new AtomicLong();
_evictionsAtomicLong = new AtomicLong();
Metrics.newGauge(new MetricName(ORG_APACHE_BLUR, THRIFT_CACHE, SIZE), new Gauge<Long>() {
@Override
public Long value() {
return _cacheMap.weightedSize();
}
});
Metrics.newGauge(new MetricName(ORG_APACHE_BLUR, THRIFT_CACHE, COUNT), new Gauge<Long>() {
@Override
public Long value() {
return (long) _cacheMap.size();
}
});
Metrics.newGauge(new MetricName(ORG_APACHE_BLUR, THRIFT_CACHE_ATTRIBUTE_MAP, COUNT), new Gauge<Long>() {
@Override
public Long value() {
return (long) _attributeKeys.size();
}
});
}
public <K extends TBase<?, ?>, V extends TBase<?, ?>> V put(ThriftCacheKey<K> key, V t) throws BlurException {
Tracer trace = Trace.trace(THRIFT_CACHE_PUT, Trace.param(KEY, key));
try {
synchronized (_lastModTimestamps) {
Long lastModTimestamp = _lastModTimestamps.get(key.getTable());
if (lastModTimestamp != null && key.getTimestamp() < lastModTimestamp) {
// This means that the key was created before the index was modified.
// So do not cache the value because it's already out of date with the
// index.
return t;
}
}
LOG.debug("Inserting into cache [{0}] with key [{1}]", t, key);
_cacheMap.put(key, new ThriftCacheValue<V>(t));
return t;
} finally {
trace.done();
}
}
@SuppressWarnings("unchecked")
public <K extends TBase<?, ?>, V extends TBase<?, ?>> V get(ThriftCacheKey<K> key, Class<V> clazz)
throws BlurException {
Tracer trace = Trace.trace(THRIFT_CACHE_GET, Trace.param(KEY, key));
try {
ThriftCacheValue<V> value = (ThriftCacheValue<V>) _cacheMap.get(key);
if (value == null) {
LOG.debug("Cache Miss for [{0}]", key);
_misses.mark();
_missesAtomicLong.incrementAndGet();
return null;
}
LOG.debug("Cache Hit for [{0}]", key);
_hits.mark();
_hitsAtomicLong.incrementAndGet();
return value.getValue(clazz);
} finally {
trace.done();
}
}
public <K extends TBase<?, ?>> ThriftCacheKey<K> getKey(String table, int[] shards, K tkey, Class<K> clazz)
throws BlurException {
Tracer trace = Trace.trace(THRIFT_CACHE_GET_KEY, Trace.param(TABLE, table), Trace.param(SHARDS, shards),
Trace.param(T_KEY, tkey), Trace.param(CLASS, clazz));
try {
User user = UserContext.getUser();
Map<String, String> userAttributes = getUserAttributes(user);
ClassObj<K> classObj = getClassObj(clazz);
ShardsKey shardsKey = getShardsKey(shards);
return new ThriftCacheKey<K>(userAttributes, table, shardsKey, tkey, classObj);
} finally {
trace.done();
}
}
private ShardsKey getShardsKey(int[] shards) {
ShardsKey shardsKey = new ShardsKey(shards);
ShardsKey insternalShardsKey = _shardsKeys.get(shardsKey);
if (insternalShardsKey != null) {
return insternalShardsKey;
}
insternalShardsKey = _shardsKeys.putIfAbsent(shardsKey, shardsKey);
if (insternalShardsKey == null) {
return shardsKey;
} else {
return insternalShardsKey;
}
}
@SuppressWarnings("unchecked")
private <T> ClassObj<T> getClassObj(Class<T> clazz) {
String name = clazz.getName();
ClassObj<T> classObj = (ClassObj<T>) _classObjMap.get(name);
if (classObj != null) {
return classObj;
}
classObj = new ClassObj<T>(clazz);
ClassObj<T> currentValue = (ClassObj<T>) _classObjMap.putIfAbsent(name, classObj);
if (currentValue == null) {
return classObj;
}
return currentValue;
}
private Map<String, String> getUserAttributes(User user) {
if (user == null) {
return null;
}
Map<String, String> attributes = user.getAttributes();
if (attributes == null) {
return null;
}
if (!(attributes instanceof TreeMap)) {
attributes = new TreeMap<String, String>(attributes);
}
Map<String, String> internalInstance = _attributeKeys.get(attributes);
if (internalInstance == null) {
internalInstance = _attributeKeys.putIfAbsent(attributes, attributes);
if (internalInstance == null) {
return attributes;
}
}
return internalInstance;
}
public void clearTable(String table) {
synchronized (_lastModTimestamps) {
_lastModTimestamps.put(table, System.nanoTime());
}
LOG.debug("Clearing cache for table [{0}]", table);
Set<Entry<ThriftCacheKey<?>, ThriftCacheValue<?>>> entrySet = _cacheMap.entrySet();
Iterator<Entry<ThriftCacheKey<?>, ThriftCacheValue<?>>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Entry<ThriftCacheKey<?>, ThriftCacheValue<?>> entry = iterator.next();
if (entry.getKey().getTable().equals(table)) {
iterator.remove();
}
}
}
public void clear() {
LOG.debug("Clearing all cache.");
_cacheMap.clear();
}
public long size() {
return _cacheMap.weightedSize();
}
public long getHits() {
return _hitsAtomicLong.get();
}
public long getMisses() {
return _missesAtomicLong.get();
}
public long getEvictions() {
return _evictionsAtomicLong.get();
}
}