-
Notifications
You must be signed in to change notification settings - Fork 295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Increment cross regional duplicate tokens to replicate the policy we have been applying manually. #1048
Increment cross regional duplicate tokens to replicate the policy we have been applying manually. #1048
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -155,30 +155,53 @@ public PriamInstance retriableCall() throws Exception { | |
set(100, 100); | ||
logger.info("Trying to generate a new token"); | ||
sleeper.sleep(new Random().nextInt(15000)); | ||
String myRegion = myInstanceInfo.getRegion(); | ||
// this offset ensures the nodes are spread far away from the other regions. | ||
int regionOffset = tokenManager.regionOffset(myRegion); | ||
String myRac = myInstanceInfo.getRac(); | ||
List<String> racs = config.getRacs(); | ||
int mySlot = | ||
factory.getAllIds(config.getAppName()) | ||
.stream() | ||
.filter(i -> i.getRac().equals(myRac)) | ||
.map(PriamInstance::getId) | ||
.max(Integer::compareTo) | ||
.map(id -> racs.size() + Math.max(id, regionOffset) - regionOffset) | ||
.orElseGet( | ||
() -> { | ||
Preconditions.checkState(racs.contains(myRac)); | ||
return racs.indexOf(myRac); | ||
}); | ||
int instanceCount = membership.getRacCount() * membership.getRacMembershipSize(); | ||
String newToken = tokenManager.createToken(mySlot, instanceCount, myRegion); | ||
return createToken(mySlot + regionOffset, newToken); | ||
return generateNewToken(); | ||
} | ||
}.call(); | ||
} | ||
|
||
@VisibleForTesting | ||
PriamInstance generateNewToken() { | ||
String myRegion = myInstanceInfo.getRegion(); | ||
// this offset ensures the nodes are spread far away from the other regions. | ||
int regionOffset = tokenManager.regionOffset(myRegion); | ||
String myRac = myInstanceInfo.getRac(); | ||
List<String> racs = config.getRacs(); | ||
ImmutableSet<PriamInstance> allIds = factory.getAllIds(config.getAppName()); | ||
int mySlot = | ||
allIds.stream() | ||
.filter(i -> i.getRac().equals(myRac)) | ||
.map(PriamInstance::getId) | ||
.max(Integer::compareTo) | ||
.map(id -> racs.size() + Math.max(id, regionOffset) - regionOffset) | ||
.orElseGet( | ||
() -> { | ||
Preconditions.checkState(racs.contains(myRac)); | ||
return racs.indexOf(myRac); | ||
}); | ||
int instanceCount = membership.getRacCount() * membership.getRacMembershipSize(); | ||
String newToken = tokenManager.createToken(mySlot, instanceCount, myRegion); | ||
while (newTokenIsADuplicate(newToken, allIds)) { | ||
newToken = new BigInteger(newToken).add(BigInteger.ONE).toString(); | ||
} | ||
return createToken(mySlot + regionOffset, newToken); | ||
} | ||
|
||
private boolean newTokenIsADuplicate(String newToken, ImmutableSet<PriamInstance> instances) { | ||
for (PriamInstance priamInstance : instances) { | ||
if (newToken.equals(priamInstance.getToken())) { | ||
if (myInstanceInfo.getRegion().equals(priamInstance.getDC())) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little unclear as to why we throw an Also, I'm having trouble tracing the entire code path given the multiple impls, but is it possible that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Duplicates across DCs can happen if the region hash is off by one and the tokens between regions are off by one in the opposite direction. That scenario can result if the cluster has been doubled using Priam's doubling facilities. The purpose of this PR is to adapt to this scenario. That said, there should never be in-region duplicates and if there are we need to fail immediately. In that case we should not increment until there are no duplicates because that will lead to a situation where tokens are one apart within the same region. That would cause a data imbalance.
This is an interesting concern, but I don't believe it is a threat. The only place we ever convert away from AWS region names is in an internal tuning use case. I can go into greater depth about that this morning if you're interested. |
||
throw new IllegalStateException( | ||
String.format( | ||
"Trying to add token %s to %s but it already exists in %s", | ||
newToken, myInstanceInfo.getRegion(), priamInstance.getDC())); | ||
} | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private String getReplacedIpForAssignedToken( | ||
ImmutableSet<PriamInstance> aliveInstances, PriamInstance instance) | ||
throws TokenRetrieverUtils.GossipParseException { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not strictly related to your PR but: do we need to worry about atomicity between the check here and the create below? E.g. if this is backed by Cassandra, do we need to use LWTs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internally we don't use LWTs, but Contention is accounted for by "acquiring a lock" in a separate table before writing. Note that contention is managed based on the token's relative position within the region rather than the token value itself. That prevents this change introducing the possibility of multiple tokens that are one apart in the same region.