| /* |
| * 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.carbondata.core.cache; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.apache.carbondata.common.logging.LogServiceFactory; |
| import org.apache.carbondata.core.constants.CarbonCommonConstants; |
| import org.apache.carbondata.core.util.CarbonProperties; |
| |
| import net.jodah.expiringmap.ExpirationPolicy; |
| import net.jodah.expiringmap.ExpiringMap; |
| import org.apache.log4j.Logger; |
| |
| /** |
| * class which manages the lru cache |
| */ |
| public final class CarbonLRUCache { |
| /** |
| * constant for converting MB into bytes |
| */ |
| private static final int BYTE_CONVERSION_CONSTANT = 1024 * 1024; |
| /** |
| * Attribute for Carbon LOGGER |
| */ |
| private static final Logger LOGGER = |
| LogServiceFactory.getLogService(CarbonLRUCache.class.getName()); |
| /** |
| * Map that will contain key as table unique name and value as cache Holder |
| * object |
| */ |
| private ExpiringMap<String, Cacheable> expiringMap; |
| /** |
| * lruCacheSize |
| */ |
| private long lruCacheMemorySize; |
| /** |
| * totalSize size of the cache |
| */ |
| private long currentSize; |
| |
| /** |
| * @param propertyName property name to take the size configured |
| * @param defaultPropertyName default property in case size is not configured |
| */ |
| public CarbonLRUCache(String propertyName, String defaultPropertyName) { |
| try { |
| lruCacheMemorySize = Long |
| .parseLong(CarbonProperties.getInstance().getProperty(propertyName, defaultPropertyName)); |
| } catch (NumberFormatException e) { |
| LOGGER.error(CarbonCommonConstants.CARBON_MAX_DRIVER_LRU_CACHE_SIZE |
| + " is not in a valid format. Falling back to default value: " |
| + CarbonCommonConstants.CARBON_MAX_LRU_CACHE_SIZE_DEFAULT); |
| lruCacheMemorySize = Long.parseLong(defaultPropertyName); |
| } |
| |
| // if lru cache is bigger than jvm max heap then set part size of max heap (60% default) |
| if (isBeyondMaxMemory()) { |
| double changeSize = getPartOfXmx(); |
| LOGGER.warn("Configured LRU size " + lruCacheMemorySize + |
| "MB exceeds the max size of JVM heap. Carbon will fallback to use " + |
| changeSize + " MB instead"); |
| lruCacheMemorySize = (long)changeSize; |
| } |
| |
| initCache(); |
| if (lruCacheMemorySize > 0) { |
| LOGGER.info("Configured LRU cache size is " + lruCacheMemorySize + " MB"); |
| // convert in bytes |
| lruCacheMemorySize = lruCacheMemorySize * BYTE_CONVERSION_CONSTANT; |
| } else { |
| LOGGER.info("LRU cache size not configured. Therefore default behavior will be " |
| + "considered and no LRU based eviction of columns will be done"); |
| } |
| } |
| |
| /** |
| * initialize lru cache |
| */ |
| private void initCache() { |
| // Cache entries can have individual variable expiration times and policies by adding |
| // variableExpiration to the map. ExpirationPolicy.ACCESSED means the expiration can occur based |
| // on last access time |
| expiringMap = |
| ExpiringMap.builder().expirationPolicy(ExpirationPolicy.ACCESSED).variableExpiration() |
| .build(); |
| } |
| |
| /** |
| * This method will give the list of all the keys that can be deleted from |
| * the level LRU cache |
| */ |
| private List<String> getKeysToBeRemoved(long size) { |
| List<String> toBeDeletedKeys = |
| new ArrayList<String>(CarbonCommonConstants.DEFAULT_COLLECTION_SIZE); |
| long removedSize = 0; |
| for (Entry<String, Cacheable> entry : expiringMap.entrySet()) { |
| String key = entry.getKey(); |
| Cacheable cacheInfo = entry.getValue(); |
| long memorySize = cacheInfo.getMemorySize(); |
| if (canBeRemoved(cacheInfo)) { |
| removedSize = removedSize + memorySize; |
| toBeDeletedKeys.add(key); |
| // check if after removing the current file size, required |
| // size when added to current size is sufficient to load a |
| // level or not |
| if (lruCacheMemorySize >= (currentSize - memorySize + size)) { |
| toBeDeletedKeys.clear(); |
| toBeDeletedKeys.add(key); |
| removedSize = memorySize; |
| break; |
| } |
| // check if after removing the added size/removed size, |
| // required size when added to current size is sufficient to |
| // load a level or not |
| else if (lruCacheMemorySize >= (currentSize - removedSize + size)) { |
| break; |
| } |
| } |
| } |
| // this case will come when iteration is complete over the keys but |
| // still size is not sufficient for level file to be loaded, then we |
| // will not delete any of the keys |
| if ((currentSize - removedSize + size) > lruCacheMemorySize) { |
| toBeDeletedKeys.clear(); |
| } |
| return toBeDeletedKeys; |
| } |
| |
| /** |
| * @param cacheInfo |
| * @return |
| */ |
| private boolean canBeRemoved(Cacheable cacheInfo) { |
| if (cacheInfo.getAccessCount() > 0) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * @param key |
| */ |
| public void remove(String key) { |
| synchronized (expiringMap) { |
| removeKey(key); |
| } |
| } |
| |
| /** |
| * @param keys |
| */ |
| public void removeAll(List<String> keys) { |
| synchronized (expiringMap) { |
| for (String key : keys) { |
| removeKey(key); |
| } |
| } |
| } |
| |
| /** |
| * This method will remove the key from lru cache |
| * |
| * @param key |
| */ |
| private void removeKey(String key) { |
| Cacheable cacheable = expiringMap.get(key); |
| if (null != cacheable) { |
| long memorySize = cacheable.getMemorySize(); |
| cacheable.invalidate(); |
| expiringMap.remove(key); |
| currentSize = currentSize - memorySize; |
| LOGGER.info("Removed entry from InMemory lru cache :: " + key); |
| } |
| } |
| |
| /** |
| * This method will check if required size is available in the memory and then add |
| * the given cacheable to object to lru cache |
| * |
| * @param columnIdentifier |
| * @param cacheInfo |
| */ |
| public boolean put(String columnIdentifier, Cacheable cacheInfo, long requiredSize, |
| long expiration_time) { |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Required size for entry " + columnIdentifier + " :: " + requiredSize |
| + " Current cache size :: " + currentSize); |
| } |
| boolean columnKeyAddedSuccessfully = false; |
| if (isLRUCacheSizeConfigured()) { |
| synchronized (expiringMap) { |
| if (freeMemorySizeForAddingCache(requiredSize)) { |
| currentSize = currentSize + requiredSize; |
| addEntryToLRUCacheMap(columnIdentifier, cacheInfo, expiration_time); |
| columnKeyAddedSuccessfully = true; |
| } else { |
| LOGGER.error( |
| "Size not available. Entry cannot be added to lru cache :: " + columnIdentifier |
| + " .Required Size = " + requiredSize + " Size available " + (lruCacheMemorySize |
| - currentSize)); |
| } |
| } |
| } else { |
| synchronized (expiringMap) { |
| addEntryToLRUCacheMap(columnIdentifier, cacheInfo, expiration_time); |
| currentSize = currentSize + requiredSize; |
| } |
| columnKeyAddedSuccessfully = true; |
| } |
| return columnKeyAddedSuccessfully; |
| } |
| |
| /** |
| * The method will add the cache entry to LRU cache map |
| * |
| * @param columnIdentifier |
| * @param cacheInfo |
| */ |
| private void addEntryToLRUCacheMap(String columnIdentifier, Cacheable cacheInfo, |
| long expirationTimeSeconds) { |
| if (null == expiringMap.get(columnIdentifier) && expirationTimeSeconds != 0L) { |
| expiringMap.put(columnIdentifier, cacheInfo, ExpirationPolicy.ACCESSED, expirationTimeSeconds, |
| TimeUnit.SECONDS); |
| } else expiringMap.putIfAbsent(columnIdentifier, cacheInfo); |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Added entry to InMemory lru cache :: " + columnIdentifier); |
| } |
| } |
| |
| /** |
| * this will check whether the LRU cache size is configured |
| * |
| * @return <Boolean> value |
| */ |
| private boolean isLRUCacheSizeConfigured() { |
| return lruCacheMemorySize > 0; |
| } |
| |
| /** |
| * This method will check a required column can be loaded into memory or not. If required |
| * this method will call for eviction of existing data from memory |
| * |
| * @param requiredSize |
| * @return |
| */ |
| private boolean freeMemorySizeForAddingCache(long requiredSize) { |
| boolean memoryAvailable = false; |
| if (isSizeAvailableToLoadColumnDictionary(requiredSize)) { |
| memoryAvailable = true; |
| } else { |
| // get the keys that can be removed from memory |
| List<String> keysToBeRemoved = getKeysToBeRemoved(requiredSize); |
| for (String cacheKey : keysToBeRemoved) { |
| removeKey(cacheKey); |
| } |
| // after removing the keys check again if required size is available |
| if (isSizeAvailableToLoadColumnDictionary(requiredSize)) { |
| memoryAvailable = true; |
| } |
| } |
| return memoryAvailable; |
| } |
| |
| /** |
| * This method will check if size is available to laod dictionary into memory |
| * |
| * @param requiredSize |
| * @return |
| */ |
| private boolean isSizeAvailableToLoadColumnDictionary(long requiredSize) { |
| return lruCacheMemorySize >= (currentSize + requiredSize); |
| } |
| |
| /** |
| * @param key |
| * @return |
| */ |
| public Cacheable get(String key) { |
| synchronized (expiringMap) { |
| return expiringMap.get(key); |
| } |
| } |
| |
| /** |
| * This method will empty the level cache |
| */ |
| public void clear() { |
| synchronized (expiringMap) { |
| for (Cacheable cachebleObj : expiringMap.values()) { |
| cachebleObj.invalidate(); |
| } |
| expiringMap.clear(); |
| } |
| } |
| |
| public Map<String, Cacheable> getCacheMap() { |
| return expiringMap; |
| } |
| |
| /** |
| * Check if LRU cache setting is bigger than max memory of jvm. |
| * if LRU cache is bigger than max memory of jvm when query for a big segments table, |
| * may cause JDBC server crash. |
| * @return true LRU cache is bigger than max memory of jvm, false otherwise |
| */ |
| private boolean isBeyondMaxMemory() { |
| long mSize = Runtime.getRuntime().maxMemory(); |
| long lruSize = lruCacheMemorySize * BYTE_CONVERSION_CONSTANT; |
| return lruSize >= mSize; |
| } |
| |
| /** |
| * when LRU cache is bigger than max heap of jvm. |
| * set to part of max heap size, use CARBON_LRU_CACHE_PERCENT_OVER_MAX_SIZE default 60%. |
| * @return the LRU cache size |
| */ |
| private double getPartOfXmx() { |
| long mSizeMB = Runtime.getRuntime().maxMemory() / BYTE_CONVERSION_CONSTANT; |
| return mSizeMB * CarbonCommonConstants.CARBON_LRU_CACHE_PERCENT_OVER_MAX_SIZE; |
| } |
| |
| /** |
| * @return current size of the cache in memory. |
| */ |
| public long getCurrentSize() { |
| return currentSize; |
| } |
| } |