Package org.exist.storage

Source Code of org.exist.storage.DefaultCacheManager

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2001-04 The eXist Project
*  http://exist-db.org
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  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 Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*  $Id$
*/
package org.exist.storage;

import org.apache.log4j.Logger;

import org.exist.management.Agent;
import org.exist.management.AgentFactory;
import org.exist.storage.cache.Cache;
import org.exist.util.DatabaseConfigurationException;

import java.text.NumberFormat;

import java.util.ArrayList;
import java.util.List;


/**
* CacheManager maintains a global memory pool available to all page caches. All caches start with a low default setting, but CacheManager can grow
* individual caches until the total memory is reached. Caches can also be shrinked if their "load" remains below a given threshold between check
* intervals.The check interval is determined by the global sync background thread.
*
* <p>The class computes the available memory in terms of pages.</p>
*
* @author  wolf
*/
public class DefaultCacheManager implements CacheManager
{
    private final static Logger LOG                             = Logger.getLogger( DefaultCacheManager.class );

    /** The maximum fraction of the total memory that can be used by a single cache. */
    public final static double  MAX_MEM_USE                     = 0.9;

    /** The minimum size a cache needs to have to be considered for shrinking, defined in terms of a fraction of the overall memory. */
    public final static double  MIN_SHRINK_FACTOR               = 0.5;

    /** The amount by which a large cache will be shrinked if other caches request a resize. */
    public final static double  SHRINK_FACTOR                   = 0.7;

    /**
     * The minimum number of pages that must be read from a cache between check intervals to be not considered for shrinking. This is a measure for
     * the "load" of the cache. Caches with high load will never be shrinked. A negative value means that shrinkage will not be performed.
     */
    public final static int     DEFAULT_SHRINK_THRESHOLD              = 10000;
    public final static String  DEFAULT_SHRINK_THRESHOLD_STRING       = "10000";

    public static int           DEFAULT_CACHE_SIZE                    = 64;
    public static final String  CACHE_SIZE_ATTRIBUTE                 = "cacheSize";
    public static final String  PROPERTY_CACHE_SIZE                   = "db-connection.cache-size";
   
    public static String         DEFAULT_CACHE_CHECK_MAX_SIZE_STRING    = "true";
    public static final String  CACHE_CHECK_MAX_SIZE_ATTRIBUTE       = "checkMaxCacheSize";
    public static final String  PROPERTY_CACHE_CHECK_MAX_SIZE        = "db-connection.check-max-cache-size";

    public static final String  SHRINK_THRESHOLD_ATTRIBUTE            = "cacheShrinkThreshold";
    public static final String  SHRINK_THRESHOLD_PROPERTY            = "db-connection.cache-shrink-threshold";

    /** Caches maintained by this class. */
    private List<Cache>         caches                          = new ArrayList<Cache>();

    private long                totalMem;

    /** The total maximum amount of pages shared between all caches. */
    private int                 totalPageCount;

    /** The number of pages currently used by the active caches. */
    private int                 currentPageCount                = 0;

    /** The maximum number of pages that can be allocated by a single cache. */
    private int                 maxCacheSize;

    private int                 pageSize;

    /**
     * The minimum number of pages that must be read from a cache between check intervals to be not considered for shrinking. This is a measure for
     * the "load" of the cache. Caches with high load will never be shrinked. A negative value means that shrinkage will not be performed.
     */

    private int                 shrinkThreshold                 = DEFAULT_SHRINK_THRESHOLD;

    /**
     * Signals that a resize had been requested by a cache, but the request could not be accepted during normal operations. The manager might try to
     * shrink the largest cache during the next sync event.
     */
    private Cache               lastRequest                     = null;

    private String              instanceName;

    public DefaultCacheManager( BrokerPool pool )
    {
        this.instanceName = pool.getId();
        int cacheSize;

        if( ( pageSize = pool.getConfiguration().getInteger( BrokerPool.PROPERTY_PAGE_SIZE ) ) < 0 ) {

            //TODO : should we share the page size with the native broker ?
            pageSize = BrokerPool.DEFAULT_PAGE_SIZE;
        }

        if( ( cacheSize = pool.getConfiguration().getInteger( PROPERTY_CACHE_SIZE ) ) < 0 ) {
            cacheSize = DEFAULT_CACHE_SIZE;
        }

        shrinkThreshold = pool.getConfiguration().getInteger( SHRINK_THRESHOLD_PROPERTY );

        totalMem        = cacheSize * 1024L * 1024L;
       
        final Boolean checkMaxCache = (Boolean)pool.getConfiguration().getProperty( PROPERTY_CACHE_CHECK_MAX_SIZE );
       
        if( checkMaxCache == null || checkMaxCache.booleanValue() ) {
      final long max        = Runtime.getRuntime().maxMemory();
      long maxCache   = ( max >= ( 768 * 1024 * 1024 ) ) ? ( max / 2 ) : ( max / 3 );
 
      if( totalMem > maxCache ) {
        totalMem = maxCache;
       
        LOG.warn( "The cacheSize=\"" + cacheSize +
              "\" setting in conf.xml is too large. Java has only " + ( max / 1024 ) + "k available. Cache manager will not use more than " + ( totalMem / 1024L ) + "k " +
              "to avoid memory issues which may lead to database corruptions."
        );
      }
    } else {
      LOG.warn( "Checking of Max Cache Size disabled by user, this could cause memory issues which may lead to database corruptions if you don't have enough memory allocated to your JVM!" );
    }
       
        int buffers = (int)( totalMem / pageSize );

        this.totalPageCount = buffers;
        this.maxCacheSize   = (int)( totalPageCount * MAX_MEM_USE );
        final NumberFormat nf     = NumberFormat.getNumberInstance();
       
        LOG.info( "Cache settings: " + nf.format( totalMem / 1024L ) + "k; totalPages: " + nf.format( totalPageCount ) +
                "; maxCacheSize: " + nf.format( maxCacheSize ) +
                "; cacheShrinkThreshold: " + nf.format( shrinkThreshold )
        );
       
        registerMBean();
    }

    @Override
    public void registerCache( Cache cache )
    {
        currentPageCount += cache.getBuffers();
        caches.add( cache );
        cache.setCacheManager( this );
        registerMBean( cache );
    }


    @Override
    public void deregisterCache( Cache cache )
    {
        Cache next;

        for( int i = 0; i < caches.size(); i++ ) {
            next = (Cache)caches.get( i );

            if( cache == next ) {
                caches.remove( i );
                break;
            }
        }
        currentPageCount -= cache.getBuffers();
    }


    @Override
    public int requestMem( Cache cache )
    {
        if( currentPageCount >= totalPageCount ) {

            if( cache.getBuffers() < maxCacheSize ) {
                lastRequest = cache;
            }

            // no free pages available
//            LOG.debug("Cache " + cache.getFileName() + " cannot be resized");
            return( -1 );
        }

        if( ( cache.getGrowthFactor() > 1.0 ) && ( cache.getBuffers() < maxCacheSize ) ) {

            synchronized( this ) {

                if( currentPageCount >= totalPageCount ) {

                    // another cache has been resized. Give up
                    return( -1 );
                }

                // calculate new cache size
                int newCacheSize = (int)( cache.getBuffers() * cache.getGrowthFactor() );

                if( newCacheSize > maxCacheSize ) {

                    // new cache size is too large: adjust
                    newCacheSize = maxCacheSize;
                }

                if( ( currentPageCount + newCacheSize ) > totalPageCount ) {

                    // new cache size exceeds total: adjust
                    newCacheSize = cache.getBuffers() + ( totalPageCount - currentPageCount );
                }

                if( LOG.isDebugEnabled() ) {
                    final NumberFormat nf = NumberFormat.getNumberInstance();
                    LOG.debug( "Growing cache " + cache.getFileName() + " (a " + cache.getClass().getName() + ") from " + nf.format( cache.getBuffers() ) + " to " + nf.format( newCacheSize ) );
                }
                currentPageCount -= cache.getBuffers();

                // resize the cache
                cache.resize( newCacheSize );
                currentPageCount += newCacheSize;
//                LOG.debug("currentPageCount = " + currentPageCount + "; max = " + totalPageCount);
                return( newCacheSize );
            }
        }
        return( -1 );
    }


    /**
     * Called from the global major sync event to check if caches can be shrinked. To be shrinked, the size of a cache needs to be larger than the
     * factor defined by {@link #MIN_SHRINK_FACTOR} and its load needs to be lower than {@link #DEFAULT_SHRINK_THRESHOLD}.
     *
     * <p>If shrinked, the cache will be reset to the default initial cache size.</p>
     */
    @Override
    public void checkCaches()
    {
        final int   minSize = (int)( totalPageCount * MIN_SHRINK_FACTOR );
        Cache cache;
        int   load;

        if( shrinkThreshold >= 0 ) {

            for( int i = 0; i < caches.size(); i++ ) {
                cache = (Cache)caches.get( i );

                if( cache.getGrowthFactor() > 1.0 ) {
                    load = cache.getLoad();

                    if( ( cache.getBuffers() > minSize ) && ( load < shrinkThreshold ) ) {

                        if( LOG.isDebugEnabled() ) {
                            final NumberFormat nf = NumberFormat.getNumberInstance();
                            LOG.debug( "Shrinking cache: " + cache.getFileName() + " (a " + cache.getClass().getName() + ") to " + nf.format( cache.getBuffers() ) );
                        }
                        currentPageCount -= cache.getBuffers();
                        cache.resize( getDefaultInitialSize() );
                        currentPageCount += getDefaultInitialSize();
                    }
                }
            }
        }
    }


    @Override
    public void checkDistribution()
    {
        if( lastRequest == null ) {
            return;
        }
        final int   minSize = (int)( totalPageCount * MIN_SHRINK_FACTOR );
        Cache cache;

        for( int i = 0; i < caches.size(); i++ ) {
            cache = (Cache)caches.get( i );

            if( cache.getBuffers() >= minSize ) {
                int newSize = (int)( cache.getBuffers() * SHRINK_FACTOR );

                if( LOG.isDebugEnabled() ) {
                    final NumberFormat nf = NumberFormat.getNumberInstance();
                    LOG.debug( "Shrinking cache: " + cache.getFileName() + " (a " + cache.getClass().getName() + ") to " + nf.format( newSize ) );
                }
                currentPageCount -= cache.getBuffers();
                cache.resize( newSize );
                currentPageCount += newSize;
                break;
            }
        }
        lastRequest = null;
    }


    /**
     * @return Maximum size of all Caches in pages
     */
    @Override
    public long getMaxTotal()
    {
        return( totalPageCount );
    }

    /**
     * @return Current size of all Caches in pages
     */
    @Override
    public long getCurrentSize()
    {
        return( currentPageCount );
    }

    /**
     * @return Maximum size of a single Cache in bytes
     */
    @Override
    public long getMaxSingle()
    {
        return( maxCacheSize );
    }

    public long getSizeInBytes()
    {
        return( currentPageCount * pageSize );
    }

    public long getTotalMem()
    {
        return( totalMem );
    }


    /**
     * Returns the default initial size for all caches.
     *
     * @return  Default initial size 64.
     */
    public int getDefaultInitialSize()
    {
        return( DEFAULT_CACHE_SIZE );
    }


    private void registerMBean()
    {
        final Agent agent = AgentFactory.getInstance();

        try {
            agent.addMBean( instanceName, "org.exist.management." + instanceName + ":type=CacheManager", new org.exist.management.CacheManager( this ) );
        }
        catch( final DatabaseConfigurationException e ) {
            LOG.warn( "Exception while registering cache mbean.", e );
        }
    }


    private void registerMBean( Cache cache )
    {
        final Agent agent = AgentFactory.getInstance();

        try {
            agent.addMBean( instanceName, "org.exist.management." + instanceName + ":type=CacheManager.Cache,name=" + cache.getFileName() + ",cache-type=" + cache.getType(), new org.exist.management.Cache( cache ) );
        }
        catch( final DatabaseConfigurationException e ) {
            LOG.warn( "Exception while registering cache mbean.", e );
        }
    }
}
TOP

Related Classes of org.exist.storage.DefaultCacheManager

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.