Package com.thinkaurelius.titan.graphdb.database.cache

Source Code of com.thinkaurelius.titan.graphdb.database.cache.ExpirationStoreCache$CleanupThread

package com.thinkaurelius.titan.graphdb.database.cache;

import com.codahale.metrics.Counter;
import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.Weigher;
import com.thinkaurelius.titan.core.TitanException;
import com.thinkaurelius.titan.diskstorage.BackendTransaction;
import com.thinkaurelius.titan.diskstorage.StaticBuffer;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.Entry;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.KeySliceQuery;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.SliceQuery;
import com.thinkaurelius.titan.graphdb.configuration.GraphDatabaseConfiguration;
import com.thinkaurelius.titan.util.stats.MetricManager;

import static com.thinkaurelius.titan.util.datastructures.ByteSize.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
* @author Matthias Broecheler (me@matthiasb.com)
*/
public class ExpirationStoreCache implements StoreCache {

    private static final Logger log =
            LoggerFactory.getLogger(ExpirationStoreCache.class);

    //Weight estimation
    private static final int STATICARRAYBUFFER_SIZE = STATICARRAYBUFFER_RAW_SIZE + 10; // 10 = last number is average length
    private static final int KEY_QUERY_SIZE = OBJECT_HEADER + 4 + 1 + 3 * (OBJECT_REFERENCE + STATICARRAYBUFFER_SIZE); // object_size + int + boolean + 3 static buffers

    private static final int INVALIDATE_KEY_FRACTION_PENALTY = 1000;
    private static final int PENALTY_THRESHOLD = 5;

    private static final String METRICS_PREFIX = GraphDatabaseConfiguration.METRICS_SYSTEM_PREFIX_DEFAULT
            + "." + ExpirationStoreCache.class.getSimpleName();
    private static final Counter GLOBAL_CACHE_MISSES = MetricManager.INSTANCE.getCounter(METRICS_PREFIX, "misses");
    private static final Counter GLOBAL_CACHE_RETRIEVALS = MetricManager.INSTANCE.getCounter(METRICS_PREFIX, "retrievals");

    private volatile CountDownLatch penaltyCountdown;

    private final Cache<KeySliceQuery,List<Entry>> cache;
    private final ConcurrentHashMap<StaticBuffer,Long> expiredKeys;

    private final long cacheTimeMS;
    private final long expirationGracePeriodMS;
    private final CleanupThread cleanupThread;

    public ExpirationStoreCache(final long cacheTimeMS, final long expirationGracePeriodMS, final long maximumByteSize) {
        Preconditions.checkArgument(cacheTimeMS>0,"Cache expiration must be positive: %s");
        Preconditions.checkArgument(System.currentTimeMillis()+1000l*3600*24*365*100+cacheTimeMS>0,"Cache expiration time too large, overflow may occur: %s",cacheTimeMS);
        this.cacheTimeMS = cacheTimeMS;
        int concurrencyLevel = Runtime.getRuntime().availableProcessors();
        Preconditions.checkArgument(expirationGracePeriodMS>=0,"Invalid expiration grace peiod: %s",expirationGracePeriodMS);
        this.expirationGracePeriodMS = expirationGracePeriodMS;

        CacheBuilder<KeySliceQuery,List<Entry>> cachebuilder = CacheBuilder.newBuilder()
                .maximumWeight(maximumByteSize)
                .concurrencyLevel(concurrencyLevel)
                .initialCapacity(1000)
                .expireAfterWrite(cacheTimeMS, TimeUnit.MILLISECONDS)
                .weigher(new Weigher<KeySliceQuery, List<Entry>>() {
                    @Override
                    public int weigh(KeySliceQuery keySliceQuery, List<Entry> entries) {
                        int size = GUAVA_CACHE_ENTRY_SIZE + KEY_QUERY_SIZE + ARRAYLIST_SIZE;
                        for (Entry e : entries) size+=e.getByteSize();
                        return size;
                    }
                });

        cache = cachebuilder.build();
        expiredKeys = new ConcurrentHashMap<StaticBuffer, Long>(50,0.75f,concurrencyLevel);
        penaltyCountdown = new CountDownLatch(PENALTY_THRESHOLD);

        cleanupThread = new CleanupThread();
        cleanupThread.start();
    }

    public static void resetGlobablCounts() {
        // Approximate (inexact)
        GLOBAL_CACHE_MISSES.dec(GLOBAL_CACHE_MISSES.getCount());
        GLOBAL_CACHE_RETRIEVALS.dec(GLOBAL_CACHE_RETRIEVALS.getCount());
    }

    public static long getGlobalCacheRetrievals() {
        return GLOBAL_CACHE_RETRIEVALS.getCount();
    }

    public static long getGlobalCacheMisses() {
        return GLOBAL_CACHE_MISSES.getCount();
    }

    public static long getGlobalCacheHits() {
        return getGlobalCacheRetrievals()-getGlobalCacheMisses();
    }

    private boolean isExpired(final KeySliceQuery query) {
        Long until = expiredKeys.get(query.getKey());
        if (until==null) return false;
        if (isBeyondExpirationTime(until)) {
            expiredKeys.remove(query.getKey(),until);
            return false;
        }
        //We suffer
        penaltyCountdown.countDown();
        return true;
    }

    @Override
    public List<Entry> query(final KeySliceQuery query, final BackendTransaction tx) {
        if (isExpired(query)) return tx.edgeStoreQuery(query);

        try {
            GLOBAL_CACHE_RETRIEVALS.inc();
            return cache.get(query,new Callable<List<Entry>>() {
                @Override
                public List<Entry> call() throws Exception {
                    GLOBAL_CACHE_MISSES.inc();
                    return tx.edgeStoreQuery(query);
                }
            });
        } catch (Exception e) {
            if (e instanceof TitanException) throw (TitanException)e;
            else if (e.getCause() instanceof TitanException) throw (TitanException)e.getCause();
            else throw new TitanException(e);
        }
    }

    @Override
    public List<List<Entry>> multiQuery(List<StaticBuffer> keys, SliceQuery query, BackendTransaction tx) {
        List<Entry>[] results = (List<Entry>[])new List[keys.size()];
        List<StaticBuffer> remainingKeys = new ArrayList<StaticBuffer>(keys.size());
        KeySliceQuery[] ksqs = new KeySliceQuery[keys.size()];
        for (int i=0;i<keys.size();i++) {
            StaticBuffer key = keys.get(i);
            ksqs[i] = new KeySliceQuery(key,query);
            List<Entry> result = null;
            if (!isExpired(ksqs[i])) result = cache.getIfPresent(ksqs[i]);
            else ksqs[i]=null;
            if (result!=null) results[i]=result;
            else remainingKeys.add(key);
        }
        List<List<Entry>> subresults = tx.edgeStoreMultiQuery(remainingKeys,query);
        int pos = 0;
        for (int i=0;i<results.length;i++) {
            if (results[i]!=null) continue;
            assert pos<subresults.size();
            List<Entry> subresult = subresults.get(pos);
            assert subresult!=null;
            results[i]=subresult;
            if (ksqs[i]!=null) cache.put(ksqs[i],subresult);
            pos++;
        }
        assert pos==subresults.size();
        return Arrays.asList(results);
    }

    private final long getExpirationTime() {
        return System.currentTimeMillis()+cacheTimeMS;
    }

    private final boolean isBeyondExpirationTime(long until) {
        return until<System.currentTimeMillis();
    }

    private final long getAge(long until) {
        long age = System.currentTimeMillis() - (until-cacheTimeMS);
        assert age>=0;
        return age;
    }

    @Override
    public void invalidate(StaticBuffer key) {
        expiredKeys.put(key,getExpirationTime());
        if (Math.random()<1.0/INVALIDATE_KEY_FRACTION_PENALTY) penaltyCountdown.countDown();
    }

    @Override
    public void close() {
        cleanupThread.stopThread();
    }

    private class CleanupThread extends Thread {

        private boolean stop = false;

        public CleanupThread() {
            this.setDaemon(true);
            this.setName("ExpirationStoreCache-" + getId());
        }

        @Override
        public void run() {
            while (true) {
                if (stop) return;
                try {

                    penaltyCountdown.await();
                } catch (InterruptedException e) {
                    if (stop) return;
                    else throw new RuntimeException("Cleanup thread got interrupted",e);
                }
                //Do clean up work by invalidating all entries for expired keys
                HashMap<StaticBuffer,Long> expiredKeysCopy = new HashMap<StaticBuffer,Long>(expiredKeys.size());
                for (Map.Entry<StaticBuffer,Long> expKey : expiredKeys.entrySet()) {
                    if (isBeyondExpirationTime(expKey.getValue()))
                        expiredKeys.remove(expKey.getKey(), expKey.getValue());
                    else if (getAge(expKey.getValue())>=expirationGracePeriodMS)
                        expiredKeysCopy.put(expKey.getKey(),expKey.getValue());
                }
                for (KeySliceQuery ksq : cache.asMap().keySet()) {
                    if (expiredKeysCopy.containsKey(ksq.getKey())) cache.invalidate(ksq);
                }
                penaltyCountdown = new CountDownLatch(PENALTY_THRESHOLD);
                for (Map.Entry<StaticBuffer,Long> expKey : expiredKeysCopy.entrySet()) {
                    expiredKeys.remove(expKey.getKey(),expKey.getValue());
                }
            }
        }

        void stopThread() {
            stop = true;
            this.interrupt();
        }
    }
}
TOP

Related Classes of com.thinkaurelius.titan.graphdb.database.cache.ExpirationStoreCache$CleanupThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.