-
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
consistent lookup vindex #4861
consistent lookup vindex #4861
Conversation
SafeSession and underlying session always non-nil. Append makes session not autocommitable. Remove legacy mode. Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Added two new shard sessions for commit ordering: pre and post. Added API to set the commit order and changed tx conn to honor it. Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Add the ability to request the owner column names, which will be used to check for presence of owner row by the vindex. Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
Some people may still rely on the sequential commit order of the normal transactions. So, it's better not to parallelize that part. Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
newSession.Autocommit = true | ||
newSession.Warnings = nil |
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.
Why are the warnings cleared here?
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.
This is used as a private session within the context of an outer session, specifically for vindex autocommit. The warnings of the outer one are untouched. After this execution finishes, we could consider appending any new warnings generated back to the outer session. But that may or may not be right. We should look at some real use cases before making this decision.
Maybe we should rename this to NewInnerSession
?
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.
Overall this looks sound to me.
A couple high level comments:
It would have been good to have separated out the legacyAutocommit deprecation and scattered bugfixes into a separate PR to ease the review and improve clarity.
Overall I am a bit concerned about over-promising magic by using the name "consistent_lookup". At the end of the day there's a tradeoff between potentially leaving dangling rows in the lookup table (like this one does) or potentially missing rows (like the other one does), but there's no perfect way to ensure consistency when we have best effort distributed transactions as the underlying primitive (ignoring 2PC of course).
So... maybe we should name this variant something like covering_lookup
or super_lookup
or something like that? Something to be explicit about how it might fail.
@@ -740,7 +740,9 @@ func (stc *ScatterConn) multiGo( | |||
oneShard := func(rs *srvtopo.ResolvedShard, i int) { | |||
var err error | |||
startTime, statsKey := stc.startAction(name, rs.Target) | |||
defer stc.endAction(startTime, allErrors, statsKey, &err, nil) | |||
// Send a dummy session. | |||
// TODO(sougou): plumb a real session through this call. |
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.
What are the implications here? I'm a bit confused about where the multiGo parts actually come into play?
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 previously made a dumb decision that allowed a SafeSession to be nil, which required me to check for it on every method. It turns out that this is the only use case where the nil
value is used. So, I changed it to send an empty session which means the same thing.
The comment is a reminder to look up the stack to see if the real session could to be plumbed through. However, I suspect that we'll just remove this code when we get rid of V2.
return err | ||
} | ||
|
||
// Retain backward compatibility on commit order for the normal session. |
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 seems to me we should be able to mark the session as to whether or not we can commit in parallel -- if it's only the legacy vindexes that care about the ordering, then maybe we should flag the session when adding a transaction id whether or not the commit order matters?
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.
Yeah. I want to do this as a separate feature.
go/vt/vtgate/vcursor_impl.go
Outdated
return vc.executeByOrder(method, query, bindVars, isDML, commitOrderPost) | ||
} | ||
|
||
func (vc *vcursorImpl) executeByOrder(method string, query string, bindVars map[string]*querypb.BindVariable, isDML bool, co commitOrder) (*sqltypes.Result, error) { |
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.
Instead of widening the VCursor interface into Execute, ExecutePre, and ExecutePost, it seems cleaner to make this the signature for a combined VindexExecute
function.
This solves a couple problems IMO -- one is that callers already need to know Execute vs ExecutePre vs ExecutePost, and so exposing them all in one function instead of three seems reasonable.
The second is that I have always found it confusing that the VCursor interface defines a recursive Execute method that is called via the Executor only through the vindex functions. Calling it VindexExecute
or ExecuteForVindex
or some such would be clearer.
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.
Moreover we could consider folding ExecuteAutocommit in here as well.
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 went this route because we otherwise introduce a circular dependency on commitOrder
: vtgate calls engine, which calls into vindexes, and the vindex calls back into VCursor (as interface).
We could move a commitOrder
to vindexes and export it. Do you think that will still remain readable?
Or, I just thought of this: define it in the proto. I think proto looks like the best place.
@@ -91,9 +93,9 @@ func (lkp *lookupInternal) Verify(vcursor VCursor, ids, values []sqltypes.Value) | |||
var err error | |||
var result *sqltypes.Result | |||
if lkp.Autocommit { | |||
result, err = vcursor.ExecuteAutocommit("VindexVerify", lkp.ver, bindVars, true /* isDML */) | |||
result, err = vcursor.ExecuteAutocommit("VindexVerify", lkp.ver, bindVars, false /* isDML */) |
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.
This looks like it was just a bug found during the course of doing this cleanup?
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.
Yeah. But it became significant for the feature: these failures would mark the transaction as partially complete and abort the transaction.
go/vt/vtgate/vcursor_impl.go
Outdated
|
||
func (vc *vcursorImpl) executeByOrder(method string, query string, bindVars map[string]*querypb.BindVariable, isDML bool, co commitOrder) (*sqltypes.Result, error) { | ||
vc.safeSession.SetCommitOrder(co) | ||
defer vc.safeSession.SetCommitOrder(commitOrderNormal) |
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.
Although I'm not too worried about this, the way this is implemented seems like a potentially dangerous pattern since we need to match these two. It's not that different from Lock/Unlock, but an alternative might be to use a facade:
The underlying implementation would still have the three arrays of ShardSession of course, but instead of a state variable indicating which one should be used, the facade would override and put txns in the right list based on the order.
Then the Executor would take a Session
interface instead of directly taking a safeSession
object, and we'd call into it with something like
qr, err := vc.executor.Execute(vc.ctx, method, NewSessionForCommitOrder(vc.safeSession, co), vc.marginComments.Leading+query+vc.marginComments.Trailing, bindVars)
On balance, it's probably not worth it but figured I'd just share.
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.
This approach would have totally made sense if ShardSessions was a struct. Being a slice requires wrapping it, which makes the whole thing awkward.
case commitOrderPre: | ||
session.PreSessions = append(session.PreSessions, shardSession) | ||
case commitOrderPost: | ||
session.PostSessions = append(session.PostSessions, shardSession) |
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.
why isn't this:
session.PostSessions = append(shardSession, session.PostSessions)
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 three session variables are completely independent of each other. You could potentially have two transactions going to the same shard, one from each pool. If so, the ones in the Pre
session are committed first, and the ones from Post
are committed last.
} | ||
|
||
if session.autocommitState == autocommittable && len(session.ShardSessions) == 0 { | ||
if session.autocommitState == autocommittable { |
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.
autocommittable
is sometimes spelt with one t
like in the func SetAutoCommitable
. Do we want to change all instances to be consistent?
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'll rename the function.
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'll soon push fixes to address review comments.
As for naming, I would argue that it's consistent for the use case it's meant to fulfill. Our guidelines have always recommended that you treat it as a hidden index. If so, it fulfills all the criteria.
Ideally, calling it consistent
is bad naming. We should eventually deprecate the old lookups and call this one lookup
, because the consistency should be taken for granted.
@@ -740,7 +740,9 @@ func (stc *ScatterConn) multiGo( | |||
oneShard := func(rs *srvtopo.ResolvedShard, i int) { | |||
var err error | |||
startTime, statsKey := stc.startAction(name, rs.Target) | |||
defer stc.endAction(startTime, allErrors, statsKey, &err, nil) | |||
// Send a dummy session. | |||
// TODO(sougou): plumb a real session through this call. |
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 previously made a dumb decision that allowed a SafeSession to be nil, which required me to check for it on every method. It turns out that this is the only use case where the nil
value is used. So, I changed it to send an empty session which means the same thing.
The comment is a reminder to look up the stack to see if the real session could to be plumbed through. However, I suspect that we'll just remove this code when we get rid of V2.
return err | ||
} | ||
|
||
// Retain backward compatibility on commit order for the normal session. |
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.
Yeah. I want to do this as a separate feature.
go/vt/vtgate/vcursor_impl.go
Outdated
return vc.executeByOrder(method, query, bindVars, isDML, commitOrderPost) | ||
} | ||
|
||
func (vc *vcursorImpl) executeByOrder(method string, query string, bindVars map[string]*querypb.BindVariable, isDML bool, co commitOrder) (*sqltypes.Result, error) { |
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 went this route because we otherwise introduce a circular dependency on commitOrder
: vtgate calls engine, which calls into vindexes, and the vindex calls back into VCursor (as interface).
We could move a commitOrder
to vindexes and export it. Do you think that will still remain readable?
Or, I just thought of this: define it in the proto. I think proto looks like the best place.
go/vt/vtgate/vcursor_impl.go
Outdated
|
||
func (vc *vcursorImpl) executeByOrder(method string, query string, bindVars map[string]*querypb.BindVariable, isDML bool, co commitOrder) (*sqltypes.Result, error) { | ||
vc.safeSession.SetCommitOrder(co) | ||
defer vc.safeSession.SetCommitOrder(commitOrderNormal) |
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.
This approach would have totally made sense if ShardSessions was a struct. Being a slice requires wrapping it, which makes the whole thing awkward.
@@ -91,9 +93,9 @@ func (lkp *lookupInternal) Verify(vcursor VCursor, ids, values []sqltypes.Value) | |||
var err error | |||
var result *sqltypes.Result | |||
if lkp.Autocommit { | |||
result, err = vcursor.ExecuteAutocommit("VindexVerify", lkp.ver, bindVars, true /* isDML */) | |||
result, err = vcursor.ExecuteAutocommit("VindexVerify", lkp.ver, bindVars, false /* isDML */) |
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.
Yeah. But it became significant for the feature: these failures would mark the transaction as partially complete and abort the transaction.
The VCursor API is impressively simpler no. Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
The VCursor API is impressively simpler now. |
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
This implements #4855.
Design decisions
Beyond the high level approach described in the RFC. There are a few additional design decisions I had to make to get this feature working:
WantOwnerInfo
interface. If it does the vschema builder will give it the info needed for it to verify if the main row is present.Implementation
Based on the above decisions, there are changes in the following areas:
WantOwnerInfo
interface and have the builder set the owner info if a vindex implements it.