kvserver/concurrency,tracing: add tracing tags while waiting for locks #74878
Conversation
d871ee2 to
cfdb3e9
Compare
|
@nvanbenschoten remind me pls - if a request is looking to acquire locks itself, then the |
0835e50 to
b32e587
Compare
| // 1m01s | ||
| // 1h05m01s | ||
| // 1d02h05m | ||
| func formatDuration(d time.Duration) string { |
There was a problem hiding this comment.
cockroach/pkg/util/humanizeutil/duration.go
Lines 20 to 29 in 6798516
| h.tBegin = timeutil.Now() | ||
| } | ||
| case waitElsewhere, waitQueueMaxLengthExceeded, doneWaiting: | ||
| h.sp.SetStatusTag(tagWaitKey, attribute.StringValue(s.key.String())) |
There was a problem hiding this comment.
pkg/util/tracing/crdbspan.go
Outdated
| defer s.mu.Unlock() | ||
| k := attribute.Key(key) | ||
| for i := range s.mu.tags { | ||
| if s.mu.tags[i].Key == k { |
There was a problem hiding this comment.
Should store them separately, otherwise you can remove regular tags, no?
|
Can you have someone from @cockroachdb/obs-inf-prs on this PR since they own tracing? I think there are probably some finer points to discuss. The overall idea LGTM. |
dhartunian
left a comment
There was a problem hiding this comment.
Reviewed 1 of 1 files at r1, 1 of 5 files at r2, all commit messages.
Reviewable status:complete! 0 of 0 LGTMs obtained (waiting on @AlexTalks, @andreimatei, @dhartunian, and @nvanbenschoten)
-- commits, line 14 at r2:
nit: waited
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 855 at r2 (raw file):
// A contentionEventHelper should be created when a request starts waiting in // the lock table (on what might turn out to be multiple locks). close() should // be called when the waiting is over.
Just curious in terms of API design, if this should mirror a lock with a start method to match close. If I create an instance of this struct without a start time, it seems like that would break things.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 862 at r2 (raw file):
// contentionStart marks the start of the contention tracked by this // contentionEventHelper. contentionStart time.Time
I'm a bit confused about the difference between tBegin below and contentionStart. Can you clarify the difference between "start of the contention" and "start of the contention event"? Also, is there a meaning to using both Begin and Start?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 944 at r2 (raw file):
func (h *contentionEventHelper) close() { if h.closed || h.sp == nil {
Do we ever set closed to true?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 948 at r2 (raw file):
} // Clear most tags. for _, tag := range []string{"lock_wait_key", "lock_wait_start", "lock_holder_txn"} {
Just checking my understanding here: These tags will only be visible while the span is actually waiting on the locks, right? So you have to "catch" the span while it's stuck in order to see these tags?
b32e587 to
0347f21
Compare
99cdca7 to
fc92a65
Compare
andreimatei
left a comment
There was a problem hiding this comment.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @AlexTalks, @dhartunian, @nvanbenschoten, and @tbg)
Previously, dhartunian (David Hartunian) wrote…
nit: waited
done
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 855 at r2 (raw file):
Previously, dhartunian (David Hartunian) wrote…
Just curious in terms of API design, if this should mirror a lock with a
startmethod to matchclose. If I create an instance of this struct without a start time, it seems like that would break things.
I've given it a constructor.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 862 at r2 (raw file):
Previously, dhartunian (David Hartunian) wrote…
I'm a bit confused about the difference between
tBeginbelow andcontentionStart. Can you clarify the difference between "start of the contention" and "start of the contention event"? Also, is there a meaning to using bothBeginandStart?
please see now; I've reworked this.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 927 at r2 (raw file):
Previously, tbg (Tobias Grieger) wrote…
The analog to #71622 could also be helpful here. I know, you're trying to get us to a place where we have no need for such stop-gaps, but still, it might get us places for the foreseeable future. I would certainly appreciate having this in the debug.zip (where the labels end up)
What if I promise to put trace registry dumps in the debug.zip? :)
Regardless, I'd rather not put that label in this patch. Let's discuss it separately.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 944 at r2 (raw file):
Previously, dhartunian (David Hartunian) wrote…
Do we ever set closed to
true?
we do now :)
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 948 at r2 (raw file):
Previously, dhartunian (David Hartunian) wrote…
Just checking my understanding here: These tags will only be visible while the span is actually waiting on the locks, right? So you have to "catch" the span while it's stuck in order to see these tags?
I've reworked this. Now, there is technically a single tag that stays with the span forever, and that span renders differently depending on whether the span is currently blocked versus when it's no longer blocked. In the latter case, it just has some aggregate info about the total amount of time spent waiting on locks.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 963 at r2 (raw file):
Previously, tbg (Tobias Grieger) wrote…
?cockroach/pkg/util/humanizeutil/duration.go
Lines 20 to 29 in 6798516
thanks, used that one
pkg/util/tracing/crdbspan.go, line 403 at r2 (raw file):
Previously, tbg (Tobias Grieger) wrote…
Should store them separately, otherwise you can remove regular tags, no?
yeah... I purposefully didn't go through that trouble. When no Otel tracer is attached, removing regular tags works just as well as removing "status tags", if one does it, and I think that's fine.
andreimatei
left a comment
There was a problem hiding this comment.
@AlexTalks can I please ask you for a review for the kv parts, and @dhartunian for the tracing library changes?
And @tbg, @nvanbenschoten, for whatever you feel like :).
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @AlexTalks, @dhartunian, @nvanbenschoten, and @tbg)
fc92a65 to
f204bd2
Compare
AlexTalks
left a comment
There was a problem hiding this comment.
Reviewed 9 of 9 files at r7, 1 of 1 files at r8, 3 of 3 files at r9, 5 of 7 files at r10.
Reviewable status:complete! 0 of 0 LGTMs obtained (waiting on @AlexTalks, @andreimatei, @dhartunian, @nvanbenschoten, and @tbg)
-- commits, line 22 at r9:
The changes make sense but this commit message may be a little confusing - as you mentioned, waitElsewhere is basically the same as waitFor, it's just that the lock table is under memory pressure (the number of locks tracked has exceeded configured limits for this replica). It might be that a slight rewording is necessary to make this clear here.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 896 at r10 (raw file):
closed bool // lockWait represents the time the span spent waiting on locks. If closed
the cumulative time the *request spent waiting on locks
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 906 at r10 (raw file):
// closed or if the contentionEventTracer has not yet been notified of the // first lock to wait on. curState *waitingState
I'm not sure it makes sense to additionally track this state on the contentionEventHelper when it seems state change tracking logic could be accomplished using local variables in WaitOn(..), no?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 931 at r10 (raw file):
t, ok := sp.GetLazyTag(tagContentionHelper) if ok { h = t.(*contentionEventTracer)
It may be worth discussing if having a per-span contentionEventTracer is the best way to do this, or if it would make sense to capture discrete events by contended txn/key and then be able to look at the individual contentions (and their durations).
You could potentially still have a per-span contentionEventTracer but collect each individual event in it (along with duration) so in ExpandToRecordingTags(..) you could still collect the cumulative duration of contentions (or, of course, render it a different way).
dhartunian
left a comment
There was a problem hiding this comment.
Reviewed 9 of 9 files at r7, 1 of 1 files at r8, 3 of 3 files at r9, 2 of 7 files at r10.
Reviewable status:complete! 0 of 0 LGTMs obtained (waiting on @AlexTalks, @andreimatei, @dhartunian, @nvanbenschoten, and @tbg)
-- commits, line 38 at r10:
I want to check my understanding of the data we're collecting and where/when it will get surfaced:
- When the span is waiting on a lock and we capture it in-flight, we'll get a snapshot of the
contentionEventTracerstate within via theExpandingTagmechanism. - If the span waited on locks in the past these each produce
ContentionEvents via theemit()call that add structured data to the span.
Does the contentionEventTracer leave data in its wake besides the structured ContentionEvents?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 846 at r10 (raw file):
// tagContentionHelper is the tracing span tag that the *contentionEventHelper // lives under. const tagContentionHelper = "contention_helper"
this should be content_tracer now?
Also a bunch of other cases of helper being used below that should be changed to tracer.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 865 at r10 (raw file):
// waiting on, if any, is not // counted. const tagNumLocks = "lock_num"
nit: can we name this to match the string?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 870 at r10 (raw file):
// waited on locks. If the span is currently waiting on a lock, the time it has // already waited on this lock is not reflected. const tagWaited = "lock_wait"
checking my understanding: this is recorded after the lock is released so that's why "current" wait time is not factored in?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 935 at r10 (raw file):
defer h.mu.Unlock() if !h.mu.closed { log.Fatalf(ctx, "unexpectedly found non-closed contentionEventTracer")
does this really warrant Fatal? Or is this more meant for catching errors during test?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 1028 at r10 (raw file):
// NB: we're guaranteed to have `s.{txn,key}` populated here. differentLock := false var firstLock bool
random q: why initialize these variables differently?
pkg/util/tracing/span_inner.go, line 168 at r10 (raw file):
} func (s *spanInner) SetLazyTag(key string, value interface{}, statusTag bool) *spanInner {
The API here expands quite a bit, do you think it's worth consolidating a bit? can "lazy" be a bool like statusTag?
f204bd2 to
a70534c
Compare
|
Sorry to let this sit; please give it another look. |
dhartunian
left a comment
There was a problem hiding this comment.
Much easier to follow. Thanks for making these changes. Just some nits re: unused methods being added.
Reviewed 9 of 9 files at r11, 1 of 1 files at r12, 3 of 3 files at r13, 3 of 7 files at r14, all commit messages.
Reviewable status:complete! 1 of 0 LGTMs obtained (waiting on @andreimatei, @dhartunian, @nvanbenschoten, and @tbg)
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 1004 at r10 (raw file):
// notify processes an event from the lock table. // compares the waitingState's active txn (if any) against the current // ContentionEvent (if any). If the they match, we are continuing to handle the
nit: the they
pkg/util/tracing/crdbspan.go, line 536 at r14 (raw file):
// clearTag removes a tag, if it exists. func (s *crdbSpan) clearTag(key string) {
This seems unused. Only called by spanInner.ClearStatusTag which is also unused.
pkg/util/tracing/span_inner.go, line 154 at r14 (raw file):
} func (s *spanInner) SetTag(key string, value attribute.Value, statusTag bool) *spanInner {
I don't see a usage that sets statusTag to true. Am I missing something?
pkg/util/tracing/span_inner.go, line 196 at r14 (raw file):
} func (s *spanInner) ClearStatusTag(key string) *spanInner {
Unused. Same as clearTag().
a70534c to
dbf2e89
Compare
andreimatei
left a comment
There was a problem hiding this comment.
Reviewable status:
complete! 1 of 0 LGTMs obtained (waiting on @AlexTalks, @dhartunian, @nvanbenschoten, and @tbg)
Previously, AlexTalks (Alex Sarkesian) wrote…
The changes make sense but this commit message may be a little confusing - as you mentioned,
waitElsewhereis basically the same aswaitFor, it's just that the lock table is under memory pressure (the number of locks tracked has exceeded configured limits for this replica). It might be that a slight rewording is necessary to make this clear here.
I added a few words, please see now
When the span is waiting on a lock and we capture it in-flight, we'll get a snapshot of the contentionEventTracer state within via the ExpandingTag mechanism.
Right. Except that you'll get some data from the ExpandingTag even if the span's recording is captured after the span finished waiting; see below.
If the span waited on locks in the past these each produce ContentionEvents via the emit() call that add structured data to the span.
Right. But note that, at least currently, the structured events are no-ops on non-recording spans, just like non-structured events.
Besides the structured events, the contentionEventTracer also counts the number of locks and the total time waited. See below.
Does the contentionEventTracer leave data in its wake besides the structured ContentionEvents?
It does. Or, rather, there's no "in its wake"; once created, the contentionEventTracer stays attached to the span as a LazyTag until span.Finish(). If/when the span's recording is collected, it will include the number of locks and the total time waited. If the recording is collected while the span is waiting on a lock, we get extra info about that lock.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 846 at r10 (raw file):
Previously, dhartunian (David Hartunian) wrote…
this should be
content_tracernow?Also a bunch of other cases of
helperbeing used below that should be changed totracer.
done
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 865 at r10 (raw file):
Previously, dhartunian (David Hartunian) wrote…
nit: can we name this to match the string?
I'd prefer not to, unless you feel strongly. I think it's useful for all these consts to have a common prefix, so that code completion groups them. When rendered as tags in a span, this consideration no longer applies.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 870 at r10 (raw file):
Previously, dhartunian (David Hartunian) wrote…
checking my understanding: this is recorded after the lock is released so that's why "current" wait time is not factored in?
that was the case, but I've now changed it to include the current current lock (if any)
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 896 at r10 (raw file):
Previously, AlexTalks (Alex Sarkesian) wrote…
the cumulative time the *request spent waiting on locks
done
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 906 at r10 (raw file):
Previously, AlexTalks (Alex Sarkesian) wrote…
I'm not sure it makes sense to additionally track this state on the
contentionEventHelperwhen it seems state change tracking logic could be accomplished using local variables inWaitOn(..), no?
well, curState is used in Render(), which is called async - there's no opportunity for the lockTableWaiterImpl to provide data in that case.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 931 at r10 (raw file):
Previously, AlexTalks (Alex Sarkesian) wrote…
It may be worth discussing if having a per-span
contentionEventTraceris the best way to do this, or if it would make sense to capture discrete events by contended txn/key and then be able to look at the individual contentions (and their durations).You could potentially still have a per-span
contentionEventTracerbut collect each individual event in it (along with duration) so inExpandToRecordingTags(..)you could still collect the cumulative duration of contentions (or, of course, render it a different way).
The point of only storing aggregate information (in addition to extra info about the current lock being waited on, if any), is so that all this tracing takes a fixed memory budget when recording is not enabled. When recording is enabled, we get the structured events after each lock.
You don't buy?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 935 at r10 (raw file):
Previously, dhartunian (David Hartunian) wrote…
does this really warrant
Fatal? Or is this more meant for catching errors during test?
I personally am in the crash-happy camp for bugs, on the argument that it generally leads to better and tighter code. If somehow getOrCreateContentionEventHelper were called with a span with an open contentionEventHelper, then either we forgot to close() it (in which case we're been counting bogus wait time), or we're somehow using the span for two concurrent wait table interactions, in which case they would step over each other. Both are a form of data corruption. Unless there's good reason to relax this, I'd rather generally assert that my data is sane.
You object?
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 1004 at r10 (raw file):
Previously, dhartunian (David Hartunian) wrote…
nit:
the they
done
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 1028 at r10 (raw file):
Previously, dhartunian (David Hartunian) wrote…
random q: why initialize these variables differently?
changed now
pkg/util/tracing/crdbspan.go, line 536 at r14 (raw file):
Previously, dhartunian (David Hartunian) wrote…
This seems unused. Only called by
spanInner.ClearStatusTagwhich is also unused.
removed, thanks
pkg/util/tracing/span_inner.go, line 168 at r10 (raw file):
Previously, dhartunian (David Hartunian) wrote…
The API here expands quite a bit, do you think it's worth consolidating a bit? can "lazy" be a bool like
statusTag?
please see now; I've simplified things
pkg/util/tracing/span_inner.go, line 154 at r14 (raw file):
Previously, dhartunian (David Hartunian) wrote…
I don't see a usage that sets
statusTagtotrue. Am I missing something?
this was detritus; removed
pkg/util/tracing/span_inner.go, line 196 at r14 (raw file):
Previously, dhartunian (David Hartunian) wrote…
Unused. Same as
clearTag().
removed
AlexTalks
left a comment
There was a problem hiding this comment.
Reviewed 1 of 7 files at r10.
Reviewable status:complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @andreimatei, @dhartunian, @nvanbenschoten, and @tbg)
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 931 at r10 (raw file):
Previously, andreimatei (Andrei Matei) wrote…
The point of only storing aggregate information (in addition to extra info about the current lock being waited on, if any), is so that all this tracing takes a fixed memory budget when recording is not enabled. When recording is enabled, we get the structured events after each lock.
You don't buy?
I think that makes sense. I'm not entirely sure how to reason about what our memory budget is here, but I understand the thought process.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 882 at r18 (raw file):
// over. // // contentionEventTracer is thread-safe because ExpandToRecordingTags can be
s/ExpandToRecordingTags/Render
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 896 at r18 (raw file):
// lockWait accumulates time waited for locks when the contentionEventTracer // is closed. lockWait time.Duration
Very useful!
It could potentially be even more useful to make this available to lockTableGuardImpl so that it could be used in metrics, though that may be out of scope here.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 913 at r18 (raw file):
// not necessarily the last update - if two successive states refer to the // same lock, curLockStart is not updated. curLockStart time.Time
This may duplicate lockTableGuardImpl.mu.requestWaitBegin, which I had initially included in waitingState (which would have made it available here). Perhaps this should be refactored to avoid duplication? @nvanbenschoten any thoughts?
(Also, that said, I don't think that allows us to account for waitElsewhere)
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 940 at r18 (raw file):
} h.mu.closed = false h.mu.waitStart = timeutil.Now()
Nit: If it does make sense to continue tracking this here, the lockTableImpl is created with a timeutil.TimeSource that helps with testing - it would be good to pass that in to lockTableWaiterImpl, and use that instead of timeutil.Now().
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 947 at r18 (raw file):
onEvent: onEvent, } h.mu.waitStart = timeutil.Now()
Nit: Same as above.
dhartunian
left a comment
There was a problem hiding this comment.
Reviewable status:
complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @andreimatei, @dhartunian, @nvanbenschoten, and @tbg)
Previously, andreimatei (Andrei Matei) wrote…
When the span is waiting on a lock and we capture it in-flight, we'll get a snapshot of the contentionEventTracer state within via the ExpandingTag mechanism.
Right. Except that you'll get some data from the
ExpandingTageven if the span's recording is captured after the span finished waiting; see below.If the span waited on locks in the past these each produce ContentionEvents via the emit() call that add structured data to the span.
Right. But note that, at least currently, the structured events are no-ops on non-recording spans, just like non-structured events.
Besides the structured events, thecontentionEventTraceralso counts the number of locks and the total time waited. See below.Does the contentionEventTracer leave data in its wake besides the structured ContentionEvents?
It does. Or, rather, there's no "in its wake"; once created, the
contentionEventTracerstays attached to the span as aLazyTaguntilspan.Finish(). If/when the span's recording is collected, it will include the number of locks and the total time waited. If the recording is collected while the span is waiting on a lock, we get extra info about that lock.
Ack. Thankyou!
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 935 at r10 (raw file):
Previously, andreimatei (Andrei Matei) wrote…
I personally am in the crash-happy camp for bugs, on the argument that it generally leads to better and tighter code. If somehow
getOrCreateContentionEventHelperwere called with a span with an opencontentionEventHelper, then either we forgot toclose()it (in which case we're been counting bogus wait time), or we're somehow using the span for two concurrent wait table interactions, in which case they would step over each other. Both are a form of data corruption. Unless there's good reason to relax this, I'd rather generally assert that my data is sane.
You object?
Makes sense, thx for clarifying 👍
dbf2e89 to
8d9ba98
Compare
andreimatei
left a comment
There was a problem hiding this comment.
Reviewable status:
complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @AlexTalks, @dhartunian, @nvanbenschoten, and @tbg)
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 882 at r18 (raw file):
Previously, AlexTalks (Alex Sarkesian) wrote…
s/ExpandToRecordingTags/Render
Done.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 896 at r18 (raw file):
Previously, AlexTalks (Alex Sarkesian) wrote…
Very useful!
It could potentially be even more useful to make this available to
lockTableGuardImplso that it could be used in metrics, though that may be out of scope here.
yeah... I'll leave this for next time.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 913 at r18 (raw file):
Previously, AlexTalks (Alex Sarkesian) wrote…
This may duplicate
lockTableGuardImpl.mu.requestWaitBegin, which I had initially included inwaitingState(which would have made it available here). Perhaps this should be refactored to avoid duplication? @nvanbenschoten any thoughts?(Also, that said, I don't think that allows us to account for
waitElsewhere)
as discussed offline, I've plumbed requestWaitBegin through the waitState over from the lockTableGuard to the contentionEventTracer.
I've also prepended a commit to rename requestWaitBegin -> curLockWaitStart if you don't mind, to reflect the understanding that the field is scoped to a lock.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 940 at r18 (raw file):
Previously, AlexTalks (Alex Sarkesian) wrote…
Nit: If it does make sense to continue tracking this here, the
lockTableImplis created with atimeutil.TimeSourcethat helps with testing - it would be good to pass that in tolockTableWaiterImpl, and use that instead oftimeutil.Now().
removed
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 947 at r18 (raw file):
Previously, AlexTalks (Alex Sarkesian) wrote…
Nit: Same as above.
done
AlexTalks
left a comment
There was a problem hiding this comment.
Reviewed 9 of 9 files at r19, 1 of 1 files at r20.
Reviewable status:complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @AlexTalks, @nvanbenschoten, and @tbg)
nvb
left a comment
There was a problem hiding this comment.
Reviewed 9 of 9 files at r19, 1 of 1 files at r20, 3 of 3 files at r21, 1 of 1 files at r22, 1 of 1 files at r23, 9 of 9 files at r24, all commit messages.
Reviewable status:complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @andreimatei and @tbg)
pkg/kv/kvserver/concurrency/concurrency_control.go, line 719 at r24 (raw file):
Metrics() LockTableMetrics // Clock returns the lockTable's time source.
Instead of adding to this interface, let's give lockTableWaiterImpl a new contentionEventClock field that gets set to the same time source on construction.
pkg/kv/kvserver/concurrency/lock_table.go, line 96 at r24 (raw file):
queuedWriters int // how many writers are waiting? queuedReaders int // how many readers are waiting? // lockWaitStart represents the timestamp when the request started waiting on
nit: move below guardAccess. Otherwise though, I'm glad we decided to pass this information through the waitingState.
pkg/kv/kvserver/concurrency/lock_table.go, line 1562 at r24 (raw file):
g.mu.Lock() lockWaitStart := g.mu.curLockWaitStart g.mu.Unlock()
Can we move this down to the existing critical section blow? It looks like. we only need it right after g.mu.curLockWaitStart = timeProvider.Now().
pkg/kv/kvserver/concurrency/lock_table.go, line 2046 at r24 (raw file):
g.mu.Lock() g.mu.state = waitState g.mu.state.lockWaitStart = g.mu.curLockWaitStart
Assigning to g.mu.state.lockWaitStart is nice. It prevents lockWaitStart times from accidentally ending up in the wrong guard's state. We should do that everywhere else as well.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 887 at r21 (raw file):
// Otherwise, switch s.kind { case waitFor, waitForDistinguished, waitSelf, waitElsewhere:
Should we write a test for this? Similar to pkg/kv/kvserver/concurrency/testdata/concurrency_manager/wait_self, which was added in dc689ae.
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 908 at r24 (raw file):
// closed or if the contentionEventTracer has not yet been notified of the // first lock to wait on. curState *waitingState
Does this need to be a pointer? I assume it causes the argument to notify to escape to the heap and raises (unfounded) aliasing concerns.
8d9ba98 to
896e7b1
Compare
andreimatei
left a comment
There was a problem hiding this comment.
Reviewable status:
complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @tbg)
pkg/kv/kvserver/concurrency/concurrency_control.go, line 719 at r24 (raw file):
Previously, nvanbenschoten (Nathan VanBenschoten) wrote…
Instead of adding to this interface, let's give
lockTableWaiterImpla newcontentionEventClockfield that gets set to the same time source on construction.
done
pkg/kv/kvserver/concurrency/lock_table.go, line 96 at r24 (raw file):
Previously, nvanbenschoten (Nathan VanBenschoten) wrote…
nit: move below
guardAccess. Otherwise though, I'm glad we decided to pass this information through thewaitingState.
done
pkg/kv/kvserver/concurrency/lock_table.go, line 1562 at r24 (raw file):
Previously, nvanbenschoten (Nathan VanBenschoten) wrote…
Can we move this down to the existing critical section blow? It looks like. we only need it right after
g.mu.curLockWaitStart = timeProvider.Now().
done. Please see now,
pkg/kv/kvserver/concurrency/lock_table.go, line 2046 at r24 (raw file):
Previously, nvanbenschoten (Nathan VanBenschoten) wrote…
Assigning to
g.mu.state.lockWaitStartis nice. It prevents lockWaitStart times from accidentally ending up in the wrong guard's state. We should do that everywhere else as well.
done
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 887 at r21 (raw file):
Previously, nvanbenschoten (Nathan VanBenschoten) wrote…
Should we write a test for this? Similar to
pkg/kv/kvserver/concurrency/testdata/concurrency_manager/wait_self, which was added in dc689ae.
done
pkg/kv/kvserver/concurrency/lock_table_waiter.go, line 908 at r24 (raw file):
Previously, nvanbenschoten (Nathan VanBenschoten) wrote…
Does this need to be a pointer? I assume it causes the argument to
notifyto escape to the heap and raises (unfounded) aliasing concerns.
done
The comment was claiming that a span's tag are only maintained if the span is recording, but that hasn't been the case in a while. Release note: None
An inner function is no longer serving any purpose. Release note: None
…ents Before this patch, no contention event was produced when requests were waiting for locks by pushing the txn that currently has a conflicting lock (or a reservation on a conflicting lock). This corresponds to the waitElsewhere signal from the lock table. (waitElsewhere is not the common case; the common case is for a request to wait in the lock table (through the waitFor signal). waitElsewhere happens instead of waitFor when the lock table is under memory pressure and sheds its state.) This patch makes the waitElsewhere state be handled like the other waiting states for the purposes of generating contention events. This patch also adds a data-driven test for the waitElsewhere functionality. Release note: None
Rename to curLockWaitStart to emphasize that it is set once per lock, not once per request or once per wait state. Release note: None
waitQueueMaxLengthExceeded is not really a wait state, so setting curLockWaitStart for it doesn't make much sense. Other final states like also don't update curLockWaitStart. Release note: None
896e7b1 to
2aa2f23
Compare
nvb
left a comment
There was a problem hiding this comment.
Reviewed 4 of 7 files at r33, 9 of 9 files at r36, all commit messages.
Reviewable status:complete! 1 of 0 LGTMs obtained (and 2 stale) (waiting on @andreimatei and @tbg)
pkg/kv/kvserver/concurrency/concurrency_manager_test.go, line 82 at r36 (raw file):
// debug-set-clock ts=<secs> // debug-set-discovered-locks-threshold-to-consult-finalized-txn-cache n=<count> // debug-set-max-locks <limit>
n=<count>
When the evaluation of a request blocks on a lock acquisition, this patch annotates the tracing span representing the evaluation with tags about the lock's key, holder, time when the waiting started, and number of locks that the evaluation waited on previously. To do this, the patch adds support in the tracing library for "lazy tags": tags that are only stringified on demand, when a span's recording is collected. This is useful if the stringification is expensive (so we only want to pay for it if someone actually looks at the span), and also for structured tags whose state can change over time. A lazy tag can implement one of two interfaces: Stringer or LazyTag. The latter allows the tag to be represented as a set of key-value pairs in a recording. The point of the new contention tags is to be featured prominently in the visualizer for the active spans registry that I'm working on. Looking at queries/requests experiencing contention is likely to be a very common scenario. This patch spruces up the contentionEventHelper in the lock table, to maintain the state of a waiter as a lazy tag. Release note: None Release justification: Low risk, high observability value.
2aa2f23 to
70ef641
Compare
andreimatei
left a comment
There was a problem hiding this comment.
TFTRs!
bors r+
Reviewable status:
complete! 0 of 0 LGTMs obtained (and 3 stale) (waiting on @nvanbenschoten and @tbg)
pkg/kv/kvserver/concurrency/concurrency_manager_test.go, line 82 at r36 (raw file):
Previously, nvanbenschoten (Nathan VanBenschoten) wrote…
n=<count>
done
Code quote:
// debug-set-max-locks <limit>|
Build succeeded: |
When the evaluation of a request blocks on a lock acquisition, this
patch annotates the tracing span representing the evaluation with
tags about the lock's key, holder, time when the waiting started, and
number of locks that the evaluation waited on previously.
To do this, the patch adds support in the tracing library for "lazy
tags": tags that are only stringified on demand, when a span's recording
is collected. This is useful if the stringification is expensive (so we
only want to pay for it if someone actually looks at the span), and also
for structured tags whose state can change over time. A lazy tag can
implement one of two interfaces: Stringer or LazyTag. The latter allows
the tag to be represented as a set of key-value pairs in a recording.
The point of the new contention tags is to be featured prominently in
the visualizer for the active spans registry that I'm working on.
Looking at queries/requests experiencing contention is likely to be a
very common scenario.
This patch spruces up the contentionEventHelper in the lock table, to
maintain the state of a waiter as a lazy tag.
Release note: None
Release justification: Low risk, high observability value.