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

com.dell.doradus.service.spider.ShardCache Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Dell, Inc.
 * 
 * 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.dell.doradus.service.spider;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

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

import com.dell.doradus.common.ApplicationDefinition;
import com.dell.doradus.common.TableDefinition;
import com.dell.doradus.common.Utils;
import com.dell.doradus.service.db.DBService;
import com.dell.doradus.service.db.DBTransaction;
import com.dell.doradus.service.db.DColumn;
import com.dell.doradus.service.db.Tenant;

/**
 * This class caches sharding information for sharded tables. For each sharded table, a
 * map is maintained of shard numbers to shard start dates. The cache is intended
 * primarily for update code to quickly determine if an object update will start a new
 * shard. The cache is refreshed occasionally to compensate for data aging and other
 * background activities.
 */
public class ShardCache {
    // The cache expiration time:
    private static final long MAX_CACHE_TIME_MILLIS = 1000 * 60;  // 1 minute
    
    // Cache key-to-cached date map. Key is /, and the value
    // is the cache timestamp.
    private final Map m_cacheMap = new ConcurrentHashMap();
    
    // Logging interface:
    private final Logger m_logger = LoggerFactory.getLogger(getClass().getSimpleName());
    
    // This map stores app name -> table name -> shard number -> sharding-start date 
    private final Map>> m_appShardMap =
        new HashMap>>();
    
    /**
     * Clear all cached shard information.
     */
    public synchronized void clearAll() {
        m_appShardMap.clear();
        m_cacheMap.clear();
    }   // clearAll
    
    /**
     * Clear all cached shard information for the given application.
     * 
     * @param appDef    {@link ApplicationDefinition} of an application.
     */
    public synchronized void clear(ApplicationDefinition appDef) {
        String appName = appDef.getAppName();
        for (TableDefinition tableDef : appDef.getTableDefinitions().values()) {
            m_cacheMap.remove(appName + "/" + tableDef.getTableName()); // might have no entry
        }
        m_appShardMap.remove(appName);
    }   // clear

    /**
     * Get the shard number-to-date map for a sharded table. The given
     * {@link TableDefinition} must be sharded. If the given table has no registered
     * shards, an empty map is returned. The map is copied so the caller is free
     * to modify.
     *   
     * @param tableDef  {@link TableDefinition} of a sharded table.
     * @return          Map of shard numbers-to-start dates. The map will be empty
     *                  (but not null) if the given table currently has no non-default shards.
     */
    public synchronized Map getShardMap(TableDefinition tableDef) {
        assert tableDef != null;
        assert tableDef.isSharded();
        
        String appName = tableDef.getAppDef().getAppName();
        String tableName = tableDef.getTableName();
        String cacheKey = appName + "/" + tableName;
        Date cacheDate = m_cacheMap.get(cacheKey);
        if (cacheDate == null || isTooOld(cacheDate)) {
            loadShardCache(tableDef);
        }
        
        Map result = new HashMap();
        Map> tableMap = m_appShardMap.get(appName);
        if (tableMap != null) {
            Map shardMap = tableMap.get(tableDef.getTableName());
            if (shardMap != null) {
                result.putAll(shardMap);
            }
        }
        return result;
    }   // getShardMap
    
    /**
     * Verify that the shard with the given number has been registered for the given table.
     * If it hasn't, the shard's starting date is computed, the shard is registered in the
     * _shards row, and the start date is cached.
     * 
     * @param tableDef      TableDefinition of a sharded table.
     * @param shardNumber   Shard number (must be > 0).
     */
    public synchronized void verifyShard(TableDefinition tableDef, int shardNumber) {
        assert tableDef != null;
        assert tableDef.isSharded();
        assert shardNumber > 0;
        
        Map> tableMap = m_appShardMap.get(tableDef.getAppDef().getAppName());
        if (tableMap != null) {
            Map shardMap = tableMap.get(tableDef.getTableName());
            if (shardMap != null) {
                if (shardMap.containsKey(shardNumber)) {
                    return;
                }
            }
        }
        
        // Unknown app/table/shard number, so start it.
        Date shardDate = tableDef.computeShardStart(shardNumber);
        addShardStart(tableDef, shardNumber, shardDate);
    }   // verifyShard
    
    ///// Private methods
    
    // Create a local transaction to add the register the given shard, then cache it.
    private void addShardStart(TableDefinition tableDef, int shardNumber, Date shardDate) {
        SpiderTransaction spiderTran = new SpiderTransaction();
        spiderTran.addShardStart(tableDef, shardNumber, shardDate);
        DBTransaction dbTran = DBService.instance().startTransaction(Tenant.getTenant(tableDef));
        spiderTran.applyUpdates(dbTran);
        DBService.instance().commit(dbTran);
        synchronized (this) {
            cacheShardValue(tableDef, shardNumber, shardDate);
        }
    }   // addShardStart

    // Cache the given shard.
    private void cacheShardValue(TableDefinition tableDef,
                                 Integer         shardNumber,
                                 Date            shardStart) {
        // Get or create the app name -> table map
        String appName = tableDef.getAppDef().getAppName();
        Map> tableShardNumberMap = m_appShardMap.get(appName);
        if (tableShardNumberMap == null) {
            tableShardNumberMap = new HashMap>();
            m_appShardMap.put(appName, tableShardNumberMap);
        }
        
        // Get or create the table name -> shard number map
        String tableName = tableDef.getTableName();
        Map shardNumberMap = tableShardNumberMap.get(tableName);
        if (shardNumberMap == null) {
            shardNumberMap = new HashMap();
            tableShardNumberMap.put(tableName, shardNumberMap);
        }
        
        // Add the shard number -> start date
        shardNumberMap.put(shardNumber, shardStart);
        m_logger.debug("Sharding date for {}.{} shard #{} set to: {} ({})",
                       new Object[]{appName, tableName, shardNumber, shardStart.getTime(), Utils.formatDateUTC(shardStart)});
    }   // cacheShardValue

    // Load and cache/replace sharding information for the given table. Caller must
    // worry about concurrency.
    private void loadShardCache(TableDefinition tableDef) {
        String appName = tableDef.getAppDef().getAppName();
        String tableName = tableDef.getTableName();
        m_logger.debug("Loading shard cache for {}.{}", appName, tableName);
        
        Date cacheDate = new Date();
        String cacheKey = appName + "/" + tableName;
        m_cacheMap.put(cacheKey, cacheDate);
        
        Map> tableMap = m_appShardMap.get(appName);
        if (tableMap == null) {
            tableMap = new HashMap<>();
            m_appShardMap.put(appName, tableMap);
        }
        
        Map shardMap = tableMap.get(tableName);
        if (shardMap == null) {
            shardMap = new HashMap<>();
            tableMap.put(tableName, shardMap);
        }
        
        Iterator colIter =
            DBService.instance().getAllColumns(Tenant.getTenant(tableDef), SpiderService.termsStoreName(tableDef), SpiderTransaction.SHARDS_ROW_KEY);
        while (colIter.hasNext()) {
            DColumn col = colIter.next();
            Integer shardNum = Integer.parseInt(col.getName());
            Date shardDate = new Date(Long.parseLong(col.getValue()));
            shardMap.put(shardNum, shardDate);
        }
    }   // loadShardCache
    
    // Return true if given date has exceeded MAX_CACHE_TIME_MILLIS time.
    private boolean isTooOld(Date cacheDate) {
        Date now = new Date();
        return now.getTime() - cacheDate.getTime() > MAX_CACHE_TIME_MILLIS;
    }   // isTooOld

}   // class ShardCache