Package com.sleepycat.je.recovery

Source Code of com.sleepycat.je.recovery.Checkpointer

/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2005
*      Sleepycat Software.  All rights reserved.
*
* $Id: Checkpointer.java,v 1.119 2005/09/08 17:08:52 mark Exp $
*/

package com.sleepycat.je.recovery;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;

import com.sleepycat.je.CheckpointConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.EnvironmentStats;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.cleaner.Cleaner;
import com.sleepycat.je.cleaner.TrackedFileSummary;
import com.sleepycat.je.cleaner.UtilizationProfile;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.DbTree;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.INList;
import com.sleepycat.je.dbi.MemoryBudget;
import com.sleepycat.je.log.LogManager;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.ChildReference;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.Node;
import com.sleepycat.je.tree.SearchResult;
import com.sleepycat.je.tree.Tree;
import com.sleepycat.je.tree.WithRootLatched;
import com.sleepycat.je.utilint.DaemonThread;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.PropUtil;
import com.sleepycat.je.utilint.Tracer;

/**
* The Checkpointer looks through the tree for internal nodes that must be
* flushed to the log. Checkpoint flushes must be done in ascending order from
* the bottom of the tree up.
*/
public class Checkpointer extends DaemonThread {

    private EnvironmentImpl envImpl;
    private LogManager logManager;

    /* Checkpoint sequence, initialized at recovery. */
    private long checkpointId; 

    /*
     * How much the log should grow between checkpoints. If 0, we're using time
     * based checkpointing.
     */
    private long logSizeBytesInterval;
    private long logFileMax;
    private long timeInterval;
    private long lastCheckpointMillis;

    /* Stats */
    private int nCheckpoints;
    private long lastFirstActiveLsn;
    private long lastCheckpointStart;
    private long lastCheckpointEnd;

    private int nFullINFlush;
    private int nFullBINFlush;
    private int nDeltaINFlush;
    private int nFullINFlushThisRun;
    private int nDeltaINFlushThisRun;

    /* For future addition to stats:
    private int nAlreadyEvictedThisRun;
    */

    private volatile int highestFlushLevel;

    public Checkpointer(EnvironmentImpl envImpl,
                        long waitTime,
                        String name)
        throws DatabaseException {

        super(waitTime, name, envImpl);
        this.envImpl = envImpl;
        logSizeBytesInterval =
            envImpl.getConfigManager().getLong
                (EnvironmentParams.CHECKPOINTER_BYTES_INTERVAL);
        logFileMax =
            envImpl.getConfigManager().getLong(EnvironmentParams.LOG_FILE_MAX);
        nCheckpoints = 0;
        timeInterval = waitTime;
        lastCheckpointMillis = 0;
  highestFlushLevel = IN.MIN_LEVEL;
        logManager = envImpl.getLogManager();
    }

    public int getHighestFlushLevel() {
  return highestFlushLevel;
    }

    /**
     * Figure out the wakeup period. Supplied through this static method
     * because we need to pass wakeup period to the superclass and need to do
     * the calcuation outside this constructor.
     */
    public static long getWakeupPeriod(DbConfigManager configManager)
        throws IllegalArgumentException, DatabaseException {

        long wakeupPeriod = PropUtil.microsToMillis
            (configManager.getLong
                (EnvironmentParams.CHECKPOINTER_WAKEUP_INTERVAL));
        long bytePeriod = configManager.getLong
            (EnvironmentParams.CHECKPOINTER_BYTES_INTERVAL);

        /* Checkpointing period must be set either by time or by log size. */
        if ((wakeupPeriod == 0) && (bytePeriod == 0)) {
            throw new IllegalArgumentException
                (EnvironmentParams.CHECKPOINTER_BYTES_INTERVAL.getName() +
                 " and " +
                 EnvironmentParams.CHECKPOINTER_WAKEUP_INTERVAL.getName() +
                 " cannot both be 0. ");
        }

        /*
         * Checkpointing by log size takes precendence over time based period.
         */
        if (bytePeriod == 0) {
            return wakeupPeriod;
        } else {
            return 0;
        }
    }

    /**
     * Set checkpoint id -- can only be done after recovery.
     */
    synchronized public void setCheckpointId(long lastCheckpointId) {
        checkpointId = lastCheckpointId;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("<Checkpointer name=\"").append(name).append("\"/>");
        return sb.toString();
    }

    /**
     * Load stats.
     */
    public void loadStats(StatsConfig config, EnvironmentStats stat)
        throws DatabaseException {

        stat.setNCheckpoints(nCheckpoints);
        stat.setLastCheckpointStart(lastCheckpointStart);
        stat.setLastCheckpointEnd(lastCheckpointEnd);
        stat.setLastCheckpointId(checkpointId);
        stat.setNFullINFlush(nFullINFlush);
        stat.setNFullBINFlush(nFullBINFlush);
        stat.setNDeltaINFlush(nDeltaINFlush);
       
        if (config.getClear()) {
            nCheckpoints = 0;
            nFullINFlush = 0;
            nFullBINFlush = 0;
            nDeltaINFlush = 0;
        }
    }
   
    /**
     * @return the first active LSN point of the last completed checkpoint.
     * If no checkpoint has run, return null.
     */
    public long getFirstActiveLsn() {
        return lastFirstActiveLsn;
    }

    /**
     * Initialize the FirstActiveLsn during recovery.  The cleaner needs this.
     */
    public void setFirstActiveLsn(long lastFirstActiveLsn) {
        this.lastFirstActiveLsn = lastFirstActiveLsn;
    }

    synchronized public void clearEnv() {
        envImpl = null;
    }

    /**
     * Return the number of retries when a deadlock exception occurs.
     */
    protected int nDeadlockRetries()
        throws DatabaseException {

        return envImpl.getConfigManager().getInt
            (EnvironmentParams.CHECKPOINTER_RETRY);
    }

    /**
     * Called whenever the DaemonThread wakes up from a sleep. 
     */
    protected void onWakeup()
        throws DatabaseException {

        if (envImpl.isClosed()) {
            return;
        }

        doCheckpoint(CheckpointConfig.DEFAULT,
                     false, // flushAll
                     "daemon");
    }

    /**
     * Determine whether a checkpoint should be run.
     *
     * 1. If the force parameter is specified, always checkpoint.
     *
     * 2. If the config object specifies time or log size, use that.
     *
     * 3. If the environment is configured to use log size based checkpointing,
     * check the log.
     *
     * 4. Lastly, use time based checking.
     */
    private boolean isRunnable(CheckpointConfig config)
        throws DatabaseException {

        /* Figure out if we're using log size or time to determine interval.*/
        long useBytesInterval = 0;
        long useTimeInterval = 0;
        long nextLsn = DbLsn.NULL_LSN;
        try {
            if (config.getForce()) {
                return true;
            } else if (config.getKBytes() != 0) {
                useBytesInterval = config.getKBytes() << 10;
            } else if (config.getMinutes() != 0) {
                // convert to millis
                useTimeInterval = config.getMinutes() * 60 * 1000;
            } else if (logSizeBytesInterval != 0) {
                useBytesInterval = logSizeBytesInterval;
            } else {
                useTimeInterval = timeInterval;
            }

            /*
             * If our checkpoint interval is defined by log size, check on how
             * much log has grown since the last checkpoint.
             */
            if (useBytesInterval != 0) {
                nextLsn = envImpl.getFileManager().getNextLsn();
                if (DbLsn.getNoCleaningDistance(nextLsn, lastCheckpointEnd,
            logFileMax) >=
                    useBytesInterval) {
                    return true;
                } else {
                    return false;
                }
            } else if (useTimeInterval != 0) {

                /*
                 * Our checkpoint is determined by time.  If enough time has
                 * passed and some log data has been written, do a checkpoint.
                 */
                long lastUsedLsn = envImpl.getFileManager().getLastUsedLsn();
                if (((System.currentTimeMillis() - lastCheckpointMillis) >=
                     useTimeInterval) &&
                    (DbLsn.compareTo(lastUsedLsn, lastCheckpointEnd) != 0)) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } finally {
            StringBuffer sb = new StringBuffer();
            sb.append("size interval=").append(useBytesInterval);
            if (nextLsn != DbLsn.NULL_LSN) {
                sb.append(" nextLsn=").
        append(DbLsn.getNoFormatString(nextLsn));
            }
            if (lastCheckpointEnd != DbLsn.NULL_LSN) {
                sb.append(" lastCkpt=");
                sb.append(DbLsn.getNoFormatString(lastCheckpointEnd));
            }
            sb.append(" time interval=").append(useTimeInterval);
            sb.append(" force=").append(config.getForce());
           
            Tracer.trace(Level.FINEST,
                         envImpl,
                         sb.toString());
        }
    }

    /**
     * The real work to do a checkpoint. This may be called by the checkpoint
     * thread when waking up, or it may be invoked programatically through the
     * api.
     *
     * @param allowDeltas if true, this checkpoint may opt to log BIN deltas
     *       instead of the full node.
     * @param flushAll if true, this checkpoint must flush all the way to
     *       the top of the dbtree, instead of stopping at the highest level
     *       last modified.
     * @param invokingSource a debug aid, to indicate who invoked this
     *       checkpoint. (i.e. recovery, the checkpointer daemon, the cleaner,
     *       programatically)
     */
    public synchronized void doCheckpoint(CheckpointConfig config,
            boolean flushAll,
            String invokingSource)
        throws DatabaseException {

        if (envImpl.isReadOnly()) {
            return;
        }

  if (!isRunnable(config)) {
      return;
  }

        /*
         * If there are cleaned files to be deleted, flush an extra level to
         * write out the parents of cleaned nodes.  This ensures that the node
         * will contain the LSN of a cleaned files.
         */
        boolean flushExtraLevel = false;
        Set[] cleanerFiles = null;
        Cleaner cleaner = envImpl.getCleaner();
        if (cleaner != null) {
            cleanerFiles = cleaner.getFilesAtCheckpointStart();
            if (cleanerFiles != null) {
                flushExtraLevel = true;
            }
        }

        lastCheckpointMillis = System.currentTimeMillis();
        resetPerRunCounters();

        /* Get the next checkpoint id. */
        checkpointId++;
        nCheckpoints++;
        boolean success = false;
        boolean traced = false;
        int dirtyMapMemSize = 0;
        MemoryBudget mb = envImpl.getMemoryBudget();
        try {

      /*
       * Eviction can run during checkpoint as long as it follows the
       * same rules for using provisional logging and for propagating
       * logging of the checkpoint dirty set up the tree. We have to lock
       * out the evictor after the logging of checkpoint start until
       * we've selected the dirty set and decided on the highest level to
       * be flushed. See SR 11163, 11349.
       */
      long checkpointStart = DbLsn.NULL_LSN;
      long firstActiveLsn = DbLsn.NULL_LSN;
      SortedMap dirtyMap = null;
      synchronized (envImpl.getEvictor()) {
    /* Log the checkpoint start. */
    CheckpointStart startEntry =
        new CheckpointStart(checkpointId, invokingSource);
    checkpointStart = logManager.log(startEntry);

    /*
     * Remember the first active LSN -- before this position in the
     * log, there are no active transactions at this point in time.
     */
    firstActiveLsn = envImpl.getTxnManager().getFirstActiveLsn();

    if (firstActiveLsn == DbLsn.NULL_LSN) {
        firstActiveLsn = checkpointStart;
    } else {
        if (DbLsn.compareTo(checkpointStart, firstActiveLsn) < 0) {
      firstActiveLsn = checkpointStart;
        }
    }
    /* Find the dirty set. */
    dirtyMap = selectDirtyINs(flushAll, flushExtraLevel);
      }

            /* Add each level's references to the budget. */
      int totalSize = 0;
            for (Iterator i = dirtyMap.values().iterator(); i.hasNext();) {
                Set nodeSet = (Set) i.next();
                int size = nodeSet.size() *
                    MemoryBudget.CHECKPOINT_REFERENCE_SIZE;
    totalSize += size;
                dirtyMapMemSize += size;
            }
      mb.updateMiscMemoryUsage(totalSize);

            /* Flush IN nodes. */
            boolean allowDeltas = !config.getMinimizeRecoveryTime();
            flushDirtyNodes(dirtyMap, flushAll, allowDeltas,
                            flushExtraLevel, checkpointStart);

            /*
             * Flush utilization info AFTER flushing IN nodes to reduce the
             * inaccuracies caused by the sequence FileSummaryLN-LN-BIN.
             */
            flushUtilizationInfo();

            CheckpointEnd endEntry =
                new CheckpointEnd(invokingSource,
                                  checkpointStart,
                                  envImpl.getRootLsn(),
                                  firstActiveLsn,
                                  Node.getLastId(),
                                  envImpl.getDbMapTree().getLastDbId(),
                                  envImpl.getTxnManager().getLastTxnId(),
                                  checkpointId);

            /*
             * Log checkpoint end and update state kept about the last
             * checkpoint location. Send a trace message *before* the
             * checkpoint end log entry. This is done so that the normal trace
             * message doesn't affect the time-based isRunnable() calculation,
             * which only issues a checkpoint if a log record has been written
             * since the last checkpoint.
             */
            trace(envImpl, invokingSource, true);
            traced = true;

            /*
             * Always flush to ensure that cleaned files are not referenced,
             * and to ensure that this checkpoint is not wasted if we crash.
             */
            lastCheckpointEnd =
              logManager.logForceFlush(endEntry,
                                  true); // fsync required
            lastFirstActiveLsn = firstActiveLsn;
            lastCheckpointStart = checkpointStart;

      /*
       * Reset the highestFlushLevel so evictor activity knows there's no
       * further requirement for provisional logging. SR 11163.
       */
      highestFlushLevel = IN.MIN_LEVEL;

            success = true;

            if (cleaner != null && cleanerFiles != null) {
                cleaner.updateFilesAtCheckpointEnd(cleanerFiles);
            }
        } catch (DatabaseException e) {
            Tracer.trace(envImpl, "Checkpointer", "doCheckpoint",
                         "checkpointId=" + checkpointId, e);
            throw e;
        } finally {
            mb.updateMiscMemoryUsage(0 - dirtyMapMemSize);
            if (!traced) {
                trace(envImpl, invokingSource, success);
            }
        }
    }

    private void trace(EnvironmentImpl envImpl,
                       String invokingSource, boolean success ) {
        StringBuffer sb = new StringBuffer();
        sb.append("Checkpoint ").append(checkpointId);
        sb.append(": source=" ).append(invokingSource);
        sb.append(" success=").append(success);
        sb.append(" nFullINFlushThisRun=").append(nFullINFlushThisRun);
        sb.append(" nDeltaINFlushThisRun=").append(nDeltaINFlushThisRun);
        Tracer.trace(Level.CONFIG, envImpl, sb.toString());
    }

    /**
     * Flush a FileSummaryLN node for each TrackedFileSummary that is currently
     * active.  Tell the UtilizationProfile about the updated file summary.
     */
    private void flushUtilizationInfo()
        throws DatabaseException {

        /* Utilization flushing may be disabled for unittests. */
        if (!DbInternal.getCheckpointUP
      (envImpl.getConfigManager().getEnvironmentConfig())) {
            return;
        }
       
        UtilizationProfile profile = envImpl.getUtilizationProfile();

        TrackedFileSummary[] activeFiles =
            envImpl.getUtilizationTracker().getTrackedFiles();

        for (int i = 0; i < activeFiles.length; i += 1) {
            profile.flushFileSummary(activeFiles[i]);
        }
    }

    /**
     * Flush the nodes in order, from the lowest level to highest level.  As a
     * flush dirties its parent, add it to the dirty map, thereby cascading the
     * writes up the tree. If flushAll wasn't specified, we need only cascade
     * up to the highest level set at the start of checkpointing.
     *
     * Note that all but the top level INs and the BINDeltas are logged
     * provisionally. That's because we don't need to process lower INs because
     * the higher INs will end up pointing at them.
     */
    private void flushDirtyNodes(SortedMap dirtyMap,
         boolean flushAll,
         boolean allowDeltas,
                                 boolean flushExtraLevel,
                                 long checkpointStart)
        throws DatabaseException {

        while (dirtyMap.size() > 0) {

            /* Work on one level's worth of nodes in ascending level order. */
            Integer currentLevel = (Integer) dirtyMap.firstKey();
            boolean logProvisionally =
                (currentLevel.intValue() != highestFlushLevel);

            Set nodeSet = (Set) dirtyMap.get(currentLevel);
            Iterator iter = nodeSet.iterator();

            /* Flush all those nodes */
            while (iter.hasNext()) {
                CheckpointReference targetRef =
                    (CheckpointReference) iter.next();

                /*
                 * Check if the db is still valid since INs of deleted
                 * databases are left on the in-memory tree until the post
                 * commit processing of a Environment.removeDatabase occurs.
                 */
                if (!(targetRef.db.getIsDeleted())) {
                    flushIN(targetRef, dirtyMap, currentLevel.intValue(),
                            logProvisionally, allowDeltas, checkpointStart);
                }

                iter.remove();
            }

            /* We're done with this level. */
            dirtyMap.remove(currentLevel);

            /* We can stop at this point. */
            if (currentLevel.intValue() == highestFlushLevel) {
                break;
            }
        }
    }

    /**
     * Scan the INList for all dirty INs. Arrange them in level sorted map for
     * level ordered flushing.
     */
    private SortedMap selectDirtyINs(boolean flushAll,
                                     boolean flushExtraLevel)
        throws DatabaseException {

        SortedMap newDirtyMap = new TreeMap();

        INList inMemINs = envImpl.getInMemoryINs();
        inMemINs.latchMajor();

        /*
   * Opportunistically recalculate the environment wide memory count.
   * Incurs no extra cost because we're walking the IN list anyway.  Not
   * the best in terms of encapsulation as prefereably all memory
   * calculations are done in MemoryBudget, but done this way to avoid
   * any extra latching.
   */
        long totalSize = 0;
        MemoryBudget mb = envImpl.getMemoryBudget();

        try {
            Iterator iter = inMemINs.iterator();
            while (iter.hasNext()) {
                IN in = (IN) iter.next();
                in.latch(false);
                try {
                    totalSize = mb.accumulateNewUsage(in, totalSize);

                    if (in.getDirty()) {
                        Integer level = new Integer(in.getLevel());
                        Set dirtySet;
                        if (newDirtyMap.containsKey(level)) {
                            dirtySet = (Set) newDirtyMap.get(level);
                        } else {
                            dirtySet = new HashSet();
                            newDirtyMap.put(level, dirtySet);
                        }
                        dirtySet.add
                             (new CheckpointReference(in.getDatabase(),
                                                      in.getNodeId(),
                                                      in.containsDuplicates(),
                                                      in.isDbRoot(),
                                                      in.getMainTreeKey(),
                                                      in.getDupTreeKey()));
                    }
                } finally {
                    in.releaseLatch();
                }
            }

            /* Set the tree cache size. */
            mb.refreshTreeMemoryUsage(totalSize);

            /*
             * If we're flushing all for cleaning, we must flush to the point
             * that there are no nodes with LSNs in the cleaned files.
             */
            if (newDirtyMap.size() > 0) {
                if (flushAll) {
                    highestFlushLevel =
      envImpl.getDbMapTree().getHighestLevel();
                } else {
                    highestFlushLevel =
                        ((Integer) newDirtyMap.lastKey()).intValue();
                    if (flushExtraLevel) {
                        highestFlushLevel += 1;
                    }
                }
            } else {
    highestFlushLevel = IN.MAX_LEVEL;
      }
        } finally {
            inMemINs.releaseMajorLatchIfHeld();
        }

        return newDirtyMap;
    }
   
    /**
     * Flush the target IN.
     */
    private void flushIN(CheckpointReference targetRef,
                         Map dirtyMap,
                         int currentLevel,
                         boolean logProvisionally,
                         boolean allowDeltas,
                         long checkpointStart)
        throws DatabaseException {

        Tree tree = targetRef.db.getTree();
        boolean targetWasRoot = false;
        if (targetRef.isDbRoot) {
            /* We're trying to flush the root. */
            RootFlusher flusher =
    new RootFlusher(targetRef.db, logManager, targetRef.nodeId);
            tree.withRootLatched(flusher);
            boolean flushed = flusher.getFlushed();

            /*
             * If this target isn't the root anymore, we'll have to handled it
             * like a regular node.
             */
            targetWasRoot = flusher.stillRoot();
           
            /*
             * Update the tree's owner, whether it's the env root or the
             * dbmapping tree.
             */
            if (flushed) {
                DbTree dbTree = targetRef.db.getDbEnvironment().getDbMapTree();
                dbTree.modifyDbRoot(targetRef.db);
                nFullINFlushThisRun++;
                nFullINFlush++;
            }
        }

        /*
         * The following attempt to flush applies to two cases:
   *
         * (1) the target was not ever the root
   *
         * (2) the target was the root, when the checkpoint dirty set was
         * assembled but is not the root now.
   *
         */
        if (!targetWasRoot) {

            /*
             * The "isRoot" param is used to stop a search in
             * BIN.descendOnParentSearch and is passed as false (never stop).
             */
            SearchResult result =
                tree.getParentINForChildIN(targetRef.nodeId,
                                           targetRef.containsDuplicates,
                                           false,  // isRoot
                                           targetRef.mainTreeKey,
                                           targetRef.dupTreeKey,
                                           false,  // requireExactMatch
                                           false,  // updateGeneration
                                           -1,     // targetLevel
                                           null,   // trackingList
                                           false); // doFetch

            /*
             * We must make sure that every IN that was selected for the
             * checkpointer's dirty IN set at the beginning of checkpoint is
             * written into the log and can be properly accessed from
             * ancestors. However, we have to take care for cases where the
             * evictor has written out a member of this dirty set before the
             * checkpointer got to it. See SR 10249.
             *
             * If no possible parent is found, the compressor may have deleted
             * this item before we got to processing it.
             */
            if (result.parent != null) {
                boolean mustLogParent = false;
                try {
                    if (result.exactParentFound) {
                        /*
                         * If the child has already been evicted, don't
                         * refetch it.
                         */
                        IN renewedTarget =
                            (IN) result.parent.getTarget(result.index);

                        if (renewedTarget == null) {
                            /* nAlreadyEvictedThisRun++;  -- for future */
                            mustLogParent = true;
                        } else {
                            mustLogParent =
                                logTargetAndUpdateParent(renewedTarget,
                                                         result.parent,
                                                         result.index,
                                                         allowDeltas,
                                                         checkpointStart,
                                                         logProvisionally);
                        }
                    } else {
                        /* result.exactParentFound was false. */
                        if (result.childNotResident) {

                            /*
                             * But it was because the child wasn't resident.
                             * To be on the safe side, we'll put the parent
                             * into the dirty set to be logged when that level
                             * is processed.
                             *
                             * Only do this if the parent we found is at a
                             * higher level than the child.  This ensures that
                             * the non-exact search does not find a sibling
                             * rather than a parent. [#11555]
                             */
                            if (result.parent.getLevel() > currentLevel) {
                                mustLogParent = true;
                            }
                            /* nAlreadyEvictedThisRun++; -- for future. */
                        }
                    }

                    if (mustLogParent) {
                        addToDirtyMap(dirtyMap, result.parent);
                    }
                } finally {
                    result.parent.releaseLatch();
                }
            }
        }
    }

    private boolean logTargetAndUpdateParent(IN target,
                                             IN parent,
                                             int index,
                                             boolean allowDeltas,
                                             long checkpointStart,
                                             boolean logProvisionally)
        throws DatabaseException {

        target.latch(false);
        long newLsn = DbLsn.NULL_LSN;
        boolean mustLogParent = true;
        try {

            /*
             * Compress this node if necessary. Note that this may dirty the
             * node.
             */
            envImpl.lazyCompress(target);

            if (target.getDirty()) {

                /*
                 * Note that target decides whether to log a delta. Only BINs
                 * that fall into the required percentages and have not been
                 * cleaned will be logged with a delta.  Cleaner migration is
                 * allowed.
                 */
                newLsn = target.log(logManager,
                                    allowDeltas,
                                    logProvisionally,
                                    true,  // allowMigration
                                    parent);
                if (allowDeltas && newLsn == DbLsn.NULL_LSN) {
                    nDeltaINFlushThisRun++;
                    nDeltaINFlush++;

                    /*
                     * If this BIN was already logged after checkpoint start
                     * and before this point (i.e. by an eviction), we must
                     * make sure that the last full version is accessible from
                     * ancestors. We can skip logging parents only if this is
                     * the first logging of this node in the checkpoint
                     * interval.
                     */
                    long lastFullLsn =  target.getLastFullVersion();
                    if (DbLsn.compareTo(lastFullLsn,
                                        checkpointStart) < 0) {
                        mustLogParent = false;
                    }
                }
            }
        } finally {
            target.releaseLatch();
        }

        /* Update the parent if a full version was logged. */
        if (newLsn != DbLsn.NULL_LSN) {
            nFullINFlushThisRun++;
            nFullINFlush++;
            if (target instanceof BIN) {
                nFullBINFlush++;
            }
            parent.updateEntry(index, newLsn);
        }
       
        return mustLogParent;
    }

    /*
     * RootFlusher lets us write out the root IN within the root latch.
     */
    private static class RootFlusher implements WithRootLatched {
        private DatabaseImpl db;
        private boolean flushed;
        private boolean stillRoot;
        private LogManager logManager;
        private long targetNodeId;

        RootFlusher(DatabaseImpl db,
                    LogManager logManager,
                    long targetNodeId) {
            this.db = db;
            flushed = false;
            this.logManager = logManager;
            this.targetNodeId = targetNodeId;
            stillRoot = false;
        }

        /**
         * Flush the rootIN if dirty.
         */
        public IN doWork(ChildReference root)
            throws DatabaseException {

      if (root == null) {
    return null;
      }
            IN rootIN = (IN) root.fetchTarget(db, null);
            rootIN.latch(false);
            try {
                if (rootIN.getNodeId() == targetNodeId) {

                    /*
         * stillRoot handles the situation where the root was split
         * after it was placed in the checkpointer's dirty set.
                     */
                    stillRoot = true;
                    if (rootIN.getDirty()) {
                        long newLsn = rootIN.log(logManager);
                        root.setLsn(newLsn);
                        flushed = true;
                    }
                }
            } finally {
                rootIN.releaseLatch();
            }                   
            return null;
        }

        boolean getFlushed() {
            return flushed;
        }

        boolean stillRoot() {
            return stillRoot;
        }
    }

    /**
     * Add a node to the dirty map. The dirty map is keyed by level (Integers)
     * and holds sets of IN references.
     */
    private void addToDirtyMap(Map dirtyMap, IN in) {
        Integer inLevel = new Integer(in.getLevel());
        Set inSet = (Set) dirtyMap.get(inLevel);
       
        /* If this level doesn't exist in the map yet, make a new entry. */
        if (inSet == null) {
            inSet = new HashSet();
            dirtyMap.put(inLevel, inSet);
        }   
       
        /* Add to the set. */
        inSet.add(new CheckpointReference(in.getDatabase(),
                                          in.getNodeId(),
                                          in.containsDuplicates(),
                                          in.isDbRoot(),
                                          in.getMainTreeKey(),
                                          in.getDupTreeKey()));
    }

    /**
     * Reset per-run counters.
     */
    private void resetPerRunCounters() {
        nFullINFlushThisRun = 0;
        nDeltaINFlushThisRun = 0;
        /* nAlreadyEvictedThisRun = 0; -- for future */
    }

    /*
     * CheckpointReferences are used to identify nodes that must be flushed as
     * part of the checkpoint. We don't keep an actual reference to the node
     * because that prevents nodes from being GC'ed during checkpoint.
     *
     * Using a checkpointReference introduces a window between the point when
     * the checkpoint dirty set is created and when the node is flushed. Some
     * of the fields saved in the reference are immutable: db, nodeId,
     * containsDuplicates. The others are not and we have to handle potential
     * change:
     *
     * isDbRoot: it's possible for isDbRoot to go from true->false, but not
     *         false->true. True->false is handled by the flushIN method
     *         by finding the root and checking if it is the target.
     * mainTreeKey, dupTreeKey: These can change only in the event of a
     *         split. If they do, there is the chance that the checkpointer
     *         will find the wrong node to flush, but that's okay because
     *         the split guarantees flushing to the root, so the target will
     *         be properly logged within the checkpoint period.
     */
    static class CheckpointReference {
        DatabaseImpl db;
        long nodeId;
        boolean containsDuplicates;
        boolean isDbRoot;
        byte[] mainTreeKey;
        byte[] dupTreeKey;

        CheckpointReference(DatabaseImpl db,
                            long nodeId,
                            boolean containsDuplicates,
                            boolean isDbRoot,
                            byte[] mainTreeKey,
                            byte[] dupTreeKey) {
            this.db = db;
            this.nodeId = nodeId;
            this.containsDuplicates = containsDuplicates;
            this.isDbRoot = isDbRoot;
            this.mainTreeKey = mainTreeKey;
            this.dupTreeKey = dupTreeKey;
        }

        public boolean equals(Object o) {
            if (!(o instanceof CheckpointReference)) {
                return false;
            }

            CheckpointReference other = (CheckpointReference) o;
            return nodeId == other.nodeId;
        }

        public int hashCode() {
            return (int) nodeId;
        }
    }
}
TOP

Related Classes of com.sleepycat.je.recovery.Checkpointer

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.