All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rackspacecloud.blueflood.cache.TtlCache Maven / Gradle / Ivy

/*
 * Copyright 2013 Rackspace
 *
 *    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.rackspacecloud.blueflood.cache;

import com.netflix.astyanax.model.ColumnFamily;
import com.rackspacecloud.blueflood.internal.Account;
import com.rackspacecloud.blueflood.internal.ClusterException;
import com.rackspacecloud.blueflood.internal.InternalAPI;
import com.rackspacecloud.blueflood.io.AstyanaxIO;
import com.rackspacecloud.blueflood.rollup.Granularity;
import com.rackspacecloud.blueflood.types.Locator;
import com.rackspacecloud.blueflood.utils.TimeValue;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Meter;
import org.apache.http.client.HttpResponseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

// todo: need to go into safety mode if http fetches are taking too long.
public class TtlCache extends AbstractJmxCache implements TtlCacheMBean {
    private static final Logger log = LoggerFactory.getLogger(TtlCache.class);
    
    // these values get used in the absence of a ttl (internal API failure, etc.).
    static final Map, TimeValue> SAFETY_TTLS =
            new HashMap, TimeValue>() {{
                /// FIX: TTLs should be specified at CF level
                for (Granularity gran : Granularity.granularities())
                    put(AstyanaxIO.getColumnFamilyMapper().get(gran.name()),
                        new TimeValue(gran.getTTL().getValue() * 5, gran.getTTL().getUnit()));
            }};
    
    private final com.google.common.cache.LoadingCache, TimeValue>> cache;
    private final Meter generalErrorMeter;
    private final Meter httpErrorMeter;
    
    // allowable errors per minute.
    private double safetyThreshold = 10d;
    
    private volatile long lastFetchError = 0;

    public TtlCache(String label, TimeValue expiration, int cacheConcurrency, final InternalAPI internalAPI) {
        try {
            final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
            final String name = String.format(TtlCache.class.getPackage().getName() + ":type=%s,scope=%s,name=Stats", TtlCache.class.getSimpleName(), label);
            final ObjectName nameObj = new ObjectName(name);
            mbs.registerMBean(this, nameObj);
            instantiateYammerMetrics(TtlCache.class, label, nameObj);
        } catch (Exception ex) {
            log.error("Unable to register mbean for " + getClass().getName());
        }
        generalErrorMeter = Metrics.newMeter(TtlCache.class, "Load Errors", label, "Rollups", TimeUnit.MINUTES);
        httpErrorMeter = Metrics.newMeter(TtlCache.class, "Http Errors", label, "Rollups", TimeUnit.MINUTES);
        
        
        CacheLoader, TimeValue>> loader =
                new CacheLoader, TimeValue>>() {
            
                    // values from the default account are used to build a ttl map for tenants that do not exist in the
                    // internal API.  These values are put into the cache, meaning subsequent cache requests do not
                    // incur a miss and hit the internal API.
                    private final Account DEFAULT_ACCOUNT = new Account() {
                        @Override
                        public TimeValue getMetricTtl(String resolution) {
                            return SAFETY_TTLS.get(AstyanaxIO.getColumnFamilyMapper()
                                    .get(Granularity.fromString(resolution).name()));
                        }
                    };

                    @Override
                    public Map, TimeValue> load(final String key) throws Exception {
                        // load account, build ttl map.
                        try {
                            Account acct = internalAPI.fetchAccount(key);
                            return buildTtlMap(acct);
                        } catch (HttpResponseException ex) {
                            // cache the default value on a 404. this means that we will not be hammering the API for values
                            // that are constantly not there.  The other option was to let the Http error bubble out, use a
                            // and value from SAFETY_TTLS.  But the same thing (an HTTP round trip) would happen the very next
                            // time a TTL is requested.
                            if (ex.getStatusCode() == 404) {
                                httpErrorMeter.mark();
                                log.warn(ex.getMessage());
                                return buildTtlMap(DEFAULT_ACCOUNT);
                            } else
                                throw ex;
                        }

                    }
                };
        cache = CacheBuilder.newBuilder()
                .expireAfterWrite(expiration.getValue(), expiration.getUnit())
                .concurrencyLevel(cacheConcurrency)
                .recordStats()
                .build(loader);
    }
    
    // override this if you're not interested in caching the entire ttl map.
    protected Map, TimeValue> buildTtlMap(Account acct) {
        Map, TimeValue> map = new HashMap, TimeValue>();
        for (Granularity gran : Granularity.granularities())
            map.put(AstyanaxIO.getColumnFamilyMapper().get(gran.name()), acct.getMetricTtl(gran.shortName()));
        return map;
    }
    
    // may return null (e.g.: if the granularity isn't in the build-map.
    public TimeValue getTtl(String acctId, ColumnFamily CF) {
        // if error rate exceeds a threshold return SAFETY_TTL.  Otherwise, we spend a non-trivial amount of time stuck
        // in blocking calls into whatever obtains the account.
        if (isSafetyMode())
            return SAFETY_TTLS.get(CF);
        
        try {
            return cache.get(acctId).get(CF);
        } catch (ExecutionException ex) {
            if (ex.getCause() instanceof HttpResponseException) {
                httpErrorMeter.mark();
                log.debug(ex.getCause().getMessage());
            } else {
                // log this every 10s, then use a sane default value for the ttl.
                generalErrorMeter.mark();
                long now = System.currentTimeMillis();
                if (now - lastFetchError > 2000) {
                    log.error("Problem fetching accounts from internal API");
                    if (ex.getCause() instanceof ClusterException) {
                        for (Throwable subCause : ((ClusterException)ex.getCause()).getExceptions())
                            log.error("Subcause: " + subCause.getMessage());
                    } else
                        log.error(ex.getCause().getMessage());
                    lastFetchError = now; // I don't care about races here.
                }
            }
            return SAFETY_TTLS.get(CF);
        }
    }
    
    //
    // Jmx methods
    //


    @Override
    public CacheStats getStats() {
        return cache.stats();
    }

    public synchronized boolean isSafetyMode() {
        return generalErrorMeter.oneMinuteRate() > safetyThreshold;
    }

    public synchronized void setSafetyThreshold(double d) { safetyThreshold = d; }
    public synchronized double getSafetyThreshold() { return safetyThreshold; }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy