Search in sources :

Example 1 with MemberConfig

use of com.torodb.mongodb.commands.pojos.MemberConfig in project torodb by torodb.

the class TopologyCoordinator method lookForSyncSource.

/**
   * Looks for an optimal sync source to replicate from.
   *
   * The first attempt, we ignore those nodes with slave delay higher than our own, hidden nodes,
   * and nodes that are excessively lagged. The second attempt includes such nodes, in case those
   * are the only ones we can reach. This loop attempts to set 'closestIndex'.
   *
   * @param now              the current time
   * @param lastOpAppliedOp  the last OpTime this node has apply
   * @param onlyOptimal      if true, slaves with more delay than ourselve, hidden nodes or
   *                         excessively lagged nodes are ignored
   * @param oldestSyncOpTime the oldest optime considered not excessively lagged. Only used if
   *                         onlyOptimal is true.
   * @return the new optimal sync source, which is not {@link Optional#isPresent() present} if no
   *         one can be chosen
   */
private Optional<MemberConfig> lookForSyncSource(Instant now, Optional<OpTime> lastOpAppliedOp, boolean onlyOptimal, OpTime oldestSyncOpTime) {
    OpTime lastOpApplied = lastOpAppliedOp.orElse(OpTime.EPOCH);
    Stream<MemberHeartbeatData> hbCandidateStream = _hbdata.stream().filter(MemberHeartbeatData::isUp).filter(hbData -> hbData.getState().isReadable()).filter(hbData -> hbData.getOpTime().isAfter(lastOpApplied));
    if (onlyOptimal) {
        hbCandidateStream = hbCandidateStream.filter(hbData -> hbData.getOpTime().isEqualOrAfter(oldestSyncOpTime));
    }
    Stream<MemberConfig> mcCandidateStream = hbCandidateStream.map(this::getMemberConfig).filter(mc -> !isBlacklistedMember(mc, now));
    if (onlyOptimal) {
        mcCandidateStream = mcCandidateStream.filter(mc -> !mc.isHidden()).filter(mc -> mc.getSlaveDelay() < slaveDelaySecs);
    }
    //If there are several candidates, the one whose ping is lower is returned
    return mcCandidateStream.reduce((MemberConfig cand1, MemberConfig cand2) -> {
        long ping1 = getPing(cand1.getHostAndPort());
        long ping2 = getPing(cand2.getHostAndPort());
        if (ping1 < ping2) {
            return cand1;
        }
        return cand2;
    });
}
Also used : MemberHeartbeatData(com.torodb.mongodb.commands.pojos.MemberHeartbeatData) MemberState(com.torodb.mongodb.commands.pojos.MemberState) OpTime(com.eightkdata.mongowp.OpTime) MemberHeartbeatData(com.torodb.mongodb.commands.pojos.MemberHeartbeatData) Nonnegative(javax.annotation.Nonnegative) UnauthorizedException(com.eightkdata.mongowp.exceptions.UnauthorizedException) InvalidOptionsException(com.eightkdata.mongowp.exceptions.InvalidOptionsException) MemberConfig(com.torodb.mongodb.commands.pojos.MemberConfig) ReplSetHeartbeatArgument(com.torodb.mongodb.commands.signatures.internal.ReplSetHeartbeatCommand.ReplSetHeartbeatArgument) HashMap(java.util.HashMap) ReplSetHeartbeatReply(com.torodb.mongodb.commands.signatures.internal.ReplSetHeartbeatReply) ShutdownInProgressException(com.eightkdata.mongowp.exceptions.ShutdownInProgressException) ReplicaSetConfig(com.torodb.mongodb.commands.pojos.ReplicaSetConfig) OptionalInt(java.util.OptionalInt) ArrayList(java.util.ArrayList) ReplSetProtocolVersion(com.torodb.mongodb.commands.pojos.ReplSetProtocolVersion) ReplSetSyncFromReply(com.torodb.mongodb.commands.signatures.repl.ReplSetSyncFromCommand.ReplSetSyncFromReply) UnsignedInteger(com.google.common.primitives.UnsignedInteger) Duration(java.time.Duration) Map(java.util.Map) RemoteCommandResponse(com.eightkdata.mongowp.client.core.MongoConnection.RemoteCommandResponse) MongoException(com.eightkdata.mongowp.exceptions.MongoException) ErrorCode(com.eightkdata.mongowp.ErrorCode) Nonnull(javax.annotation.Nonnull) WeakHashMap(java.util.WeakHashMap) Nullable(javax.annotation.Nullable) NodeNotFoundException(com.eightkdata.mongowp.exceptions.NodeNotFoundException) Set(java.util.Set) Health(com.torodb.mongodb.commands.pojos.MemberHeartbeatData.Health) Instant(java.time.Instant) HostAndPort(com.google.common.net.HostAndPort) List(java.util.List) Logger(org.apache.logging.log4j.Logger) Stream(java.util.stream.Stream) Entry(java.util.Map.Entry) Optional(java.util.Optional) Preconditions(com.google.common.base.Preconditions) LogManager(org.apache.logging.log4j.LogManager) Collections(java.util.Collections) HostUnreachableException(com.eightkdata.mongowp.exceptions.HostUnreachableException) NotThreadSafe(javax.annotation.concurrent.NotThreadSafe) OpTime(com.eightkdata.mongowp.OpTime) MemberConfig(com.torodb.mongodb.commands.pojos.MemberConfig)

Example 2 with MemberConfig

use of com.torodb.mongodb.commands.pojos.MemberConfig in project torodb by torodb.

the class TopologyCoordinator method chooseNewSyncSource.

/**
   * Chooses and sets a new sync source, based on our current knowledge of the world.
   *
   * @return the new sync source or {@link Optional#empty()} if we cannot calculate a new sync
   *         source yet
   */
@Nonnull
Optional<HostAndPort> chooseNewSyncSource(Instant now, Optional<OpTime> lastOpApplied) {
    // if we have a target we've requested to sync from, use it
    if (_forceSyncSourceIndex != -1) {
        assert _forceSyncSourceIndex < _rsConfig.getMembers().size();
        HostAndPort syncSource = _rsConfig.getMembers().get(_forceSyncSourceIndex).getHostAndPort();
        _syncSource = Optional.of(syncSource);
        _forceSyncSourceIndex = -1;
        String msg = "syncing from: " + syncSource + " by request";
        LOGGER.info(msg);
        return _syncSource;
    }
    // wait for 2N pings before choosing a sync target
    if (_hbdata == null) {
        //we dont have a repl config yet
        assert _rsConfig == null;
        return Optional.empty();
    }
    int needMorePings = _hbdata.size() * 2 - getTotalPings();
    if (needMorePings > 0) {
        LOGGER.info("Waiting for {}  pings from other members before syncing", needMorePings);
        _syncSource = Optional.empty();
        return _syncSource;
    }
    // If we are only allowed to sync from the primary, set that
    if (!_rsConfig.isChainingAllowed()) {
        if (_currentPrimaryIndex == -1) {
            LOGGER.warn("Cannot select sync source because chaining is not allowed and primary " + "is unknown/down");
            _syncSource = Optional.empty();
            return _syncSource;
        } else if (isBlacklistedMember(getCurrentPrimaryMember(), now)) {
            LOGGER.warn("Cannot select sync source because chaining is not allowed and " + "primary is not currently accepting our updates");
            _syncSource = Optional.empty();
            return _syncSource;
        } else {
            HostAndPort syncSource = _rsConfig.getMembers().get(_currentPrimaryIndex).getHostAndPort();
            _syncSource = Optional.of(syncSource);
            String msg = "syncing from primary: " + syncSource;
            LOGGER.info(msg);
            return _syncSource;
        }
    }
    // find the member with the lowest ping time that is ahead of me
    // Find primary's oplog time. Reject sync candidates that are more than
    // maxSyncSourceLagSecs seconds behind.
    OpTime primaryOpTime;
    if (_currentPrimaryIndex != -1) {
        primaryOpTime = _hbdata.get(_currentPrimaryIndex).getOpTime();
        assert primaryOpTime != null;
    } else {
        // choose a time that will exclude no candidates, since we don't see a primary
        primaryOpTime = OpTime.ofSeconds(_maxSyncSourceLagSecs);
    }
    if (primaryOpTime.getSecs() < _maxSyncSourceLagSecs) {
        // erh - I think this means there was just a new election
        // and we don't yet know the new primary's optime
        primaryOpTime = OpTime.ofSeconds(_maxSyncSourceLagSecs);
    }
    OpTime oldestSyncOpTime = OpTime.ofSeconds(primaryOpTime.getSecs() - _maxSyncSourceLagSecs);
    Optional<MemberConfig> newSyncSourceMember = lookForSyncSource(now, lastOpApplied, true, oldestSyncOpTime);
    if (!newSyncSourceMember.isPresent()) {
        newSyncSourceMember = lookForSyncSource(now, lastOpApplied, false, oldestSyncOpTime);
    }
    if (!newSyncSourceMember.isPresent()) {
        // Did not find any members to sync from
        String msg = "could not find member to sync from";
        // Only log when we had a valid sync source before
        if (_syncSource.isPresent()) {
            LOGGER.info(msg);
        }
        _syncSource = Optional.empty();
        return _syncSource;
    } else {
        _syncSource = Optional.of(newSyncSourceMember.get().getHostAndPort());
        LOGGER.info("syncing from: {}", _syncSource.get());
        return _syncSource;
    }
}
Also used : HostAndPort(com.google.common.net.HostAndPort) OpTime(com.eightkdata.mongowp.OpTime) MemberConfig(com.torodb.mongodb.commands.pojos.MemberConfig) Nonnull(javax.annotation.Nonnull)

Example 3 with MemberConfig

use of com.torodb.mongodb.commands.pojos.MemberConfig in project torodb by torodb.

the class TopologyCoordinator method processHeartbeatResponse.

/**
   * Processes a heartbeat response from "target" that arrived around "now", having spent
   * "networkRoundTripTime" millis on the network.
   * <p>
   * Updates internal topology coordinator state, and returns instructions about what action to take
   * next.
   * <p>
   * If the next action is {@link HeartbeatResponseAction#makeNoAction() "NoAction"} then nothing
   * has to be done.
   * <p>
   * If the next action indicates {@link HeartbeatResponseAction#makeReconfigAction() "Reconfig"},
   * the caller should verify the configuration in hbResponse is acceptable, perform any other
   * reconfiguration actions it must, and call
   * {@link #updateConfig(
   * com.eightkdata.mongowp.mongoserver.api.safe.library.v3m0.pojos.ReplicaSetConfig,
   * java.time.Instant, com.eightkdata.mongowp.OpTime) updateConfig}
   * with the appropiate arguments.
   * <p>
   * This call should be paired (with intervening network communication) with a call to
   * prepareHeartbeatRequest for the same "target".
   *
   * @param now                  the aproximated time when the response has been recived
   * @param networkRoundTripTime the time spent on network
   * @param target               the host that send the respond
   * @param hbResponse
   */
HeartbeatResponseAction processHeartbeatResponse(Instant now, Duration networkRoundTripTime, HostAndPort target, RemoteCommandResponse<ReplSetHeartbeatReply> hbResponse) {
    PingStats hbStats = getPingOrDefault(target);
    Preconditions.checkState(hbStats.getLastHeartbeatStartDate() != null, "It seems that a hb " + "response has been recived before it has been prepared");
    if (!hbResponse.isOk()) {
        hbStats.miss();
    } else {
        hbStats.hit(networkRoundTripTime);
    }
    boolean isUnauthorized = (hbResponse.getErrorCode() == ErrorCode.UNAUTHORIZED) || (hbResponse.getErrorCode() == ErrorCode.AUTHENTICATION_FAILED);
    Duration alreadyElapsed = Duration.between(hbStats.getLastHeartbeatStartDate(), now);
    Duration nextHeartbeatDelay;
    // determine next start time
    if (_rsConfig != null && (hbStats.getNumFailuresSinceLastStart() <= MAX_HEARTBEAT_RETRIES) && (alreadyElapsed.toMillis() < _rsConfig.getHeartbeatTimeoutPeriod())) {
        if (isUnauthorized) {
            nextHeartbeatDelay = HEARTBEAT_INTERVAL;
        } else {
            nextHeartbeatDelay = Duration.ZERO;
        }
    } else {
        nextHeartbeatDelay = HEARTBEAT_INTERVAL;
    }
    Optional<ReplSetHeartbeatReply> commandReply = hbResponse.getCommandReply();
    if (hbResponse.isOk() && commandReply.get().getConfig().isPresent()) {
        long currentConfigVersion = _rsConfig != null ? _rsConfig.getConfigVersion() : -2;
        ReplicaSetConfig newConfig = commandReply.get().getConfig().get();
        assert newConfig != null;
        if (newConfig.getConfigVersion() > currentConfigVersion) {
            HeartbeatResponseAction nextAction = HeartbeatResponseAction.makeReconfigAction().setNextHeartbeatDelay(nextHeartbeatDelay);
            return nextAction;
        } else {
            // target erroneously sent us one, even through it isn't newer.
            if (newConfig.getConfigVersion() < currentConfigVersion) {
                LOGGER.debug("Config version from heartbeat was older than ours.");
                LOGGER.trace("Current config: {}. Config from heartbeat: {}", _rsConfig, newConfig);
            } else {
                LOGGER.trace("Config from heartbeat response was same as ours.");
            }
        }
    }
    // so return early.
    if (_rsConfig == null) {
        HeartbeatResponseAction nextAction = HeartbeatResponseAction.makeNoAction();
        nextAction.setNextHeartbeatDelay(nextHeartbeatDelay);
        return nextAction;
    }
    OptionalInt memberIndexOpt = _rsConfig.findMemberIndexByHostAndPort(target);
    if (!memberIndexOpt.isPresent()) {
        LOGGER.debug("replset: Could not find {} in current config so ignoring --" + " current config: {}", target, _rsConfig);
        HeartbeatResponseAction nextAction = HeartbeatResponseAction.makeNoAction();
        nextAction.setNextHeartbeatDelay(nextHeartbeatDelay);
        return nextAction;
    }
    assert memberIndexOpt.isPresent();
    int memberIndex = memberIndexOpt.getAsInt();
    MemberHeartbeatData hbData = _hbdata.get(memberIndex);
    assert hbData != null;
    MemberConfig member = _rsConfig.getMembers().get(memberIndex);
    if (!hbResponse.isOk()) {
        if (isUnauthorized) {
            LOGGER.debug("setAuthIssue: heartbeat response failed due to authentication" + " issue for member _id: {}", member.getId());
            hbData.setAuthIssue(now);
        } else if (hbStats.getNumFailuresSinceLastStart() > MAX_HEARTBEAT_RETRIES || alreadyElapsed.toMillis() >= _rsConfig.getHeartbeatTimeoutPeriod()) {
            LOGGER.debug("setDownValues: heartbeat response failed for member _id:{}" + ", msg: {}", member.getId(), hbResponse.getErrorDesc());
            hbData.setDownValues(now, hbResponse.getErrorDesc());
        } else {
            LOGGER.trace("Bad heartbeat response from {}; trying again; Retries left: {}; " + "{} ms have already elapsed", target, MAX_HEARTBEAT_RETRIES - hbStats.getNumFailuresSinceLastStart(), alreadyElapsed.toMillis());
        }
    } else {
        ReplSetHeartbeatReply nonNullReply = commandReply.get();
        LOGGER.trace("setUpValues: heartbeat response good for member _id:{}, msg:  {}", member.getId(), nonNullReply.getHbmsg());
        hbData.setUpValues(now, member.getHostAndPort(), nonNullReply);
    }
    HeartbeatResponseAction nextAction = updateHeartbeatDataImpl(memberIndex, now);
    nextAction.setNextHeartbeatDelay(nextHeartbeatDelay);
    return nextAction;
}
Also used : MemberHeartbeatData(com.torodb.mongodb.commands.pojos.MemberHeartbeatData) ReplSetHeartbeatReply(com.torodb.mongodb.commands.signatures.internal.ReplSetHeartbeatReply) ReplicaSetConfig(com.torodb.mongodb.commands.pojos.ReplicaSetConfig) Duration(java.time.Duration) OptionalInt(java.util.OptionalInt) MemberConfig(com.torodb.mongodb.commands.pojos.MemberConfig)

Example 4 with MemberConfig

use of com.torodb.mongodb.commands.pojos.MemberConfig in project torodb by torodb.

the class TopologyCoordinator method shouldChangeSyncSource.

/**
   * Determines if a new sync source should be chosen, if a better candidate sync source is
   * available.
   *
   * It returns true if there exists a viable sync source member other than our current source,
   * whose oplog has reached an optime greater than the max sync source lag later than current
   * source's. It can return true in other scenarios (like if {@link #setForceSyncSourceIndex(int) }
   * has been called or if we don't have a current sync source.
   *
   * @param now is used to skip over currently blacklisted sync sources.
   * @return
   */
boolean shouldChangeSyncSource(HostAndPort currentSource, Instant now) {
    // If the user requested a sync source change, return true.
    if (_forceSyncSourceIndex != -1) {
        return true;
    }
    OptionalInt currentMemberIndex = _rsConfig.findMemberIndexByHostAndPort(currentSource);
    if (!currentMemberIndex.isPresent()) {
        return true;
    }
    assert _hbdata.get(currentMemberIndex.getAsInt()) != null;
    OpTime currentOpTime = _hbdata.get(currentMemberIndex.getAsInt()).getOpTime();
    if (currentOpTime == null) {
        // change.
        return false;
    }
    long currentSecs = currentOpTime.getSecs();
    long goalSecs = currentSecs + _maxSyncSourceLagSecs;
    for (int i = 0; i < _hbdata.size(); i++) {
        MemberHeartbeatData it = _hbdata.get(i);
        MemberConfig candidateConfig = _rsConfig.getMembers().get(i);
        OpTime itOpTime = it.getOpTime();
        if (itOpTime != null && it.isUp() && it.getState().isReadable() && !isBlacklistedMember(candidateConfig, now) && goalSecs < itOpTime.getSecs()) {
            LOGGER.info("changing sync target because current sync target's most recent OpTime " + "is {}  which is more than {} seconds behind member {} whose most recent " + "OpTime is {} ", currentOpTime, _maxSyncSourceLagSecs, candidateConfig.getHostAndPort(), itOpTime);
            return true;
        }
    }
    return false;
}
Also used : MemberHeartbeatData(com.torodb.mongodb.commands.pojos.MemberHeartbeatData) OptionalInt(java.util.OptionalInt) OpTime(com.eightkdata.mongowp.OpTime) MemberConfig(com.torodb.mongodb.commands.pojos.MemberConfig)

Example 5 with MemberConfig

use of com.torodb.mongodb.commands.pojos.MemberConfig in project torodb by torodb.

the class TopologyCoordinator method executeReplSetSyncFrom.

ReplSetSyncFromReply executeReplSetSyncFrom(ErrorCode status, HostAndPort target, OpTime lastOpApplied) throws MongoException {
    if (status == ErrorCode.CALLBACK_CANCELED) {
        throw new ShutdownInProgressException("replication system is shutting down");
    }
    final HostAndPort syncFromRequested = target;
    MemberConfig targetConfig = null;
    int targetIndex;
    for (targetIndex = 0; targetIndex < _rsConfig.getMembers().size(); targetIndex++) {
        MemberConfig it = _rsConfig.getMembers().get(targetIndex);
        if (it.getHostAndPort().equals(target)) {
            targetConfig = it;
            break;
        }
    }
    if (targetConfig == null) {
        throw new NodeNotFoundException("Could not find member \"" + target + "\" in replica set");
    }
    if (targetConfig.isArbiter()) {
        throw new InvalidOptionsException("Cannot sync from \"" + target + "\" because it is an arbiter");
    }
    String warning = null;
    MemberHeartbeatData hbdata = _hbdata.get(targetIndex);
    if (hbdata.isAuthIssue()) {
        throw new UnauthorizedException("not authorized to communicate with " + target);
    }
    if (hbdata.getHealth() == Health.UNREACHABLE) {
        throw new HostUnreachableException("I cannot reach the requested member: " + target);
    }
    assert hbdata.getOpTime() != null;
    if (hbdata.getOpTime().getSecs() + 10 < lastOpApplied.getSecs()) {
        LOGGER.warn("attempting to sync from {}, but its latest opTime is {} and ours is {} " + "so this may not work", target, hbdata.getOpTime().getSecs(), lastOpApplied.getSecs());
        warning = "requested member \"" + target + "\" is more than 10 seconds behind us";
    }
    HostAndPort prevSyncSource = getSyncSourceAddress().orElse(null);
    setForceSyncSourceIndex(targetIndex);
    return new ReplSetSyncFromReply(prevSyncSource, syncFromRequested, warning);
}
Also used : MemberHeartbeatData(com.torodb.mongodb.commands.pojos.MemberHeartbeatData) HostAndPort(com.google.common.net.HostAndPort) NodeNotFoundException(com.eightkdata.mongowp.exceptions.NodeNotFoundException) InvalidOptionsException(com.eightkdata.mongowp.exceptions.InvalidOptionsException) ShutdownInProgressException(com.eightkdata.mongowp.exceptions.ShutdownInProgressException) HostUnreachableException(com.eightkdata.mongowp.exceptions.HostUnreachableException) UnauthorizedException(com.eightkdata.mongowp.exceptions.UnauthorizedException) ReplSetSyncFromReply(com.torodb.mongodb.commands.signatures.repl.ReplSetSyncFromCommand.ReplSetSyncFromReply) MemberConfig(com.torodb.mongodb.commands.pojos.MemberConfig)

Aggregations

MemberConfig (com.torodb.mongodb.commands.pojos.MemberConfig)6 MemberHeartbeatData (com.torodb.mongodb.commands.pojos.MemberHeartbeatData)5 OpTime (com.eightkdata.mongowp.OpTime)3 HostAndPort (com.google.common.net.HostAndPort)3 OptionalInt (java.util.OptionalInt)3 HostUnreachableException (com.eightkdata.mongowp.exceptions.HostUnreachableException)2 InvalidOptionsException (com.eightkdata.mongowp.exceptions.InvalidOptionsException)2 NodeNotFoundException (com.eightkdata.mongowp.exceptions.NodeNotFoundException)2 ShutdownInProgressException (com.eightkdata.mongowp.exceptions.ShutdownInProgressException)2 UnauthorizedException (com.eightkdata.mongowp.exceptions.UnauthorizedException)2 ReplicaSetConfig (com.torodb.mongodb.commands.pojos.ReplicaSetConfig)2 ReplSetHeartbeatReply (com.torodb.mongodb.commands.signatures.internal.ReplSetHeartbeatReply)2 ReplSetSyncFromReply (com.torodb.mongodb.commands.signatures.repl.ReplSetSyncFromCommand.ReplSetSyncFromReply)2 Duration (java.time.Duration)2 Nonnull (javax.annotation.Nonnull)2 ErrorCode (com.eightkdata.mongowp.ErrorCode)1 RemoteCommandResponse (com.eightkdata.mongowp.client.core.MongoConnection.RemoteCommandResponse)1 MongoException (com.eightkdata.mongowp.exceptions.MongoException)1 Preconditions (com.google.common.base.Preconditions)1 UnsignedInteger (com.google.common.primitives.UnsignedInteger)1