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

com.sleepycat.je.rep.impl.node.DurabilityQuorum 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.rep.impl.node;

import static java.util.logging.Level.FINE;

import java.util.logging.Logger;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Durability.ReplicaAckPolicy;
import com.sleepycat.je.rep.InsufficientAcksException;
import com.sleepycat.je.rep.InsufficientReplicasException;
import com.sleepycat.je.rep.arbitration.Arbiter;
import com.sleepycat.je.rep.impl.RepImpl;
import com.sleepycat.je.rep.impl.RepNodeImpl;
import com.sleepycat.je.rep.stream.FeederTxns;
import com.sleepycat.je.rep.txn.MasterTxn;
import com.sleepycat.je.utilint.LoggerUtils;

/**
 * Provides information about quorums needed for durability decisions.
 */
public class DurabilityQuorum {

    private final RepImpl repImpl;
    private final Logger logger;

    public DurabilityQuorum(RepImpl repImpl) {

       this.repImpl = repImpl;
       logger = LoggerUtils.getLogger(getClass());
    }

    /**
     * See if there are a sufficient number of replicas alive to support
     * the commit for this transaction. Used as an optimizing step before any
     * writes are executed, to reduce the number of outstanding writes that
     * suffer from insufficient ack problems.
     *
     * If this node is not the master, just return. A different kind of check
     * will catch the fact that this node cannot support writes.
     *
     * TODO: Read only transactions on the master should not have to wait.
     * In the future, introduce either a read-only attribute as part of
     * TransactionConfig or a read only transaction class to optimize this.
     *
     * @param insufficientReplicasTimeout timeout in ms
     * @throws InsufficientReplicasException if there are not enough replicas
     * connected to this feeder to be able to commit the transaction.
     */
    public void ensureReplicasForCommit(MasterTxn txn,
                                        int insufficientReplicasTimeout)
        throws DatabaseException, InterruptedException,
               InsufficientReplicasException {

        RepNode repNode = repImpl.getRepNode();
        if (!repNode.isMaster()) {
            return;
        }

        ReplicaAckPolicy ackPolicy =
            txn.getDefaultDurability().getReplicaAck();
        int requiredReplicaAckCount = getCurrentRequiredAckCount(ackPolicy);
        if (logger.isLoggable(FINE)) {
            LoggerUtils.fine(logger, repImpl,
                             "Txn " + txn + ": checking that " +
                             requiredReplicaAckCount +
                             " feeders exist before starting commit");
        }

        /* No need to wait for anyone else, only this node is needed. */
        if (requiredReplicaAckCount == 0) {
            return;
        }

        if (repNode.feederManager().awaitFeederReplicaConnections
            (requiredReplicaAckCount, insufficientReplicasTimeout)) {
            /* Wait was successful */
            return;
        }

        /*
         * Timed out, not enough replicas connected, or feeder was shutdown
         * normally, that is, without any exceptions while waiting.
         */
        if (!repNode.isMaster()) {

            /*
             * Continue if we are no longer the master after the wait. The
             * transaction will fail if it tries to acquire write locks, or
             * at commit.
             */
            return;
        }

        if (ackPolicy.equals(ReplicaAckPolicy.SIMPLE_MAJORITY) &&
            repNode.getArbiter().activateArbitration()) {
            return;
        }

        final boolean includeArbiters =
            !ackPolicy.equals(ReplicaAckPolicy.ALL);
        throw new InsufficientReplicasException(
            txn, ackPolicy, requiredReplicaAckCount,
            repNode.feederManager().activeAckReplicas(includeArbiters));
    }

    /**
     * Determine whether acknowledgments from the specified replica should be
     * counted against transaction durability requirements.
     *
     * @param replica the replica node
     * @return whether acknowledgments from the replica should be counted
     */
    public boolean replicaAcksQualify(final RepNodeImpl replica) {

        /* Only acknowledgments from electable nodes should be counted */
        return replica.getType().isElectable();
    }

    /**
     * Determine if this transaction has been adequately acknowledged.
     *
     * @throws InsufficientAcksException if the transaction's durability
     * requirements have not been met.
     */
    public void ensureSufficientAcks(FeederTxns.TxnInfo txnInfo,
                                     int timeoutMs)
        throws InsufficientAcksException {

        int pendingAcks = txnInfo.getPendingAcks();
        if (pendingAcks == 0) {
            return;
        }

        MasterTxn txn = txnInfo.getTxn();
        final int requiredAcks = getCurrentRequiredAckCount(
            txn.getCommitDurability().getReplicaAck());
        int requiredAckDelta = txn.getRequiredAckCount() - requiredAcks;
        if (requiredAckDelta >= pendingAcks) {

            /*
             * The group size was reduced while waiting for acks and the
             * acks received are sufficient given the new reduced group
             * size.
             */
            return;
        }

        /* Snapshot the state to be used in the error message */
        final String dumpState = repImpl.dumpAckFeederState();

        /*
         * Repeat the check to ensure that acks have not been received in
         * the time between the completion of the await() call above and
         * the creation of the exception message. This tends to happen when
         * there are lots of threads in the process thus potentially
         * delaying the resumption of this thread following the timeout
         * resulting from the await.
         *
         * It should be noted that some transactions may be setup to not
         * decrement the wait latch count for Arbiter acks. Checking an
         * Arbiters feeder VLSN here will account for the Arbiter ack.
         */
        final FeederManager feederManager =
            repImpl.getRepNode().feederManager();
        int currentFeederCount =
            feederManager.getNumCurrentAckFeeders(txn.getCommitVLSN());
        if (currentFeederCount >= requiredAcks) {
            String msg = "txn " + txn.getId() +
                " commit vlsn:" + txnInfo.getCommitVLSN() +
                " acknowledged after explicit feeder check" +
                " latch count:" + txnInfo.getPendingAcks() +
                " state:" + dumpState +
                " required acks:" + requiredAcks;

            LoggerUtils.info(logger, repImpl, msg);
            return;
        }

        /*
         * We can avoid the exception if it's possible for this node to enter
         * activate arbitration. It's useful to check for this again here in
         * case we happen to lose connections to replicas in the (brief)
         * period since the pre-log hook.  Note that in this case we merely
         * want to check; we don't want to switch into active arbitration
         * unless/until we actually lose the connection to the replica at
         * commit time. TODO: this doesn't seem right! Shouldn't we require
         * activation at this point!!!
         */
        if (repImpl.getRepNode().getArbiter().activationPossible()) {
            return;
        }
        throw new InsufficientAcksException(txn, pendingAcks, timeoutMs,
                                            dumpState);

    }

    /**
     * Returns the minimum number of acknowledgments required to satisfy the
     * ReplicaAckPolicy for a given group size. Does not include the master.
     * The method factors in considerations like the current arbitration status
     * of the environment and the composition of the replication group.
     *
     * TODO: it seems sufficient to return a number, as opposed to a set of
     * qualified ack nodes, as long as {@link #replicaAcksQualify} will only
     * count qualified acks against the required count. That does mean that
     * getCurrentRequiredAckCount and noteReplicaAcks for a transaction must be
     * kept consistent.
     *
     * @return the number of nodes that are needed, not including the master.
     */
    public int getCurrentRequiredAckCount(ReplicaAckPolicy ackPolicy) {

        /*
         * If the electableGroupSizeOverride is determining the size of the
         * election quorum, let it also influence the durability quorum.
         */
        RepNode repNode = repImpl.getRepNode();
        int electableGroupSizeOverride =
            repNode.getElectionQuorum().getElectableGroupSizeOverride();
        if (electableGroupSizeOverride > 0) {

            /*
             * Use the override-defined group size to determine the
             * number of acks.
             */
            return ackPolicy.minAckNodes(electableGroupSizeOverride) - 1;
        }

        Arbiter arbiter = repNode.getArbiter();
        if (arbiter.isApplicable(ackPolicy)) {
            return arbiter.getAckCount(ackPolicy);
        }

        return ackPolicy.minAckNodes
            (repNode.getGroup().getAckGroupSize()) - 1;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy