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

org.apache.solr.search.LFUCache Maven / Gradle / Ivy

/*
 * 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.solr.search;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.RamUsageEstimator;
import org.apache.solr.common.SolrException;
import org.apache.solr.metrics.MetricsMap;
import org.apache.solr.metrics.SolrMetricsContext;
import org.apache.solr.util.ConcurrentLFUCache;
import org.apache.solr.util.IOFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.solr.common.params.CommonParams.NAME;

/**
 * SolrCache based on ConcurrentLFUCache implementation.
 * 

* This implementation does not use a separate cleanup thread. Instead it uses the calling thread * itself to do the cleanup when the size of the cache exceeds certain limits. *

* Also see SolrCaching *

* This API is experimental and subject to change * * @see org.apache.solr.util.ConcurrentLFUCache * @see org.apache.solr.search.SolrCache * @since solr 3.6 * @deprecated This cache implementation is deprecated and will be removed in Solr 9.0. * Use {@link CaffeineCache} instead. */ public class LFUCache implements SolrCache, Accountable { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(LFUCache.class); public static final String TIME_DECAY_PARAM = "timeDecay"; public static final String CLEANUP_THREAD_PARAM = "cleanupThread"; public static final String INITIAL_SIZE_PARAM = "initialSize"; public static final String MIN_SIZE_PARAM = "minSize"; public static final String ACCEPTABLE_SIZE_PARAM = "acceptableSize"; public static final String AUTOWARM_COUNT_PARAM = "autowarmCount"; public static final String SHOW_ITEMS_PARAM = "showItems"; // contains the statistics objects for all open caches of the same type private List statsList; private long warmupTime = 0; private String name; private int autowarmCount; private State state; private CacheRegenerator regenerator; private String description = "Concurrent LFU Cache"; private ConcurrentLFUCache cache; private int showItems = 0; private Boolean timeDecay = true; private int maxIdleTimeSec; private MetricsMap cacheMap; private Set metricNames = ConcurrentHashMap.newKeySet(); private SolrMetricsContext solrMetricsContext; private int maxSize; private int minSizeLimit; private int initialSize; private int acceptableSize; private boolean cleanupThread; @Override public Object init(Map args, Object persistence, CacheRegenerator regenerator) { state = State.CREATED; this.regenerator = regenerator; name = (String) args.get(NAME); String str = (String) args.get(SIZE_PARAM); maxSize = str == null ? 1024 : Integer.parseInt(str); str = (String) args.get(MIN_SIZE_PARAM); if (str == null) { minSizeLimit = (int) (maxSize * 0.9); } else { minSizeLimit = Integer.parseInt(str); } checkAndAdjustLimits(); str = (String) args.get(ACCEPTABLE_SIZE_PARAM); if (str == null) { acceptableSize = (int) (maxSize * 0.95); } else { acceptableSize = Integer.parseInt(str); } // acceptable limit should be somewhere between minLimit and limit acceptableSize = Math.max(minSizeLimit, acceptableSize); str = (String) args.get(INITIAL_SIZE_PARAM); initialSize = str == null ? maxSize : Integer.parseInt(str); str = (String) args.get(AUTOWARM_COUNT_PARAM); autowarmCount = str == null ? 0 : Integer.parseInt(str); str = (String) args.get(CLEANUP_THREAD_PARAM); cleanupThread = str == null ? false : Boolean.parseBoolean(str); str = (String) args.get(SHOW_ITEMS_PARAM); showItems = str == null ? 0 : Integer.parseInt(str); // Don't make this "efficient" by removing the test, default is true and omitting the param will make it false. str = (String) args.get(TIME_DECAY_PARAM); timeDecay = (str == null) ? true : Boolean.parseBoolean(str); str = (String) args.get(MAX_IDLE_TIME_PARAM); if (str == null) { maxIdleTimeSec = -1; } else { maxIdleTimeSec = Integer.parseInt(str); } description = generateDescription(); cache = new ConcurrentLFUCache<>(maxSize, minSizeLimit, acceptableSize, initialSize, cleanupThread, false, null, timeDecay, maxIdleTimeSec); cache.setAlive(false); statsList = (List) persistence; if (statsList == null) { // must be the first time a cache of this type is being created // Use a CopyOnWriteArrayList since puts are very rare and iteration may be a frequent operation // because it is used in getStatistics() statsList = new CopyOnWriteArrayList<>(); // the first entry will be for cumulative stats of caches that have been closed. statsList.add(new ConcurrentLFUCache.Stats()); } statsList.add(cache.getStats()); return statsList; } private String generateDescription() { String descr = "Concurrent LFU Cache(maxSize=" + maxSize + ", initialSize=" + initialSize + ", minSize=" + minSizeLimit + ", acceptableSize=" + acceptableSize + ", cleanupThread=" + cleanupThread + ", timeDecay=" + timeDecay + ", maxIdleTime=" + maxIdleTimeSec; if (autowarmCount > 0) { descr += ", autowarmCount=" + autowarmCount + ", regenerator=" + regenerator; } descr += ')'; return descr; } @Override public String name() { return name; } @Override public int size() { return cache.size(); } @Override public V put(K key, V value) { return cache.put(key, value); } @Override public V remove(K key) { return cache.remove(key); } @Override public V computeIfAbsent(K key, IOFunction mappingFunction) { return cache.computeIfAbsent(key, k -> { try { return mappingFunction.apply(k); } catch (IOException e) { throw new UncheckedIOException(e); } }); } @Override public V get(K key) { return cache.get(key); } @Override public void clear() { cache.clear(); } @Override public void setState(State state) { this.state = state; cache.setAlive(state == State.LIVE); } @Override public State getState() { return state; } @Override public void warm(SolrIndexSearcher searcher, SolrCache old) { if (regenerator == null) return; long warmingStartTime = System.nanoTime(); LFUCache other = (LFUCache) old; // warm entries if (autowarmCount != 0) { int sz = other.size(); if (autowarmCount != -1) sz = Math.min(sz, autowarmCount); Map items = other.cache.getMostUsedItems(sz); Map.Entry[] itemsArr = new Map.Entry[items.size()]; int counter = 0; for (Object mapEntry : items.entrySet()) { itemsArr[counter++] = (Map.Entry) mapEntry; } for (int i = itemsArr.length - 1; i >= 0; i--) { try { boolean continueRegen = regenerator.regenerateItem(searcher, this, old, itemsArr[i].getKey(), itemsArr[i].getValue()); if (!continueRegen) break; } catch (Exception e) { SolrException.log(log, "Error during auto-warming of key:" + itemsArr[i].getKey(), e); } } } warmupTime = TimeUnit.MILLISECONDS.convert(System.nanoTime() - warmingStartTime, TimeUnit.NANOSECONDS); } @Override public void close() throws IOException { SolrCache.super.close(); // add the stats to the cumulative stats object (the first in the statsList) statsList.get(0).add(cache.getStats()); statsList.remove(cache.getStats()); cache.destroy(); } //////////////////////// SolrInfoMBeans methods ////////////////////// @Override public String getName() { return LFUCache.class.getName(); } @Override public String getDescription() { return description; } @Override public Category getCategory() { return Category.CACHE; } // returns a ratio, not a percent. private static String calcHitRatio(long lookups, long hits) { if (lookups == 0) return "0.00"; if (lookups == hits) return "1.00"; int hundredths = (int) (hits * 100 / lookups); // rounded down if (hundredths < 10) return "0.0" + hundredths; return "0." + hundredths; } @Override public SolrMetricsContext getSolrMetricsContext() { return solrMetricsContext; } @Override public void initializeMetrics(SolrMetricsContext parentContext, String scope) { solrMetricsContext = parentContext.getChildContext(this); cacheMap = new MetricsMap((detailed, map) -> { if (cache != null) { ConcurrentLFUCache.Stats stats = cache.getStats(); long lookups = stats.getCumulativeLookups(); long hits = stats.getCumulativeHits(); long inserts = stats.getCumulativePuts(); long evictions = stats.getCumulativeEvictions(); long idleEvictions = stats.getCumulativeIdleEvictions(); long size = stats.getCurrentSize(); map.put(LOOKUPS_PARAM, lookups); map.put(HITS_PARAM, hits); map.put(HIT_RATIO_PARAM, calcHitRatio(lookups, hits)); map.put(INSERTS_PARAM, inserts); map.put(EVICTIONS_PARAM, evictions); map.put(SIZE_PARAM, size); map.put(MAX_SIZE_PARAM, maxSize); map.put(MIN_SIZE_PARAM, minSizeLimit); map.put(ACCEPTABLE_SIZE_PARAM, acceptableSize); map.put(AUTOWARM_COUNT_PARAM, autowarmCount); map.put(CLEANUP_THREAD_PARAM, cleanupThread); map.put(SHOW_ITEMS_PARAM, showItems); map.put(TIME_DECAY_PARAM, timeDecay); map.put(RAM_BYTES_USED_PARAM, ramBytesUsed()); map.put(MAX_IDLE_TIME_PARAM, maxIdleTimeSec); map.put("idleEvictions", idleEvictions); map.put("warmupTime", warmupTime); long clookups = 0; long chits = 0; long cinserts = 0; long cevictions = 0; long cidleEvictions = 0; // NOTE: It is safe to iterate on a CopyOnWriteArrayList for (ConcurrentLFUCache.Stats statistics : statsList) { clookups += statistics.getCumulativeLookups(); chits += statistics.getCumulativeHits(); cinserts += statistics.getCumulativePuts(); cevictions += statistics.getCumulativeEvictions(); cidleEvictions += statistics.getCumulativeIdleEvictions(); } map.put("cumulative_lookups", clookups); map.put("cumulative_hits", chits); map.put("cumulative_hitratio", calcHitRatio(clookups, chits)); map.put("cumulative_inserts", cinserts); map.put("cumulative_evictions", cevictions); map.put("cumulative_idleEvictions", cidleEvictions); if (detailed && showItems != 0) { Map items = cache.getMostUsedItems(showItems == -1 ? Integer.MAX_VALUE : showItems); for (Map.Entry e : (Set) items.entrySet()) { Object k = e.getKey(); Object v = e.getValue(); String ks = "item_" + k; String vs = v.toString(); map.put(ks, vs); } } } }); solrMetricsContext.gauge(this, cacheMap, true, scope, getCategory().toString()); } // for unit tests only MetricsMap getMetricsMap() { return cacheMap; } @Override public Set getMetricNames() { return metricNames; } @Override public String toString() { return name + (cacheMap != null ? cacheMap.getValue().toString() : ""); } @Override public long ramBytesUsed() { synchronized (statsList) { return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOfObject(name) + RamUsageEstimator.sizeOfObject(metricNames) + RamUsageEstimator.sizeOfObject(statsList) + RamUsageEstimator.sizeOfObject(cache); } } @Override public int getMaxSize() { return maxSize != Integer.MAX_VALUE ? maxSize : -1; } @Override public void setMaxSize(int maxSize) { if (maxSize > 0) { this.maxSize = maxSize; } else { this.maxSize = Integer.MAX_VALUE; } checkAndAdjustLimits(); cache.setUpperWaterMark(maxSize); cache.setLowerWaterMark(minSizeLimit); description = generateDescription(); } @Override public int getMaxRamMB() { return -1; } @Override public void setMaxRamMB(int maxRamMB) { // no-op } private void checkAndAdjustLimits() { if (minSizeLimit <= 0) minSizeLimit = 1; if (maxSize <= minSizeLimit) { if (maxSize > 1) { minSizeLimit = maxSize - 1; } else { maxSize = minSizeLimit + 1; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy