com.sleepycat.je.evictor.Evictor Maven / Gradle / Ivy
The newest version!
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.je.evictor;
import static com.sleepycat.je.evictor.EvictorStatDefinition.BIN_DELTA_BLIND_OPS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.BIN_DELTA_FETCH_MISS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.BIN_FETCH;
import static com.sleepycat.je.evictor.EvictorStatDefinition.BIN_FETCH_MISS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.BIN_FETCH_MISS_RATIO;
import static com.sleepycat.je.evictor.EvictorStatDefinition.CACHED_IN_COMPACT_KEY;
import static com.sleepycat.je.evictor.EvictorStatDefinition.CACHED_IN_NO_TARGET;
import static com.sleepycat.je.evictor.EvictorStatDefinition.CACHED_IN_SPARSE_TARGET;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_DIRTY_NODES_EVICTED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_EVICTION_RUNS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_LNS_EVICTED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_EVICTED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_MOVED_TO_PRI2_LRU;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_MUTATED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_PUT_BACK;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_SKIPPED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_STRIPPED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_TARGETED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_ROOT_NODES_EVICTED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_SHARED_CACHE_ENVS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.FULL_BIN_MISS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.GROUP_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.GROUP_NAME;
import static com.sleepycat.je.evictor.EvictorStatDefinition.LN_FETCH;
import static com.sleepycat.je.evictor.EvictorStatDefinition.LN_FETCH_MISS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_CACHEMODE_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_CACHEMODE_NAME;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_CRITICAL_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_CRITICAL_NAME;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_DAEMON_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_DAEMON_NAME;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_EVICTORTHREAD_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_EVICTORTHREAD_NAME;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_MANUAL_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.N_BYTES_EVICTED_MANUAL_NAME;
import static com.sleepycat.je.evictor.EvictorStatDefinition.PRI1_LRU_SIZE;
import static com.sleepycat.je.evictor.EvictorStatDefinition.PRI2_LRU_SIZE;
import static com.sleepycat.je.evictor.EvictorStatDefinition.THREAD_UNAVAILABLE;
import static com.sleepycat.je.evictor.EvictorStatDefinition.UPPER_IN_FETCH;
import static com.sleepycat.je.evictor.EvictorStatDefinition.UPPER_IN_FETCH_MISS;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.CacheMode;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.EnvironmentMutableConfig;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.DbTree;
import com.sleepycat.je.dbi.EnvConfigObserver;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.INList;
import com.sleepycat.je.dbi.MemoryBudget;
import com.sleepycat.je.log.Provisional;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.ChildReference;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.SearchResult;
import com.sleepycat.je.tree.Tree;
import com.sleepycat.je.tree.WithRootLatched;
import com.sleepycat.je.utilint.AtomicLongStat;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.FloatStat;
import com.sleepycat.je.utilint.IntStat;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.LongStat;
import com.sleepycat.je.utilint.StatDefinition;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.StoppableThreadFactory;
import com.sleepycat.je.utilint.TestHook;
import com.sleepycat.je.utilint.TestHookExecute;
/**
*
* Overview
* --------
*
* The Evictor is responsible for managing the JE cache. The cache is
* actually a collection of in-memory btree nodes, implemented by the
* com.sleepycat.je.dbi.INList class. A subset of the nodes in te INList
* are candidates for eviction. This subset is tracked in one or more
* LRULists, which are maintained by the Evictor. When a node is evicted,
* it is detached from its containing BTree and then removed from the INList
* and from its containing LRUList. Once all references to an evicted node
* are removed, it can be GC'd by the JVM.
*
* The Evictor owns a pool of threads that are available to handle eviction
* tasks. The eviction pool is a standard java.util.concurrent thread pool,
* and can be mutably configured in terms of core threads, max threads, and
* keepalive times.
*
* Eviction is carried out by three types of threads:
* 1. An application thread, in the course of doing critical eviction.
* 2. Daemon threads, such as the cleaner or INCompressor, in the course of
* doing their respective duties.
* 3. Eviction pool threads.
*
* Memory consumption is tracked by the MemoryBudget. The Arbiter, which is
* also owned by the Evictor, is used to query the MemoryBudget and determine
* whether eviction is actually needed, and if so, how many bytes should be
* evicted by an evicting thread.
*
* Multiple threads can do eviction concurrently. As a result, it's important
* that eviction is both thread safe and as parallel as possible. Memory
* thresholds are generally accounted for in an unsynchronized fashion, and are
* seen as advisory. The only point of true synchronization is around the
* selection of a node for eviction. The act of eviction itself can be done
* concurrently.
*
* The eviction method is not reentrant, and a simple concurrent hash map
* of threads is used to prevent recursive calls.
*
* Details on the implementation of the LRU-based eviction policy
* --------------------------------------------------------------
*
* ------------------
* Data structures
* ------------------
*
* An LRU eviction policy is approximated by one or more LRULists. An LRUList
* is a doubly linked list consisting of BTree nodes. If a node participates
* in an LRUList, then whenever it is accessed, it moves to the "back" of the
* list. When eviction is needed, the evictor evicts the nodes at the "front"
* of the LRULists.
*
* An LRUList is implemented as 2 IN references: a "front" ref pointing to the
* IN at the front of the list and a "back" ref, pointing to the IN at the back
* of the list. In addition, each IN has "nextLRUNode" and "prevLRUNode" refs
* for participating in an LRUList. This implementation works because an IN can
* belong to at most 1 LRUList at a time. Furthermore, it is the responsibility
* of the Evictor to know which LRUList a node belongs to at any given time
* (more on this below). As a result, each LRUList can assume that a node will
* either not be in any list at all, or will belong to "this" list. This way,
* membership of a node to an LRUList can be tested by just checking that
* either the nextLRUNode or prevLRUNode field of the node is non-null.
*
* The operations on an LRUList are:
*
* - addBack(IN) :
* Insert an IN at the back of the list. Assert that the node does not belong
* to an LRUList already.
*
* - addFront(IN) :
* Insert an IN at the front of the list. Assert that the node does not belong
* to an LRUList already.
*
* - moveBack(IN) :
* Move an IN to the back of the list, if it is in the list already. Noop
* if the node is not in the list.
*
* - moveFront(IN) :
* Move an IN to the front of the list, if it is in the list already. Noop
* if the node is not in the list.
*
* - removeFront() :
* Remove the IN at the front of the list and return it to the caller.
* Return null if the list is empty.
*
* - remove(IN) :
* Remove the IN from the list, if it is there. Return true if the node was
* in the list, false otherwise.
*
* - contains(IN):
* Return true if the node is contained in the list, false otherwise.
*
* All of the above methods are synchronized on the LRUList object. This may
* create a synchronization bottleneck. To alleviate this, the Evictor uses
* multiple LRULists, which taken together comprise a logical LRU list, called
* an LRUSet. The number of LRULists per LRUSet (numLRULists) is fixed and
* determined by a config parameter (max of 64). The LRULists are stored in
* an array whose length is numLRULists.
*
* The Evictor actually maintains 2 LRUSets: priority-1 and priority-2.
* Within an LRUSet, the nodeId is used to place a node to an LRUList: a
* node with id N goes to the (N % numLRULists)-th list. In addition, each
* node has a flag (isInPri2LRU) to identify which LRUSet it belongs to.
* This way, the Evictor knows which LRUList a node should belong to, and
* accesses the appropriate LRUList instance when it needs to add/remove/move
* a node within the LRU.
*
* Access to the isInPri2LRU flag is synchronized via the SH/EX node latch.
*
* When there is no off-heap cache configured, the priority-1 LRU is the
* "mixed" one and the priority-2 LRU is the "dirty" one. When there is an
* off-heap cache configured, the priority-1 LRU is the "normal" one and the
* priority-2 LRU is the "level-2" one.
*
* Justification for the mixed and dirty LRUSets: We would like to keep dirty
* INs in memory as much as possible to achieve "write absorption". Ideally,
* dirty INs should be logged by the checkpointer only. So, we would like to
* have the option in the Evictor to chose a clean IN to evict over a dirty
* IN, even if the dirty IN is colder than the clean IN. In this mode, having
* a single LRUSet will not perform very well in the situation when most (or
* a lot) or the INs are dirty (because each time we get a dirty IN from an
* LRUList, we would have to put it back to the list and try another IN until
* we find a clean one, thus spending a lot of CPU time trying to select an
* eviction target).
*
* Justification for the normal and level-2 LRUSets: With an off-heap cache,
* if level-2 INs were not treated specially, the main cache evictor may run
* out of space and (according to LRU) evict a level 2 IN, even though the IN
* references off-heap BINs (which will also be evicted). The problem is that
* we really don't want to evict the off-heap BINs (or their LNs) when the
* off-heap cache is not full. Therefore we only evict level-2 INs with
* off-heap children when there are no other nodes that can be evicted. A
* level-2 IN is moved to the priority-2 LRUSet when it is encountered by the
* evictor in the priority-1 LRUSet.
*
* Within each LRUSet, picking an LRUList to evict from is done in a round-
* robin fashion. To this end, the Evictor maintains 2 int counters:
* nextPri1LRUList and nextPri2LRUList. To evict from the priority-1 LRUSet, an
* evicting thread picks the (nextPri1LRUList % numLRULists)-th list, and
* then increments nextPri1LRUList. Similarly, to evict from the priority-2
* LRUSet, an evicting thread picks the (nextPri2LRUList % numLRULists)-th
* list, and then increments nextPri2LRUList. This does not have to be done in
* a synchronized way.
*
* A new flag (called hasCachedChildren) is added to each IN to indicate
* whether the IN has cached children or not. This flag is used and maintained
* for upper INs (UINs) only. The need for this flag is explained below.
* Access to this flag is synchronized via the SH/EX node latch.
*
* ---------------------------------------------------------------------------
* LRUSet management: adding/removing/moving INs in/out of/within the LRUSets
* ---------------------------------------------------------------------------
*
* We don't want to track upper IN (UIN) nodes that have cached children.
* There are 2 reasons for this: (a) we cannot evict UINs with cached children
* (the children must be evicted first) and (b) UINs will normally have high
* access rate, and would add a lot of CPU overhead if they were tracked.
*
* The hasCachedChildren flag is used as a quick way to determine whether a
* UIN has cached children or not.
*
* Adding a node to the LRU.
* -------------------------
*
* A IN N is added in an LRUSet via one of the following Evictor methods:
* addBack(IN), addFront(IN), pri2AddBack(IN), or pri2AddFront(IN). The
* first 2 add the node to the priority-1 LRUSet and set its isInPri2LRU flag
* to false. The last 2 add the node to the priority-2 LRUSet and set its
* isInPri2LRU flag to true.
*
* Note: DINs and DBINs are never added to the LRU.
*
* A node N is added to the LRU in the following situations:
*
* 1. N is fetched into memory from the log. Evictor.addBack(N) is called
* inside IN.postfetchInit() (just before N is connected to its parent).
*
* 2. N is a brand new node created during a split, and either N is a BIN or
* N does not get any cached children from its split sibling.
* Evictor.addFront(N) is called if N is a BIN and the cachemode is
* MAKE_COLD or EVICT_BIN. Otherwise, Evictor.addBack(child) is called.
*
* 3. N is a UIN that is being split, and before the split it had cached
* children, but all its cached children have now moved to its newly
* created sibling. Evictor.addBack(N) is called in this case.
*
* 4. N is a UIN that looses its last cached child (either because the child is
* evicted or it is deleted). Evictor.addBack(N) is called inside
* IN.setTarget(), if the target is null, N is a UIN, N's hasCachedChildren
* flag is true, and N after setting the target to null, N has no remaining
* cached children.
*
* 5. N is the 1st BIN in a brand new tree. In this case, Evictor.addBack(N)
* is called inside Tree.findBinForInsert().
*
* 6. N is a node visited during IN.rebuildINList() and N is either a BIN or
* a UIN with no cached children.
*
* 7. An evicting thread T removes N from the LRU, but after T EX-latches N,
* it determines that N is not evictable or should not be evicted, and
* should be put back in the LRU. T puts N back to the LRU using one of
* the above 4 methods (for details, read about the eviction processing
* below), but ONLY IF (a) N is still in the INList, and (b) N is not in
* the LRU already.
*
* Case (b) can happen if N is a UIN and after T removed N from the LRU
* but before T could latch N, another thread T1 added a child to N and
* removed that child. Thus, by item 4 above, T1 adds N back to the LRU.
* Furthermore, since N is now back in the LRU, case (a) can now happen
* as well if another thread can evict N before T latches it.
*
* 8. When the checkpointer (or any other thread/operation) cleans a dirty IN,
* it must move it from the priority-2 LRUSet (if there) to the priority-1
* one. This is done via the Evictor.moveToPri1LRU(N) method: If the
* isInPri2LRU flag of N is true, LRUList.remove(N) is called to remove
* the node from the priority-2 LRUSet. If N was indeed in the priority-2
* LRUSet (i.e., LRUList.remove() returns true), addBack(N) is called to
* put it in the priority-1 LRUSet.
*
* By moving N to the priority-1 LRUSet only after atomically removing it
* from the priority-2 LRUSet and checking that it was indeed there, we
* prevent N from being added into the LRU if N has been or would be removed
* from the LRU by a concurrently running evicting thread.
*
* In cases 2, 3, 4, 5, 7, and 8 N is EX-latched. In case 1, the node is not
* latched, but it is inaccessible by any other threads because it is not
* connected to its parent yet and the parent is EX-latched (but N has already
* been inserted in the INList; can this create any problems ?????). In case
* 6 there is only one thread running. So, in all cases it's ok to set the
* isInPri2LRU flag of the node.
*
* Question: can a thread T try to add a node N, seen as a Java obj instance,
* into the LRU, while N is already there? I believe not, and LRUList addBack()
* and addFront() methods assert that this cannot happen. In cases 1, 2, and 5
* above N is newly created node, so it cannot be in the LRU already. In cases
* 3 and 4, N is a UIN that has cached children, so it cannot be in the LRU.
* In case 6 there is only 1 thread. Finally, in cases 7 and 8, T checks that
* N is not in the LRU before attempting to add it (and the situation cannot
* change between tis check and the insertion into the LRU because N is EX-
* latched).
*
* Question: can a thread T try to add a node N, seen as a logical entity
* represented by its nodeId, into the LRU, while N is already there?
* Specifically, (a) can two Java instances, N1 and N2, of the same node
* N exist in memory at the same time, and (b) while N1 is in the LRU, can
* a thread T try to add N2 in the LRU? The answer to (a) is "yes", and as
* far as I can think, the answer to (b) is "no", but there is no explicit
* check in the code for this. Consider the following sequence of events:
* Initially only N1 is in memory and in the LRU. An evicting thread T1
* removes N1 from the LRU, thread T2 adds N1 in the LRU, thread T3 removes
* N1 from the LRU and actually evicts it, thread T4 fetches N from the log,
* thus creating instance N2 and adding N2 to the LRU, thread T1 finally
* EX-latches N1 and has to decide what to do with it. The check in case
* 7a above makes sure that N1 will not go back to the LRU. In fact the
* same check makes sure that N1 will not be evicted (i.e., logged, if
* dirty). T1 will just skip N1, thus allowing it to be GCed.
*
* Removing a node from the LRU
* ----------------------------
*
* A node is removed from the LRU when it is selected as an eviction target
* by an evicting thread. The thread chooses an LRUList list to evict from
* and calls removeFront() on it. The node is not latched when it is removed
* from the LRU in this case. The evicting thread is going to EX-latch the
* node shortly after the removal. But as explain already earlier, between
* the removal and the latching, another thread may put the node back to the
* LRU, and as a result, another thread may also choose the same node for
* eviction. The node may also be detached from the BTree, or its database
* closed, or deleted.
*
* A node may also be removing from the LRU by a non-evicting thread. This
* is done via the Evictor.remove(IN) method. The method checks the node's
* isInDrtryLRU flag to determine which LRUSet the node belongs to (if any)
* and then calls LRUList.remove(N). The node must be at least SH latched
* when the method is called. The method is a noop if the node is not in the
* LRU. The node may not belong to any LRUList, because it has been selected
* for eviction by another thread (and thus removed from LRU), but the
* evicting thread has not yet latched the node. There are 3 cases (listed
* below) where Evictor.remove(N) is called. In the first two cases
* Evictor.remove(N) is invoked from INList.removeInternal(N). This makes
* sure that N is removed from the LRU whenever it it removed from the
* INList (to guarantee that the nodes in the LRU are always a subset of
* the nodes in the INList).
*
* 1. When a tree branch containing N gets detached from its tree. In this
* case, INList.remove(N) is invoked inside accountForSubtreeRemoval() or
* accountForDeferredWriteSubtreeRemoval().
*
* 2. When the database containing N gets deleted or truncated. In this case,
* INList.iter.remove() is called via DatabaseImpl.startDbExtinction().
*
* 3. N is a UIN with no cached children (hasCachedChildren flag is false)
* and a new child for N is fetched. The call to Evictor.remove(N) is
* done inside IN.setTarget().
*
* Moving a node within the LRU
* ----------------------------
*
* A node N is moved within its containing LRUList (if any) via the Evictor
* moveBack(IN) and moveFront(IN) methods. The methods check the isInPri2LRU
* flag of the node to determine the LRUSet the node belongs to and then move
* the node to the back or to the front of the LRUList. The node will be at
* least SH latched when these methods are called. Normally, the IN will be
* in an LRUList. However, it may not belong to any LRUList, because it has
* been selected for eviction by another thread (and thus removed from LRU),
* but the evicting thread has not yet EX-latched the node. In this case,
* these methods are is a noop. The methods are called in the following
* situations:
*
* 1. N is latched with cachemode DEFAULT, KEEP_HOT, or EVICT_LN and N is a
* BIN or a UIN with no cached children (the hasCachedChildren flag is
* used to check if the UIN has cached children, so we don't need to
* iterate over all of the node's child entries). In this case,
* Evictor.moveBack(N) .
*
* 2. N is latched with cachemode MAKE_COLD or EVICT_BIN and N is a BIN.
* In this case, Evictor.moveFront(N) is called.
*
* -------------------
* Eviction Processing
* -------------------
*
* A thread can initiate eviction by invoking the Evictor.doEviction() method.
* This method implements an "eviction run". An eviction run consists of a
* number of "eviction passes", where each pass is given as input a maximum
* number of bytes to evict. An eviction pass is implemented by the
* Evictor.evictBatch() method.
*
* Inside Evictor.evictBatch(), an evicting thread T:
*
* 1. Picks the priority-1 LRUset initially as the "current" LRUSet to be
* processed,
*
* 2. Initializes the max number of nodes to be processed per LRUSet to the
* current size of the priority-1 LRUSet,
*
* 3. Executes the following loop:
*
* 3.1. Picks a non-empty LRUList from the current LRUSet in a round-robin
* fashion, as explained earlier, and invokes LRUList.removeFront() to
* remove the node N at the front of the list. N becomes the current
* eviction target.
*
* 3.2. If the DB node N belongs to has been deleted or closed, skips this node,
* i.e., leaves N outside the LRU and goes to 3.4.
*
* 3.3. Calls ProcessTarget(N) (see below)
*
* 3.4. If the current LRUset is the priority-1 one and the number of target nodes
* processed reaches the max number allowed, the priority-2 LRUSet becomes
* the current one, the max number of nodes to be processed per LRUSet is
* set to the current size of the priority-2 LRUSet, and the number of
* nodes processed is reset to 0.
*
* 3.5. Breaks the loop if the max number of bytes to evict during this pass
* has been reached, or memConsumption is less than (maxMemory - M) (where
* M is a config param), or the number of nodes that have been processed
* in the current LRUSet reaches the max allowed.
*
* --------------------------
* The processTarget() method
* --------------------------
*
* This method is called after a node N has been selected for eviction (and as
* result, removed from the LRU). The method EX-latches N and determines
* whether it can/should really be evicted, and if not what is the appropriate
* action to be taken by the evicting thread. Before returning, the method
* unlatches N. Finally, it returns the number of bytes evicted (if any).
*
* If a decision is taken to evict N or mutate it to a BINDelta, N must first
* be unlatched and its parent must be searched within the tree. During this
* search, many things can happen to the unlatched N, and as a result, after
* the parent is found and the N is relatched, processTarget() calls itself
* recursively to re-consider all the possible actions for N.
*
* Let T be an evicting thread running processTarget() to determine what to do
* with a target node N. The following is the list of possible outcomes:
*
* 1. SKIP - Do nothing with N if:
* (a) N is in the LRU. This can happen if N is a UIN and while it is
* unlatched by T, other threads fetch one or more of N's children,
* but then all of N's children are removed again, thus causing N to
* be put back to the LRU.
* (b) N is not in the INList. Given than N can be put back to the LRU while
* it is unlatched by T, it can also be selected as an eviction target
* by another thread and actually be evicted.
* (c) N is a UIN with cached children. N could have acquired children
* after the evicting thread removed it from the LRU, but before the
* evicting thread could EX-latch it.
* (d) N is the root of the DB naming tree or the DBmapping tree.
* (e) N is dirty, but the DB is read-only.
* (f) N's environment used a shared cache and the environment has been
* closed or invalidated.
* (g) If a decision was taken to evict od mutate N, but the tree search
* (using N's keyId) to find N's parent, failed to find the parent, or
* N itself. This can happen if during the search, N was evicted by
* another thread, or a branch containing N was completely removed
* from the tree.
*
* 2. PUT BACK - Put N to the back of the LRUSet it last belonged to, if:
* (a) It is a BIN that was last accessed with KEEP_HOT cache mode.
* (b) N has an entry with a NULL LSN and a null target.
*
* 3. PARTIAL EVICT - perform partial eviction on N, if none of the cases
* listed above is true. Currently, partial eviction applies to BINs only
* and involves the eviction (stripping) of evictable LNs. If a cached LN
* is not evictable, the whole BIN is not evictable as well. Currently,
* only MapLNs may be non-evictable (see MapLN.isEvictable()).
*
* After partial eviction is performed the following outcomes are possible:
*
* 4. STRIPPED PUT BACK - Put N to the back of the LRUSet it last belonged to,
* if partial eviction did evict any bytes, and N is not a BIN in EVICT_BIN
* or MAKE_COLD cache mode.
*
* 5. PUT BACK - Put N to the back of the LRUSet it last belonged to, if
* no bytes were stripped, but partial eviction determined that N is not
* evictable.
*
* 6. MUTATE - Mutate N to a BINDelta, if none of the above apply and N is a
* BIN that can be mutated.
*
* 7. MOVE DIRTY TO PRI-2 LRU - Move N to the front of the priority-2 LRUSet,
* if none of the above apply and N is a dirty node that last belonged to
* the priority-1 LRUSet, and a dirty LRUSet is used (meaning that no
* off-heap cache is configured).
*
* 8. MOVE LEVEL-2 TO PRI-2 LRU - Move N to the front of the priority-2 LRUSet,
* if none of the above apply and N is a level-2 node with off-heap BINs
* that last belonged to the priority-1 LRUSet.
*
* 9. EVICT - Evict N is none of the above apply.
*
* -------
* TODO:
* -------
*
* 1. Decide what to do about assertions (keep, remove, convert to JE
* exceptions, convert to DEBUG-only expensive checks).
*
*/
public class Evictor implements EnvConfigObserver {
/*
* If new eviction source enums are added, a new stat is created, and
* EnvironmentStats must be updated to add a getter method.
*
* CRITICAL eviction is called by operations executed app or daemon
* threads which detect that the cache has reached its limits
* CACHE_MODE eviction is called by operations that use a specific
* Cursor.
* EVICTORThread is the eviction pool
* MANUAL is the call to Environment.evictMemory, called by recovery or
* application code.
*/
public enum EvictionSource {
/* Using ordinal for array values! */
EVICTORTHREAD {
String getName() {
return N_BYTES_EVICTED_EVICTORTHREAD_NAME;
}
String getDesc() {
return N_BYTES_EVICTED_EVICTORTHREAD_DESC;
}
},
MANUAL {
String getName() {
return N_BYTES_EVICTED_MANUAL_NAME;
}
String getDesc() {
return N_BYTES_EVICTED_MANUAL_DESC;
}
},
CRITICAL {
String getName() {
return N_BYTES_EVICTED_CRITICAL_NAME;
}
String getDesc() {
return N_BYTES_EVICTED_CRITICAL_DESC;
}
},
CACHEMODE {
String getName() {
return N_BYTES_EVICTED_CACHEMODE_NAME;
}
String getDesc() {
return N_BYTES_EVICTED_CACHEMODE_DESC;
}
},
DAEMON {
String getName() {
return N_BYTES_EVICTED_DAEMON_NAME;
}
String getDesc() {
return N_BYTES_EVICTED_DAEMON_DESC;
}
};
abstract String getName();
abstract String getDesc();
public StatDefinition getNumBytesEvictedStatDef() {
return new StatDefinition(getName(), getDesc());
}
}
/*
* The purpose of EvictionDebugStats is to capture the stats of a single
* eviction run (i.e., an execution of the Evictor.doEviction() method by
* a single thread). An instance of EvictionDebugStats is created at the
* start of doEviction() and is passed around to the methods called from
* doEviction(). At the end of doEviction(), the EvictionDebugStats
* instance can be printed out (for debugging), or (TODO) the captured
* stats can be loaded to the global Evictor.stats.
*/
static class EvictionDebugStats {
boolean inPri1LRU;
boolean withParent;
long pri1Size;
long pri2Size;
int numSelectedPri1;
int numSelectedPri2;
int numPutBackPri1;
int numPutBackPri2;
int numBINsStripped1Pri1;
int numBINsStripped2Pri1;
int numBINsStripped1Pri2;
int numBINsStripped2Pri2;
int numBINsMutatedPri1;
int numBINsMutatedPri2;
int numUINsMoved1;
int numUINsMoved2;
int numBINsMoved1;
int numBINsMoved2;
int numUINsEvictedPri1;
int numUINsEvictedPri2;
int numBINsEvictedPri1;
int numBINsEvictedPri2;
void reset() {
inPri1LRU = true;
withParent = false;
pri1Size = 0;
pri2Size = 0;
numSelectedPri1 = 0;
numSelectedPri2 = 0;
numPutBackPri1 = 0;
numPutBackPri2 = 0;
numBINsStripped1Pri1 = 0;
numBINsStripped2Pri1 = 0;
numBINsStripped1Pri2 = 0;
numBINsStripped2Pri2 = 0;
numBINsMutatedPri1 = 0;
numBINsMutatedPri2 = 0;
numUINsMoved1 = 0;
numUINsMoved2 = 0;
numBINsMoved1 = 0;
numBINsMoved2 = 0;
numUINsEvictedPri1 = 0;
numUINsEvictedPri2 = 0;
numBINsEvictedPri1 = 0;
numBINsEvictedPri2 = 0;
}
void incNumSelected() {
if (inPri1LRU) {
numSelectedPri1++;
} else {
numSelectedPri2++;
}
}
void incNumPutBack() {
if (inPri1LRU) {
numPutBackPri1++;
} else {
numPutBackPri2++;
}
}
void incNumStripped() {
if (inPri1LRU) {
if (withParent) {
numBINsStripped2Pri1++;
} else {
numBINsStripped1Pri1++;
}
} else {
if (withParent) {
numBINsStripped2Pri2++;
} else {
numBINsStripped1Pri2++;
}
}
}
void incNumMutated() {
if (inPri1LRU) {
numBINsMutatedPri1++;
} else {
numBINsMutatedPri2++;
}
}
void incNumMoved(boolean isBIN) {
if (withParent) {
if (isBIN) {
numBINsMoved2++;
} else {
numUINsMoved2++;
}
} else {
if (isBIN) {
numBINsMoved1++;
} else {
numUINsMoved1++;
}
}
}
void incNumEvicted(boolean isBIN) {
if (inPri1LRU) {
if (isBIN) {
numBINsEvictedPri1++;
} else {
numUINsEvictedPri1++;
}
} else {
if (isBIN) {
numBINsEvictedPri2++;
} else {
numUINsEvictedPri2++;
}
}
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Eviction stats PRI1: size = ");
sb.append(pri1Size);
sb.append("\n");
sb.append("selected = ");
sb.append(numSelectedPri1);
sb.append(" | ");
sb.append("put back = ");
sb.append(numPutBackPri1);
sb.append(" | ");
sb.append("stripped = ");
sb.append(numBINsStripped1Pri1);
sb.append("/");
sb.append(numBINsStripped2Pri1);
sb.append(" | ");
sb.append("mutated = ");
sb.append(numBINsMutatedPri1);
sb.append(" | ");
sb.append("moved = ");
sb.append(numBINsMoved1);
sb.append("/");
sb.append(numBINsMoved2);
sb.append(" - ");
sb.append(numUINsMoved1);
sb.append("/");
sb.append(numUINsMoved2);
sb.append(" | ");
sb.append("evicted = ");
sb.append(numBINsEvictedPri1);
sb.append(" - ");
sb.append(numUINsEvictedPri1);
sb.append("\n");
sb.append("Eviction stats PRI2: size = ");
sb.append(pri2Size);
sb.append("\n");
sb.append("selected = ");
sb.append(numSelectedPri2);
sb.append(" | ");
sb.append("put back = ");
sb.append(numPutBackPri2);
sb.append(" | ");
sb.append("stripped = ");
sb.append(numBINsStripped1Pri2);
sb.append("/");
sb.append(numBINsStripped2Pri2);
sb.append(" | ");
sb.append("mutated = ");
sb.append(numBINsMutatedPri2);
sb.append(" | ");
sb.append("evicted = ");
sb.append(numBINsEvictedPri2);
sb.append(" - ");
sb.append(numUINsEvictedPri2);
sb.append("\n");
return sb.toString();
}
}
/*
* The purpose of LRUDebugStats is to capture stats on the current state
* of an LRUSet. This is done via a call to LRUEvictor.getPri1LRUStats(),
* or LRUEvictor.getPri2LRUStats(). For now at least, these methods are
* meant to be used for debugging and unit testing only.
*/
static class LRUDebugStats {
int size;
int dirtySize;
int numBINs;
int numDirtyBINs;
int numStrippedBINs;
int numDirtyStrippedBINs;
void reset() {
size = 0;
dirtySize = 0;
numBINs = 0;
numDirtyBINs = 0;
numStrippedBINs = 0;
numDirtyStrippedBINs = 0;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Clean/Dirty INs = ");
sb.append(size - dirtySize);
sb.append("/");
sb.append(dirtySize);
sb.append(" BINs = ");
sb.append(numBINs - numDirtyBINs);
sb.append("/");
sb.append(numDirtyBINs);
sb.append(" Stripped BINs = ");
sb.append(numStrippedBINs - numDirtyStrippedBINs);
sb.append("/");
sb.append(numDirtyStrippedBINs);
return sb.toString();
}
}
/*
* LRUList implementation
*/
static class LRUList {
private static final boolean doExpensiveCheck = false;
private final int id;
private int size = 0;
private IN front = null;
private IN back = null;
LRUList(int id) {
this.id = id;
}
synchronized void addBack(IN node) {
/* Make sure node is not in any LRUlist already */
if (node.getNextLRUNode() != null ||
node.getPrevLRUNode() != null) {
throw EnvironmentFailureException.unexpectedState(
node.getEnv(),
Thread.currentThread().getId() + "-" +
Thread.currentThread().getName() +
"-" + node.getEnv().getName() +
"Attempting to add node " + node.getNodeId() +
" in the LRU, but node is already in the LRU.");
}
assert(!node.isDIN() && !node.isDBIN());
node.setNextLRUNode(node);
if (back != null) {
node.setPrevLRUNode(back);
back.setNextLRUNode(node);
} else {
assert(front == null);
node.setPrevLRUNode(node);
}
back = node;
if (front == null) {
front = back;
}
++size;
}
synchronized void addFront(IN node) {
/* Make sure node is not in any LRUlist already */
if (node.getNextLRUNode() != null ||
node.getPrevLRUNode() != null) {
throw EnvironmentFailureException.unexpectedState(
node.getEnv(),
Thread.currentThread().getId() + "-" +
Thread.currentThread().getName() +
"-" + node.getEnv().getName() +
"Attempting to add node " + node.getNodeId() +
" in the LRU, but node is already in the LRU.");
}
assert(!node.isDIN() && !node.isDBIN());
node.setPrevLRUNode(node);
if (front != null) {
node.setNextLRUNode(front);
front.setPrevLRUNode(node);
} else {
assert(back == null);
node.setNextLRUNode(node);
}
front = node;
if (back == null) {
back = front;
}
++size;
}
synchronized void moveBack(IN node) {
/* If the node is not in the list, don't do anything */
if (node.getNextLRUNode() == null) {
assert(node.getPrevLRUNode() == null);
return;
}
if (doExpensiveCheck && !contains2(node)) {
System.out.println("LRUList.moveBack(): list " + id +
"does not contain node " +
node.getNodeId() +
" Thread: " +
Thread.currentThread().getId() + "-" +
Thread.currentThread().getName() +
" isBIN: " + node.isBIN() +
" inPri2LRU: " + node.isInPri2LRU());
assert(false);
}
if (node.getNextLRUNode() == node) {
/* The node is aready at the back */
assert(back == node);
assert(node.getPrevLRUNode().getNextLRUNode() == node);
} else {
assert(front != back);
assert(size > 1);
if (node.getPrevLRUNode() == node) {
/* the node is at the front */
assert(front == node);
assert(node.getNextLRUNode().getPrevLRUNode() == node);
front = node.getNextLRUNode();
front.setPrevLRUNode(front);
} else {
/* the node is in the "middle" */
assert(front != node && back != node);
assert(node.getPrevLRUNode().getNextLRUNode() == node);
assert(node.getNextLRUNode().getPrevLRUNode() == node);
node.getPrevLRUNode().setNextLRUNode(node.getNextLRUNode());
node.getNextLRUNode().setPrevLRUNode(node.getPrevLRUNode());
}
node.setNextLRUNode(node);
node.setPrevLRUNode(back);
back.setNextLRUNode(node);
back = node;
}
}
synchronized void moveFront(IN node) {
/* If the node is not in the list, don't do anything */
if (node.getNextLRUNode() == null) {
assert(node.getPrevLRUNode() == null);
return;
}
if (doExpensiveCheck && !contains2(node)) {
System.out.println("LRUList.moveFront(): list " + id +
"does not contain node " +
node.getNodeId() +
" Thread: " +
Thread.currentThread().getId() + "-" +
Thread.currentThread().getName() +
" isBIN: " + node.isBIN() +
" inPri2LRU: " + node.isInPri2LRU());
assert(false);
}
if (node.getPrevLRUNode() == node) {
/* the node is aready at the front */
assert(front == node);
assert(node.getNextLRUNode().getPrevLRUNode() == node);
} else {
assert(front != back);
assert(size > 1);
if (node.getNextLRUNode() == node) {
/* the node is at the back */
assert(back == node);
assert(node.getPrevLRUNode().getNextLRUNode() == node);
back = node.getPrevLRUNode();
back.setNextLRUNode(back);
} else {
/* the node is in the "middle" */
assert(front != node && back != node);
assert(node.getPrevLRUNode().getNextLRUNode() == node);
assert(node.getNextLRUNode().getPrevLRUNode() == node);
node.getPrevLRUNode().setNextLRUNode(node.getNextLRUNode());
node.getNextLRUNode().setPrevLRUNode(node.getPrevLRUNode());
}
node.setPrevLRUNode(node);
node.setNextLRUNode(front);
front.setPrevLRUNode(node);
front = node;
}
}
synchronized IN removeFront() {
if (front == null) {
assert(back == null);
return null;
}
IN res = front;
if (front == back) {
assert(front.getNextLRUNode() == front);
assert(front.getPrevLRUNode() == front);
assert(size == 1);
front = null;
back = null;
} else {
assert(size > 1);
front = front.getNextLRUNode();
front.setPrevLRUNode(front);
}
res.setNextLRUNode(null);
res.setPrevLRUNode(null);
--size;
return res;
}
synchronized boolean remove(IN node) {
/* If the node is not in the list, don't do anything */
if (node.getNextLRUNode() == null) {
assert(node.getPrevLRUNode() == null);
return false;
}
assert(node.getPrevLRUNode() != null);
if (doExpensiveCheck && !contains2(node)) {
System.out.println("LRUList.remove(): list " + id +
"does not contain node " +
node.getNodeId() +
" Thread: " +
Thread.currentThread().getId() + "-" +
Thread.currentThread().getName() +
" isBIN: " + node.isBIN() +
" inPri2LRU: " + node.isInPri2LRU());
assert(false);
}
if (front == back) {
assert(size == 1);
assert(front == node);
assert(front.getNextLRUNode() == front);
assert(front.getPrevLRUNode() == front);
front = null;
back = null;
} else if (node.getPrevLRUNode() == node) {
/* node is at the front */
assert(front == node);
assert(node.getNextLRUNode().getPrevLRUNode() == node);
front = node.getNextLRUNode();
front.setPrevLRUNode(front);
} else if (node.getNextLRUNode() == node) {
/* the node is at the back */
assert(back == node);
assert(node.getPrevLRUNode().getNextLRUNode() == node);
back = node.getPrevLRUNode();
back.setNextLRUNode(back);
} else {
/* the node is in the "middle" */
assert(size > 2);
assert(front != back);
assert(front != node && back != node);
assert(node.getPrevLRUNode().getNextLRUNode() == node);
assert(node.getNextLRUNode().getPrevLRUNode() == node);
node.getPrevLRUNode().setNextLRUNode(node.getNextLRUNode());
node.getNextLRUNode().setPrevLRUNode(node.getPrevLRUNode());
}
node.setNextLRUNode(null);
node.setPrevLRUNode(null);
--size;
return true;
}
synchronized void removeINsForEnv(EnvironmentImpl env) {
if (front == null) {
assert(back == null);
return;
}
IN node = front;
while (true) {
IN nextNode = node.getNextLRUNode();
IN prevNode = node.getPrevLRUNode();
if (node.getDatabase().getEnv() == env) {
node.setNextLRUNode(null);
node.setPrevLRUNode(null);
if (front == back) {
assert(size == 1);
assert(front == node);
assert(nextNode == front);
assert(prevNode == front);
front = null;
back = null;
--size;
break;
} else if (prevNode == node) {
/* node is at the front */
assert(size > 1);
assert(front == node);
assert(nextNode.getPrevLRUNode() == node);
front = nextNode;
front.setPrevLRUNode(front);
node = front;
--size;
} else if (nextNode == node) {
/* the node is at the back */
assert(size > 1);
assert(back == node);
assert(prevNode.getNextLRUNode() == node);
back = prevNode;
back.setNextLRUNode(back);
--size;
break;
} else {
/* the node is in the "middle" */
assert(size > 2);
assert(front != back);
assert(front != node && back != node);
assert(prevNode.getNextLRUNode() == node);
assert(nextNode.getPrevLRUNode() == node);
prevNode.setNextLRUNode(nextNode);
nextNode.setPrevLRUNode(prevNode);
node = nextNode;
--size;
}
} else if (nextNode == node) {
break;
} else {
node = nextNode;
}
}
}
synchronized boolean contains(IN node) {
return (node.getNextLRUNode() != null);
}
private boolean contains2(IN node) {
if (front == null) {
assert(back == null);
return false;
}
IN curr = front;
while (true) {
if (curr == node) {
return true;
}
if (curr.getNextLRUNode() == curr) {
break;
}
curr = curr.getNextLRUNode();
}
return false;
}
synchronized List copyList() {
if (front == null) {
assert(back == null);
return Collections.emptyList();
}
List list = new ArrayList<>();
IN curr = front;
while (true) {
list.add(curr);
if (curr.getNextLRUNode() == curr) {
break;
}
curr = curr.getNextLRUNode();
}
return list;
}
int getSize() {
return size;
}
synchronized void getStats(EnvironmentImpl env, LRUDebugStats stats) {
if (front == null) {
assert(back == null);
return;
}
IN curr = front;
while (true) {
if (env == null || curr.getEnv() == env) {
stats.size++;
if (curr.getDirty()) {
stats.dirtySize++;
}
if (curr.isBIN()) {
stats.numBINs++;
if (curr.getDirty()) {
stats.numDirtyBINs++;
}
if (!curr.hasCachedChildren()) {
stats.numStrippedBINs++;
if (curr.getDirty()) {
stats.numDirtyStrippedBINs++;
}
}
}
}
if (curr.getNextLRUNode() == curr) {
break;
}
curr = curr.getNextLRUNode();
}
}
}
/**
* EnvInfo stores info related to the environments that share this evictor.
*/
private static class EnvInfo {
EnvironmentImpl env;
INList ins;
}
/* Prevent endless eviction loops under extreme resource constraints. */
private static final int MAX_BATCHES_PER_RUN = 100;
private static final boolean traceUINs = false;
private static final boolean traceBINs = false;
private static final Level traceLevel = Level.INFO;
/* LRU-TODO: remove */
private static final boolean collectEvictionDebugStats = false;
/**
* Number of LRULists per LRUSet. This is a configuration parameter.
*
* In general, using only one LRUList may create a synchronization
* bottleneck, because all LRUList methods are synchronized and are
* invoked with high frequency from multiple thread. To alleviate
* this bottleneck, we need the option to break a single LRUList
* into multiple ones comprising an "LRUSet" (even though this
* reduces the quality of the LRU approximation).
*/
private final int numLRULists;
/*
* This is true when an off-heap cache is in use. If true, then the
* priority-2 LRUSet is always used for level 2 INs, and useDirtyLRUSet
* and mutateBins are both set to false.
*/
private final boolean useOffHeapCache;
/**
* Whether to use the priority-2 LRUSet for dirty nodes or not.
*
* When useOffHeapCache is true, useDirtyLRUSet is always false. When
* useOffHeapCache is false, useDirtyLRUSet is set via a configuration
* parameter.
*/
private final boolean useDirtyLRUSet;
/*
* Whether to allow deltas when logging a dirty BIN that is being evicted.
* This is a configuration parameter.
*/
private final boolean allowBinDeltas;
/*
* Whether to mutate BINs to BIN deltas rather than evicting the full node.
*
* When useOffHeapCache is true, mutateBins is always false. When
* useOffHeapCache is false, mutateBins is set via a configuration
* parameter.
*/
private final boolean mutateBins;
/*
* Access count after which we clear the DatabaseImpl cache.
* This is a configuration parameter.
*/
private int dbCacheClearCount;
/*
* This is a configuration parameter. If true, eviction is done by a pool
* of evictor threads, as well as being done inline by application threads.
* Note: runEvictorThreads is needed as a distinct flag, rather than
* setting maxThreads to 0, because the ThreadPoolExecutor does not permit
* maxThreads to be 0.
*/
private boolean runEvictorThreads;
/* This is a configuration parameter. */
private int terminateMillis;
/* The thread pool used to manage the background evictor threads. */
private final ThreadPoolExecutor evictionPool;
/* Flag to help shutdown launched eviction tasks. */
private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
private int maxPoolThreads;
private final AtomicInteger activePoolThreads = new AtomicInteger(0);
/*
* Whether this evictor (and the memory cache) is shared by multiple
* environments
*/
private final boolean isShared;
/*
* In case of multiple environments sharing a cache (and this Evictor),
* firstEnvImpl references the 1st EnvironmentImpl to be created with
* the shared cache.
*/
private final EnvironmentImpl firstEnvImpl;
private final List envInfos;
/**
* This is used only when this evictor is shared by multiple envs. It
* "points" to the next env to perform "special eviction" in.
*/
private int specialEvictionIndex = 0;
/*
*
*/
private final Arbiter arbiter;
/**
* With an off-heap cache configured:
* pri1LRUSet contains nodes of any type and level. A freshly cached node
* goes into this LRUSet. A level-2 node will go to the pri2LRUSet if it is
* selected for eviction from the pri1LRUSet and it contains off-heap BINs.
* A node will move from the pri2LRUSet to the pri1LRUSet when its last
* off-heap BIN is evicted from the off-heap cache.
*
* Without an off-heap cache configured:
* pri1LRUSet contains both clean and dirty nodes. A freshly cached node
* goes into this LRUSet. A dirty node will go to the pri2LRUSet if it is
* selected for eviction from the pri1LRUSet. A node will move from the
* pri2LRUSet to the pri1LRUSet when it gets logged (i.e., cleaned) by
* the checkpointer.
*/
private final LRUList[] pri1LRUSet;
private final LRUList[] pri2LRUSet;
/**
* nextPri1LRUList is used to implement the traversal of the lists in
* the pri1LRUSet by one or more evicting threads. Such a thread will
* select for eviction the front node from the (nextPri1LRUList %
* numLRULists)-th list, and then increment nextPri1LRUList.
* nextPri2LRUList plays the same role for the priority-2 LRUSet.
*/
private int nextPri1LRUList = 0;
private int nextPri2LRUList = 0;
/*
* The evictor is disabled during the 1st phase of recovery. The
* RecoveryManager enables the evictor after it finishes its 1st
* phase.
*/
private boolean isEnabled = false;
/* Eviction calls cannot be recursive. */
private ReentrancyGuard reentrancyGuard;
private final Logger logger;
/*
* Stats
*/
private final StatGroup stats;
/*
* Number of eviction tasks that were submitted to the background evictor
* pool, but were refused because all eviction threads were busy.
*/
private final AtomicLongStat nThreadUnavailable;
/* Number of evictBatch() invocations. */
private final LongStat nEvictionRuns;
/*
* Number of nodes selected as eviction targets. An eviction target may
* actually be evicted, or skipped, or put back to the LRU, potentially
* after partial eviction or BIN-delta mutation is done on it.
*/
private final LongStat nNodesTargeted;
/* Number of nodes evicted. */
private final LongStat nNodesEvicted;
/* Number of closed database root nodes evicted. */
private final LongStat nRootNodesEvicted;
/* Number of dirty nodes logged and evicted. */
private final LongStat nDirtyNodesEvicted;
/* Number of LNs evicted. */
private final LongStat nLNsEvicted;
/* Number of BINs stripped. */
private final LongStat nNodesStripped;
/* Number of BINs mutated to deltas. */
private final LongStat nNodesMutated;
/* Number of target nodes put back to the LRU w/o any other action taken */
private final LongStat nNodesPutBack;
/* Number of target nodes skipped. */
private final LongStat nNodesSkipped;
/* Number of target nodes moved to the priority-2 LRU */
private final LongStat nNodesMovedToPri2LRU;
/* Number of bytes evicted per eviction source. */
private final AtomicLongStat[] numBytesEvicted;
/*
* Tree related cache hit/miss stats. A subset of the cache misses recorded
* by the log manager, in that these only record tree node hits and misses.
* Recorded by IN.fetchIN and IN.fetchLN, but grouped with evictor stats.
* Use AtomicLongStat for multithreading safety.
*/
private final AtomicLongStat nLNFetch;
private final AtomicLongStat nLNFetchMiss;
/*
* Number of times IN.fetchIN() or IN.fetchINWithNoLatch() was called
* to fetch a UIN.
*/
private final AtomicLongStat nUpperINFetch;
/*
* Number of times IN.fetchIN() or IN.fetchINWithNoLatch() was called
* to fetch a UIN and that UIN was not already cached.
*/
private final AtomicLongStat nUpperINFetchMiss;
/*
* Number of times IN.fetchIN() or IN.fetchINWithNoLatch() was called
* to fetch a BIN.
*/
private final AtomicLongStat nBINFetch;
/*
* Number of times IN.fetchIN() or IN.fetchINWithNoLatch() was called
* to fetch a BIN and that BIN was not already cached.
*/
private final AtomicLongStat nBINFetchMiss;
/*
* Number of times IN.fetchIN() or IN.fetchINWithNoLatch() was called
* to fetch a BIN, that BIN was not already cached, and a BIN-delta was
* fetched from disk.
*/
private final AtomicLongStat nBINDeltaFetchMiss;
private final FloatStat binFetchMissRatio;
/*
* Number of calls to BIN.mutateToFullBIN()
*/
private final AtomicLongStat nFullBINMiss;
/*
* Number of blind operations on BIN deltas
*/
private final AtomicLongStat nBinDeltaBlindOps;
/* Stats for IN compact array representations currently in cache. */
private final AtomicLong nINSparseTarget;
private final AtomicLong nINNoTarget;
private final AtomicLong nINCompactKey;
/* Number of envs sharing the cache. */
private final IntStat sharedCacheEnvs;
/* Debugging and unit test support. */
/*
* Number of consecutive "no-eviction" events (i.e. when evictBatch()
* returns 0). It is incremented at each "no-eviction" event and reset
* to 0 when eviction does occur. It is used to determine whether to
* log a WARNING for a "no-eviction" event: only 1 warning is logged
* per sequence of consecutive "no-eviction" events (to avoid flooding
* the logger files).
*/
private int numNoEvictionEvents = 0;
private TestHook
© 2015 - 2024 Weber Informatics LLC | Privacy Policy