-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Find Errant GTIDs #6296
Find Errant GTIDs #6296
Conversation
…tions. Unfortunately the data structure of other GTIDSets does not allow for us to represent a proper diff for those flavors. Signed-off-by: Peter Farr <[email protected]>
Signed-off-by: Peter Farr <[email protected]>
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.
Hi @PrismaPhonic! Saw your request for preliminary reviews for this draft.
Thought I'd share a bit of insight since I similarly developed errant GTID detection in orchestrator
(work spread over openark/orchestrator#607, openark/orchestrator#617, openark/orchestrator#707 and probably more).
I apologize in advance that this turns a bit lengthy.
- First thing to consider what the reference (base, or
other
) GTID is and what it means.
- Naively, that would be the master of the replica being examined. Compare GTID with the immediate master and get the errant value.
This unfortunately is only half correct, since the immediate master may in itself be a replica. The immediate master may itself have errant GTID, thereby making all of its own replicas (including the one we're looking at) have errant GTIDs. But as compared with its direct master, maybe our replica does not have an errant GTID. It is a matter of definition to declare whether our replica does or does not have errant GTID. That may depend on what operations we wish to achieve. Some refactoring operations involving our replica may make the problem worse, others won't. - Compare with the "real" master of the topology? This is more correct, because the master is the source of truth. But then, we may want to understand where the errant transaction originated. On our replica? Or, on its immediate or any of its ancestry masters? Back to previous clause.
Whichever we decide, this leads us to the question of "when and how" we collected the executed_gtid_set
for our replica and for the base.
Say we first sample the base. For simplicity, let's assume it's the direct master. If we happen to first sample the master and then the replica, then it's possible that in between our sampling, new transactions will have been applied on the master and propagated to the replica. In which case, by the time we read the GTID set from the replica, it makes the appearance of having an errant transaction, although in reality nothing is wrong.
This is why in evaluating the diff, we must disregard any part of the GTID set that contains the UUID of the replica's master or any of its ancestors. It is safe to skip a UUID that belongs to an ancestor because there is no way for our replica to have a transaction with such UUID that is errant. The transaction must have originated from an ancestor, therefore it is applied on the ancestor.
So all this lengthy preface is to note that other GTIDSet
should come with context, and by comparing the difference of two GTID sets does not imply an errant transaction.
And all of the above may be premature, since current code merely runs a Difference
method; but I thought I'd throw this as heads up.
- Asking out of complete ignorance. In
orchestrator
my choice was to not evaluate the difference in code, but instead delegate it to MySQL by executing GTID_SUBTRACT(). The line of thought was that if I wanted to run an operation on some replica and was able to read itsexecuted_gtid_set
, then I'd also have access to runGTID_SUBTRACT()
. I'm merely wondering if the same line of thought can be applied here and if it at all makes sense to contact MySQL to evaluate the diff. Obviously, the downside is the need to connect to a MySQL server.
Hi @shlomi-noach! Thank you very much for your in depth reply. Since it's pretty late over here I'll give a brief reply for now and try to expand tomorrow if necessary.
Thanks for the great reply! If you have some time for a video chat later this week, or early next week I would love to pick your brain more about this. |
That makes sense. Also, in the context of a failover, I guess you will only be looking at 1st tier replicas? That simplifies things.
That's basically what
Makes sense.
Sounds good! One thing to note is whether you necessarily intend to wait for the replica to consume its relay logs (hence, its |
Signed-off-by: Peter Farr <[email protected]>
Signed-off-by: Peter Farr <[email protected]>
… possible combinations we might see when comparing two intervals. Expanded testing and filled out comments for clarity Signed-off-by: Peter Farr <[email protected]>
… used for Mysql56GTIDSet anyways. Signed-off-by: Peter Farr <[email protected]>
|
||
// Make a fresh, empty set to hold the new value. | ||
// This function is not supposed to modify the original set. | ||
differenceSet := make(Mysql56GTIDSet) |
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.
if this is something that is used in the hot path, and will contain more than just a few items, you might want to consider using make with a given size, to avoid resizing hash maps
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.
I was thinking about that, but we don't know yet what the size of the differenceSet
will be. Are you thinking that we should just make it the same size as the receiver to cover all potential SIDs we might find that have valid diffs?
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.
⭐ Really nice code, well commented code
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.
The rewrite looks good!
sid1: []interval{{20, 30}, {35, 39}, {40, 53}, {55, 75}}, | ||
sid2: []interval{{1, 7}, {20, 50}, {60, 70}}, | ||
sid4: []interval{{1, 30}}, | ||
} |
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.
Can you please add the following two test cases:
- Where the result range is a single value or single-value range (e.g.
{1, 7}
-{2, 6}
) - Where the result range is empty (e.g.
{2, 10}, {20-30}
-{1, 40}
)
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.
2 is technically covered by {20, 30} - {20, 30} although that's a very trivial case. I'll include yours. Great suggestions!
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.
Added both, and pushed up. Tests still passing :-)
…onger call the second stack s2, and instead just called it otherIntervals. Signed-off-by: Peter Farr <[email protected]>
…Ds method on SlaveStatus. I think this belongs here because it's clear that we are referring to ErrantGTIDs of the receiver, and it gives us access to the MasterUUID when comparing relay log positions. Also added testing to verify correctness of new method. Signed-off-by: Peter Farr <[email protected]>
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.
Concur with @systay, nicely written and well-commented.
|
||
// Found server id match between sets, so now we need to subtract each interval. | ||
var diffIntervals []interval | ||
advance := func() bool { |
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.
It might be possible to replace advance and advanceOther with a single func and passing the relevant []interval to it (and having it return the 0th element for later use).
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.
I had wanted to do that at first. The reason why I choose to use two different closures is that what we do for advance
and advanceOther
is different. In the case that we advance
we always want to push a new interval onto diffIntervals
. In the case that we advanceOther
we do not. Unless I'm missing something, I don't think these can be merged into a single helper method.
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.
That is why I suggested returning the 0th element. As of now you always call advance
with intervals
and advanceOther
with otherIntervals
. Depending on which one you are operating on, you can do different things.
For instance, the first time you use a common advance func could look like this:
if i, ok := advance(intervals); ok {
diffIntervals = append(diffIntervals, i)
}
else {
continue
}
if _, ok := advance(otherIntervals); !ok {
differenceSet[sid] = intervals
continue
}
I'm not going to insist on this. If you feel the current version is more readable, that is fine.
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.
Ahhhh I see what you're saying. Yes, we could do it this way - although then we would have this kind of code in every switch branch. To me it reads cleaner to contain this logic into advance
and advanceOther
but this is certainly another way we could do it - and not worse. Likely just a matter of personal preference. Thanks for the feedback!
Signed-off-by: Peter Farr <[email protected]>
…ver to a new set rather than deleting master sid from existing set. Signed-off-by: Peter Farr <[email protected]>
Signed-off-by: Peter Farr <[email protected]>
…aster sid is consistent for both receiver and supplied SlaveStatus' Signed-off-by: Peter Farr <[email protected]>
Thanks for all of the great feedback everyone! I've finished addressing all existing feedback. |
This PR adds a function called
FindErrantGTIDs
which finds the errant GTIDs for a given replica if all other replica slave status' are supplied as well.This includes:
Mysql56GTIDSet
which can be used to find the difference between the receiver GTIDSet, and the supplied GTIDSet.FindErrantGTIDs
method onSlaveStatus
which can tell us the receivers errant GTIDs, when we've supplied a comprehensive list of all knownSlaveStatus
for replicas of the same shard. I choose to make this a method onSlaveStatus
rather than a standalone function because I believe it makes it more clear that we are finding the errant GTIDs of the receiverSlaveStatus
.Related Issue: #6206