Skip to content
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

sql: populate query-level stats earlier & add contention to telemetry log #84718

Merged
merged 1 commit into from
Aug 9, 2022

Conversation

THardy98
Copy link
Contributor

@THardy98 THardy98 commented Jul 20, 2022

Addresses: #71328

This change adds contention time (measured in nanoseconds) to the
SampledQuery telemetry log.

To accomodate this change, we needed to collect query-level statistics
earlier. Previously, query-level statistics were fetched when we called
Finish under the instrumentationHelper, however this occurred after
we had already emitted our query execution logs. Now, we collect
query-level stats in dispatchToExecutionEngine after we've executed
the query.

As a tradeoff to collecting query-level stats earlier, we need to fetch
the trace twice:

  • once when populating query-level stats (trace is required to compute
    query-level stats) at populateQueryLevelStats in
    dispatchToExecutionEngine after query execution
  • once during the instrumentation helper's Finish (as we do currently)

This allows us to collect query-level stats earlier without omitting any
tracing events we record currently. This approach is safer, with the
additional overhead of fetching the trace twice only occuring at the
tracing sampling rate of 1-2%, which is fairly conservative. The concern
with only fetching the trace at query-level stats population was the
ommission of a number of events that occur in
commitSQLTransactionInternal (or any execution paths that don't lead
to dispatchToExecutionEngine).

Release note (sql change): Add ContentionTime field to the
SampledQuery telemetry log. Query-level statistics are collected
earlier to facilitate the adding of contention time to query execution
logs. The earlier collection of query-level statistics requires the
additional overhead of fetching the query's trace twice (instead of
previously once).

@THardy98 THardy98 requested review from a team July 20, 2022 15:37
@THardy98 THardy98 requested a review from a team as a code owner July 20, 2022 15:37
@cockroach-teamcity
Copy link
Member

This change is Reviewable

@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch from 4e00a9b to e07d5db Compare July 20, 2022 15:42
Copy link
Member

@yuzefovich yuzefovich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connExecutor changes :lgtm:

Reviewed 7 of 8 files at r1, 1 of 1 files at r2, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @THardy98)


pkg/sql/instrumentation.go line 285 at r2 (raw file):

	}

	if collectExecStats || ih.implicitTxn {

nit: previously this block would only execute if there was no error returned by GetQueryLevelStats, maybe we should preserve that behavior?

Copy link
Contributor Author

@THardy98 THardy98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @yuzefovich)


pkg/sql/instrumentation.go line 285 at r2 (raw file):

Previously, yuzefovich (Yahor Yuzefovich) wrote…

nit: previously this block would only execute if there was no error returned by GetQueryLevelStats, maybe we should preserve that behavior?

This and RecordStatementExecStats previously did not run if an error was encountered during GetQueryLevelStats.

Changed queryLevelStats to queryLevelStatsWithErr in instrumentationHelper to keep track of whether we encountered an error during GetQueryLevelStats. We check the Err value of this, and if Err is nil (no error has occurred), then we accumulate transaction stats and record statement execution stats.

Copy link
Member

@yuzefovich yuzefovich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 5 of 5 files at r3, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @THardy98)

@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch 4 times, most recently from 26a4ba6 to 7fc139b Compare July 21, 2022 15:50
Copy link
Contributor

@maryliag maryliag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 3 of 8 files at r1, 1 of 1 files at r2, 2 of 5 files at r3, 2 of 3 files at r4.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @THardy98 and @yuzefovich)


-- commits line 16 at r4:
what type of events? anything we're collecting today that we will lose?


pkg/sql/conn_executor_exec.go line 1208 at r4 (raw file):

}

func populateQueryLevelStats(ctx context.Context, p *planner) {

is there any tests that you can add to make sure the new flow didn't change the final behaviour?

@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch from 7fc139b to 8ca10a0 Compare July 29, 2022 19:49
Copy link
Contributor Author

@THardy98 THardy98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @yahor and @yuzefovich)


-- commits line 16 at r4:

Previously, maryliag (Marylia Gutierrez) wrote…

what type of events? anything we're collecting today that we will lose?

In the case where we could be missing events, these would be events that we record after query execution. From chatting with @yahor, my understanding is that our trace provides the vast majority of its value from recording events prior to, and during query execution. If we are missing post-query execution events, they are likely inconsequential.

In the case that they aren't, and we really do need them, we could fetch the trace again later and ensure we have all events. The tradeoff here is that fetching the trace is somewhat expensive, but considering we only trace a very small percentage of queries (under a configured sampling rate of something like 1 or 2%), this shouldn't be much of an issue.


pkg/sql/conn_executor_exec.go line 1208 at r4 (raw file):

Previously, maryliag (Marylia Gutierrez) wrote…

is there any tests that you can add to make sure the new flow didn't change the final behaviour?

From chatting with @yuzefovich the testing for tracing is somewhat ad-hoc, but there do seem to exist tests that ensure we're populating events that we are expecting, the primary one I believe being TestTrace.

Additionally there do seem to be failing tests in CI that seem to be related, which is a good thing.

@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch 2 times, most recently from 202002b to 9366c84 Compare July 30, 2022 16:41
@THardy98 THardy98 requested a review from a team July 30, 2022 18:06
@THardy98
Copy link
Contributor Author

THardy98 commented Jul 30, 2022

@ajwerner @postamar I'm noticing CI failures with rtt analysis benchmarking tests for schema change queries. The changes in this PR are recording 5 fewer round trips than expected in the tests. I'm not familiar enough with tracing or schema queries to know why this would occur, or if this is necessarily a bad thing.

It looked like you both seem to have written the majority of these tests so I thought I'd ask to hear your thoughts on these failures/changes.

@ajwerner
Copy link
Contributor

The changes in this PR are recording 5 fewer round trips than expected in the tests. I'm not familiar enough with tracing or schema queries to know why this would occur, or if this is necessarily a bad thing.

I can't figure out just from looking at this PR why it would reduce the round-trips. Reducing round-trips is a good thing. You can rewrite the expectations using the --rewrite flag, though beware, it can be slow to rewrite them all.

Copy link
Member

@yuzefovich yuzefovich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't figure out just from looking at this PR why it would reduce the round-trips. Reducing round-trips is a good thing.

This change might have an effect that some events are no longer included in the trace, so I don't think that we get actual RTT reduction here - instead, the trace might no longer capture some of the stuff (like say in some defer we're releasing some descriptors / leases which do some RTTs, and we no longer capture them in the trace).

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @maryliag, @yahor, and @yuzefovich)

@dhartunian dhartunian removed the request for review from a team August 1, 2022 19:04
@THardy98
Copy link
Contributor Author

THardy98 commented Aug 2, 2022

I can't figure out just from looking at this PR why it would reduce the round-trips. Reducing round-trips is a good thing.

This change might have an effect that some events are no longer included in the trace, so I don't think that we get actual RTT reduction here - instead, the trace might no longer capture some of the stuff (like say in some defer we're releasing some descriptors / leases which do some RTTs, and we no longer capture them in the trace).

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @maryliag, @yahor, and @yuzefovich)

WIth this in mind @ajwerner, would you still recommend simply --rewrite the expected # of RTT for the corresponding failing benchmark tests?

@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch from 9366c84 to 2f8899b Compare August 3, 2022 20:57
@THardy98
Copy link
Contributor Author

THardy98 commented Aug 4, 2022

After chatting with @ajwerner, we've decided to opt for fetching the trace twice, both when we:

  • populate query-level stats immediately after query execution
  • and when we call the instrumentation helper's Finish

to preserve all tracing events. The concern with only fetching the trace at query-level stats population was missing a number of events that occur in commitSQLTransactionInternal (or any execution paths that don't lead to dispatchToExecutionEngine, like this switch case). This approach is safer, with the additional overhead of fetching the trace twice only occuring at the tracing sampling rate of 1-2%, which is fairly conservative.
Will update the PR description/commit message accordingly.

@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch from 2f8899b to 741116e Compare August 4, 2022 14:24
Copy link
Contributor Author

@THardy98 THardy98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @maryliag, @yahor, and @yuzefovich)


-- commits line 16 at r4:

Previously, THardy98 (Thomas Hardy) wrote…

In the case where we could be missing events, these would be events that we record after query execution. From chatting with @yahor, my understanding is that our trace provides the vast majority of its value from recording events prior to, and during query execution. If we are missing post-query execution events, they are likely inconsequential.

In the case that they aren't, and we really do need them, we could fetch the trace again later and ensure we have all events. The tradeoff here is that fetching the trace is somewhat expensive, but considering we only trace a very small percentage of queries (under a configured sampling rate of something like 1 or 2%), this shouldn't be much of an issue.

This is no longer the case now that we fetch the trace twice. No events would be omitted.


pkg/sql/conn_executor_exec.go line 1208 at r4 (raw file):

Previously, THardy98 (Thomas Hardy) wrote…

From chatting with @yuzefovich the testing for tracing is somewhat ad-hoc, but there do seem to exist tests that ensure we're populating events that we are expecting, the primary one I believe being TestTrace.

Additionally there do seem to be failing tests in CI that seem to be related, which is a good thing.

On the tracing side, now that we fetch the trace twice, we will not be omitting any events, so no changes there.

On the query-level stats side, there are existing tests as part of TestSampledStatsCollection that ensure we get the statistic values we're expecting for sql stats.

Copy link
Contributor

@andreimatei andreimatei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @maryliag, @THardy98, @yahor, and @yuzefovich)


pkg/sql/conn_executor_exec.go line 1216 at r8 (raw file):

}

func populateQueryLevelStats(ctx context.Context, p *planner) {

this function needs a comment


pkg/sql/conn_executor_exec.go line 1218 at r8 (raw file):

func populateQueryLevelStats(ctx context.Context, p *planner) {
	ih := &p.instrumentation
	if ih.sp == nil {

there's precedent for this test written like this ingetNodesFromPlanner, but let's introduce a method on ih like Tracing() (sp, ok bool) or such, and let's also put a comment on if.sp about when it's set.


pkg/sql/conn_executor_exec.go line 1226 at r8 (raw file):

		flowsMetadata = append(flowsMetadata, flowInfo.flowsMetadata)
	}
	trace := ih.sp.GetConfiguredRecording()

does this ever need verbose recording? If not, do GetRecording(Structured).


pkg/sql/conn_executor_exec.go line 1228 at r8 (raw file):

	trace := ih.sp.GetConfiguredRecording()
	var err error
	queryLevelStats, err := execstats.GetQueryLevelStats(trace, p.execCfg.TestingKnobs.DeterministicExplain, flowsMetadata)

nit: line too long


pkg/sql/conn_executor_exec.go line 1228 at r8 (raw file):

	trace := ih.sp.GetConfiguredRecording()
	var err error
	queryLevelStats, err := execstats.GetQueryLevelStats(trace, p.execCfg.TestingKnobs.DeterministicExplain, flowsMetadata)

GetQueryLevelStats seems to make assumptions about what it finds in the trace. Do those assumptions hold when the ih did not create the span? I think they do, because the span in question is still limited to the current statement (but please check). But this all seems very shaky; if it wasn't for that span creation for the statement, it would break (I think).

I think we should either a) have the ih always create a span, or b) never create a span - and rely and assert that there is a statement span.
I think a) is easier but b) is nicer.

For b), I think we need to:

  1. add the WithForceRealSpan option to the statement span creation, in order to assure that the span is created even when trace.span_registry.enabled cluster setting is tuned off (the default is on).
  2. in the ih, assert that we're operating on a statement span, by checking the span's operation name
  3. change the tracing library so that children of a "real span" are not automatically forced to be real spans themselves. The relevant check is the opts.parentTraceID at span creation time. I was surprised that the check is there; I don't think it's intentional. I don't think that the parent being a real span is reason enough for the child to be a real span. Valid reasons for the child being a real span include the active spans registry being enabled enabled (which is the default), or that the parent recording at the time the child is being created.
    Without 3), bullet number 1) would effectively neuter the trace.span_registry.enabled = off setting because, by creating a real span at such a high level, we'd essentially be creating spans everywhere.

WDYT?


pkg/sql/execstats/traceanalyzer.go line 138 at r8 (raw file):

// ConstructQueryLevelStatsWithErr creates a QueryLevelStatsWithErr from a
// QueryLevelStats and error.
func ConstructQueryLevelStatsWithErr(stats QueryLevelStats, err error) QueryLevelStatsWithErr {

MakeQueryLevelStatsWithErr


pkg/util/log/eventpb/telemetry.proto line 94 at r8 (raw file):

  // The duration of time in nanoseconds that the query experienced contention.
  int64 contention_time = 22 [(gogoproto.jsontag) = ',omitempty'];

contention_nanos

Copy link
Contributor Author

@THardy98 THardy98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @andreimatei, @maryliag, @THardy98, @yahor, and @yuzefovich)


pkg/sql/conn_executor_exec.go line 1216 at r8 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

this function needs a comment

Added a comment.


pkg/sql/conn_executor_exec.go line 1218 at r8 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

there's precedent for this test written like this ingetNodesFromPlanner, but let's introduce a method on ih like Tracing() (sp, ok bool) or such, and let's also put a comment on if.sp about when it's set.

Done.


pkg/sql/conn_executor_exec.go line 1228 at r8 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

nit: line too long

Split to 2 lines.


pkg/sql/conn_executor_exec.go line 1228 at r8 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

GetQueryLevelStats seems to make assumptions about what it finds in the trace. Do those assumptions hold when the ih did not create the span? I think they do, because the span in question is still limited to the current statement (but please check). But this all seems very shaky; if it wasn't for that span creation for the statement, it would break (I think).

I think we should either a) have the ih always create a span, or b) never create a span - and rely and assert that there is a statement span.
I think a) is easier but b) is nicer.

For b), I think we need to:

  1. add the WithForceRealSpan option to the statement span creation, in order to assure that the span is created even when trace.span_registry.enabled cluster setting is tuned off (the default is on).
  2. in the ih, assert that we're operating on a statement span, by checking the span's operation name
  3. change the tracing library so that children of a "real span" are not automatically forced to be real spans themselves. The relevant check is the opts.parentTraceID at span creation time. I was surprised that the check is there; I don't think it's intentional. I don't think that the parent being a real span is reason enough for the child to be a real span. Valid reasons for the child being a real span include the active spans registry being enabled enabled (which is the default), or that the parent recording at the time the child is being created.
    Without 3), bullet number 1) would effectively neuter the trace.span_registry.enabled = off setting because, by creating a real span at such a high level, we'd essentially be creating spans everywhere.

WDYT?

I'm not very familiar with our tracing library, but I'm curious as to why you think option b is nicer? To me, it seems more intuitive that the ih always creates the span, given that the ih will have ownership over it.

I'm a little hesitant to include these changes in this PR, given its scope is outside of the original issue. I'm not familiar enough with our tracing library to know of the potential implications from the suggested span creation changes, where a separate issue with the appropriate stakeholders may prove to be a more fruitful discussion of options/concerns.


pkg/sql/execstats/traceanalyzer.go line 138 at r8 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

MakeQueryLevelStatsWithErr

Done.


pkg/util/log/eventpb/telemetry.proto line 94 at r8 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

contention_nanos

Done.

@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch 2 times, most recently from 30ead08 to fdb41d4 Compare August 8, 2022 16:09
Copy link
Contributor Author

@THardy98 THardy98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @andreimatei, @maryliag, @yahor, and @yuzefovich)


pkg/sql/conn_executor_exec.go line 1226 at r8 (raw file):

Previously, andreimatei (Andrei Matei) wrote…

does this ever need verbose recording? If not, do GetRecording(Structured).

Done.

@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch from fdb41d4 to 498486f Compare August 8, 2022 16:27
Copy link
Member

@yuzefovich yuzefovich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @andreimatei, @maryliag, @yahor, and @yuzefovich)


pkg/sql/conn_executor_exec.go line 1228 at r8 (raw file):

GetQueryLevelStats seems to make assumptions about what it finds in the trace.

It does.

Do those assumptions hold when the ih did not create the span? I think they do

Yep, precisely because of the span for the current statement you're referring to. This was a deliberate decision in 842d79b when the spans were expensive and we wanted to reduce the impact of sampling the statements. This might no longer be necessary but needs to be evaluated properly.

I agree with Thomas that this refactor doesn't belong in this PR. However, we shouldn't also lose the idea and reasoning that Andrei is proposing, so it'd be good to create an issue (probably would go both on SQL o11y and SQL queries dashboards) that would include the suggestions from Andrei and add a TODO (feel free to put my name on it) to the code referencing that issue.

… log

Addresses: cockroachdb#71328

This change adds contention time (measured in nanoseconds) to the
`SampledQuery` telemetry log.

To accomodate this change, we needed to collect query-level statistics
earlier. Previously, query-level statistics were fetched when we called
`Finish` under the `instrumentationHelper`, however this occurred after
we had already emitted our query execution logs. Now, we collect
query-level stats in `dispatchToExecutionEngine` after we've executed
the query.

As a tradeoff to collecting query-level stats earlier, we need to fetch
the trace twice:
- once when populating query-level stats (trace is required to compute
  query-level stats) at `populateQueryLevelStats` in
`dispatchToExecutionEngine` after query execution
- once during the instrumentation helper's `Finish` (as we do currently)

This allows us to collect query-level stats earlier without omitting any
tracing events we record currently. This approach is safer, with the
additional overhead of fetching the trace twice only occuring at the
tracing sampling rate of 1-2%, which is fairly conservative. The concern
with only fetching the trace at query-level stats population was the
ommission of a number of events that occur in
`commitSQLTransactionInternal` (or any execution paths that don't lead
to `dispatchToExecutionEngine`).

Release note (sql change): Add `ContentionTime` field to the
`SampledQuery` telemetry log. Query-level statistics are collected
earlier to facilitate the adding of contention time to query execution
logs. The earlier collection of query-level statistics requires the
additional overhead of fetching the query's trace twice (instead of
previously once).
@THardy98 THardy98 force-pushed the add_contention_to_sampled_query branch from 498486f to fd1b51f Compare August 9, 2022 16:08
Copy link
Contributor Author

@THardy98 THardy98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @andreimatei, @maryliag, @yahor, and @yuzefovich)


pkg/sql/conn_executor_exec.go line 1228 at r8 (raw file):

Previously, yuzefovich (Yahor Yuzefovich) wrote…

GetQueryLevelStats seems to make assumptions about what it finds in the trace.

It does.

Do those assumptions hold when the ih did not create the span? I think they do

Yep, precisely because of the span for the current statement you're referring to. This was a deliberate decision in 842d79b when the spans were expensive and we wanted to reduce the impact of sampling the statements. This might no longer be necessary but needs to be evaluated properly.

I agree with Thomas that this refactor doesn't belong in this PR. However, we shouldn't also lose the idea and reasoning that Andrei is proposing, so it'd be good to create an issue (probably would go both on SQL o11y and SQL queries dashboards) that would include the suggestions from Andrei and add a TODO (feel free to put my name on it) to the code referencing that issue.

Filed: #85820

Added a TODO for @yahor by the instrumentation helper's span field (sp) with the above issue.

@THardy98 THardy98 requested a review from maryliag August 9, 2022 18:26
Copy link
Contributor

@maryliag maryliag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 6 of 8 files at r6, 2 of 2 files at r7, 9 of 10 files at r9, 1 of 1 files at r10, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @andreimatei and @yahor)

@THardy98
Copy link
Contributor Author

THardy98 commented Aug 9, 2022

going to bors this given the discussion above and the issue filed, TYFR :)

@THardy98
Copy link
Contributor Author

THardy98 commented Aug 9, 2022

bors r+

@craig
Copy link
Contributor

craig bot commented Aug 9, 2022

Build succeeded:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants