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;
});
}
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;
}
}
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;
}
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;
}
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);
}
Aggregations