Package org.voltdb.planner

Source Code of org.voltdb.planner.ActivePlanRepository$FragInfo

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.voltdb.planner;

import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.voltdb.jni.ExecutionEngine;
import org.voltdb.jni.Sha1Wrapper;

public abstract class ActivePlanRepository {

    /// A plan fragment entry in the cache.
    private static class FragInfo {
        final Sha1Wrapper hash;
        final long fragId;
        final byte[] plan;
        int refCount;
        /// The ticker value current when this fragment was last (dis)used.
        /// A new FragInfo or any other not in the LRU map because it is being referenced has value 0.
        /// A non-zero value is either the fragment's current key in the LRU map OR its intended/future
        /// key, if it has been lazily updated after the fragment was reused.
        long lastUse;
        /// The statement text for this fragment.  For ad hoc queries this may be null, since
        /// there is no single statement text---ad hoc queries that differ only by their constants
        /// reuse the same plan.
        String stmtText;

        FragInfo(Sha1Wrapper key, byte[] plan, long nextId, String stmtText)
        {
            this.hash = key;
            this.plan = plan;
            this.fragId = nextId;
            this.refCount = 0;
            this.lastUse = 0;
            this.stmtText = stmtText;
        }
    }

    private static HashMap<Sha1Wrapper, FragInfo> m_plansByHash = new HashMap<Sha1Wrapper, FragInfo>();
    private static HashMap<Long, FragInfo> m_plansById = new HashMap<Long, FragInfo>();
    private static TreeMap<Long, FragInfo> m_plansLRU = new TreeMap<Long, FragInfo>();
    /// A ticker that provides temporary ids for all cached fragments, for communicating with the EE.
    private static final long INITIAL_FRAG_ID = 5000;
    private static long m_nextFragId = INITIAL_FRAG_ID;
    /// A ticker that allows the sequencing of all fragment uses, providing a key to the LRU map.
    private static long m_nextFragUse = 1;

    /**
     * Get the site-local fragment id for a given plan identified by 20-byte sha-1 hash
     */
    public static long getFragmentIdForPlanHash(byte[] planHash) {
        Sha1Wrapper key = new Sha1Wrapper(planHash);
        FragInfo frag = null;
        synchronized (FragInfo.class) {
            frag = m_plansByHash.get(key);
        }
        assert(frag != null);
        return frag.fragId;
    }

    /**
     * Get the statement text for the fragment identified by its hash
     */
    public static String getStmtTextForPlanHash(byte[] planHash) {
        Sha1Wrapper key = new Sha1Wrapper(planHash);
        FragInfo frag = null;
        synchronized (FragInfo.class) {
            frag = m_plansByHash.get(key);
        }
        assert(frag != null);
        assert(frag.stmtText != null);
        return frag.stmtText;
    }

    /**
     * Get the site-local fragment id for a given plan identified by 20-byte sha-1 hash
     * If the plan isn't known to this SPC, load it up. Otherwise addref it.
     */
    public static long loadOrAddRefPlanFragment(byte[] planHash, byte[] plan, String stmtText) {
        Sha1Wrapper key = new Sha1Wrapper(planHash);
        synchronized (FragInfo.class) {
            FragInfo frag = m_plansByHash.get(key);
            if (frag == null) {
                frag = new FragInfo(key, plan, m_nextFragId++, stmtText);
                m_plansByHash.put(frag.hash, frag);
                m_plansById.put(frag.fragId, frag);
                if (m_plansById.size() > ExecutionEngine.EE_PLAN_CACHE_SIZE) {
                    evictLRUfragment();
                }
            }
            // The fragment MAY be in the LRU map.
            // An incremented refCount is a lazy way to keep it safe from eviction
            // without having to update the map.
            // This optimizes for popular fragments in a small or stable cache that may be reused
            // many times before the eviction process needs to take any notice.
            frag.refCount++;
            return frag.fragId;
        }
    }

    private static void evictLRUfragment() {
        /// Evict the least recently used fragment (if any are currently unused).
        /// Along the way, update any obsolete entries that were left
        /// by the laziness of the fragment state changes (fragment reuse).
        /// In the rare case of a cache bloated beyond its usual limit,
        /// keep evicting as needed and as entries are available until the bloat is gone.

        while ( ! m_plansLRU.isEmpty()) {
            // Remove the earliest entry.
            Entry<Long, FragInfo> lru = m_plansLRU.pollFirstEntry();
            FragInfo frag = lru.getValue();
            if (frag.refCount > 0) {
                // The fragment is being re-used, it is no longer an eviction candidate.
                // It is only in the map due to the laziness in loadOrAddRefPlanFragment.
                // It will be re-considered (at a later key) once it is no longer referenced.
                // Resetting its lastUse to 0, here, restores it to a state identical to that
                // of a new fragment.
                // It eventually causes decrefPlanFragmentById to put it back in the map
                // at its then up-to-date key.
                // This makes it safe to keep out of the LRU map for now.
                // See the comment in decrefPlanFragmentById and the one in the next code block.
                frag.lastUse = 0;
            }
            else if (lru.getKey() != frag.lastUse) {
                // The fragment is not in use but has been re-used more recently than the key reflects.
                // This is a result of the laziness in decrefPlanFragmentById.
                // Correct the entry's key in the LRU map to reflect its last use.
                // This may STILL be the least recently used entry.
                // If so, it will be picked off in a later iteration of this loop;
                // its key will now match its lastUse value.
                m_plansLRU.put(frag.lastUse, frag);
            }
            else {
                // Found and removed the actual up-to-date least recently used entry from the LRU map.
                // Remove the entry from the other collections.
                m_plansById.remove(frag.fragId);
                m_plansByHash.remove(frag.hash);
                // Normally, one eviction for each new fragment is enough to restore order.
                // BUT, if a prior call ever failed to find an unused fragment in the cache,
                // the cache may have grown beyond its normal size. In that rare case,
                // one eviction is not enough to reduce the cache to the desired size,
                // so take another bite at the apple.
                // Otherwise, trading exactly one evicted fragment for each new fragment
                // would never reduce the cache.
                if (m_plansById.size() > ExecutionEngine.EE_PLAN_CACHE_SIZE) {
                     continue;
                }
                return;
            }
        }
        // Strange. All FragInfo entries appear to be in use. There's nothing to evict.
        // Let the cache bloat a little and try again later after the next new fragment.
    }

    /**
     * Decref the plan associated with this site-local fragment id. If the refcount
     * goes to 0, the plan may be removed (depending on caching policy).
     */
    public static void decrefPlanFragmentById(long fragmentId) {
        // skip dummy/invalid fragment ids
        if (fragmentId <= 0) return;

        FragInfo frag = null;
        synchronized (FragInfo.class) {
            frag = m_plansById.get(fragmentId);
            // The assert that used to be here would fail in TestAdHocQueries when it
            // re-initialized the RealVoltDB, clearing the m_plansById before
            // all SQLStmts were finalized. Maybe that's just a "test bug" that would be
            // better fixed with some kind of test-only cleanup hook?
            // OR It's possible that this early return is covering for a minor bug.
            // Maybe SQLStmt.finalize is calling this method when it shouldn't?
            // Maybe that's because the SQLStmt site member should be null in more cases?
            //assert(frag != null);
            if (frag == null) {
                return;
            }
            if (--frag.refCount == 0) {
                // The disused fragment belongs in the LRU map at the end -- at the current "ticker".
                // If its lastUse value is 0 like a new entry's, it is not currently in the map.
                // Put into the map in its proper position.
                // If it is already in the LRU map (at a "too early" entry), just set its lastUse value
                // as a cheap way to notify evictLRUfragment that it is not ready for eviction but
                // should instead be re-ordered further forward in the map.
                // This re-ordering only needs to happen when the eviction process considers the entry.
                // For a popular fragment in a small or stable cache, that may be after MANY
                // re-uses like this.
                // This prevents thrashing of the LRU map, repositioning recent entries.
                boolean notInLRUmap = (frag.lastUse == 0); // check this BEFORE updating lastUse
                frag.lastUse = ++m_nextFragUse;
                if (notInLRUmap) {
                    m_plansLRU.put(frag.lastUse, frag);
                }
            }
        }
    }

    /**
     * Get the full JSON plan associated with a given site-local fragment id.
     * Called by the EE
     */
    public static byte[] planForFragmentId(long fragmentId) {
        assert(fragmentId > 0);

        FragInfo frag = null;
        synchronized (FragInfo.class) {
            frag = m_plansById.get(fragmentId);
        }
        assert(frag != null);
        return frag.plan;
    }

    @Deprecated
    public static void addFragmentForTest(long fragmentId, byte[] plan, String stmtText) {
        Sha1Wrapper key = new Sha1Wrapper(new byte[20]);
        synchronized (FragInfo.class) {
            FragInfo frag = new FragInfo(key, plan, fragmentId, stmtText);
            m_plansById.put(frag.fragId, frag);
            frag.refCount++;
        }
    }

    public static void clear() {
        synchronized (FragInfo.class) {
            m_plansById.clear();
            m_plansByHash.clear();
            m_plansLRU.clear();
            m_nextFragId = INITIAL_FRAG_ID;
            m_nextFragUse = 1;
        }
    }
}
TOP

Related Classes of org.voltdb.planner.ActivePlanRepository$FragInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.