VDiff: add --no-samples option to vdiff show - #20870
Conversation
Review ChecklistHello reviewers! 👋 Please follow this checklist when reviewing this Pull Request. General
Tests
Documentation
New flags
If a workflow is added or modified:
Backward compatibility
|
VDiff show returns a per-table diff report body (stored in _vt.vdiff_table.report) alongside the summary state. The report's row-sample arrays (MismatchedRowsSample, ExtraRowsSourceSample, ExtraRowsTargetSample) carry actual sampled row data, including large blob/JSON columns, and vdiff show aggregates them across every target shard. For diffs over tables with large rows this can push the aggregated gRPC response past message-size limits and make vdiff show fail outright, leaving callers that only need progress and the has_mismatch flag with no way to read the summary. Add an only_summary option, threaded from the vtctldclient `--only-summary` flag through VDiffShowRequest and the tablet VDiffReportOptions. When set, the tablet's summary query strips the row-sample arrays from the report via JSON_REMOVE while preserving the scalar counters (ProcessedRows, MatchingRows, MismatchedRows, ExtraRows*), so the summary counts stay accurate and the large sampled rows are neither read into the response nor sent to vtctld. JSON_REMOVE returns NULL when the report is NULL (no joined vdiff_table row), matching the plain-column behavior. All other summary columns are unaffected. The summary query is composed from shared column-list and FROM/WHERE constants so the two variants differ only in the report select-expression. ## AI Disclosure This change was co-authored with Claude Code, which helped with implementation and testing. Signed-off-by: Pedro Albuquerque <pedro.albuquerque@slack-corp.com>
b4452f9 to
bf09fd7
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 14 changed files in this pull request and generated no new comments.
Files not reviewed (3)
- go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go: Generated file
- go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go: Generated file
- go/vt/proto/vtctldata/vtctldata.pb.go: Generated file
Suppressed comments (3)
go/vt/vtctl/workflow/vdiff.go:573
- This always populates
Options/ReportOptionseven whenOnlySummaryis false (the default). If downstream code treatsniloptions differently from an empty options message (e.g., for applying defaults), this can unintentionally change behavior for callers that did not setonly_summary. Consider only settingOptionswhenreq.GetOnlySummary()is true; otherwise leave it nil to preserve prior request shape.
Options: &tabletmanagerdatapb.VDiffOptions{
ReportOptions: &tabletmanagerdatapb.VDiffReportOptions{
OnlySummary: req.GetOnlySummary(),
},
},
go/vt/vttablet/tabletmanager/vdiff/action.go:134
getVDiffSummaryonly needs a boolean, but it now depends on the full*VDiffReportOptionsmessage. This increases coupling and makes the function signature more complex than necessary. Consider changing the parameter toonlySummary booland having the caller passreq.GetOptions().GetReportOptions().GetOnlySummary(); this keeps the function focused and simplifies future extension/testing.
func (vde *Engine) getVDiffSummary(vdiffID int64, dbClient binlogplayer.DBClient, reportOpts *tabletmanagerdatapb.VDiffReportOptions) (*query.QueryResult, error) {
go/vt/vttablet/tabletmanager/vdiff/action.go:138
getVDiffSummaryonly needs a boolean, but it now depends on the full*VDiffReportOptionsmessage. This increases coupling and makes the function signature more complex than necessary. Consider changing the parameter toonlySummary booland having the caller passreq.GetOptions().GetReportOptions().GetOnlySummary(); this keeps the function focused and simplifies future extension/testing.
query, err := sqlparser.ParseAndBind(vdiffSummaryQuery(reportOpts.GetOnlySummary()), sqltypes.Int64BindVariable(vdiffID), sqltypes.StringBindVariable(vde.dbName))
…only
The summary-only query previously replaced the whole per-table report with a
literal '{}'. That had two problems: BuildSummary parses the scalar counters
(ProcessedRows, MatchingRows, MismatchedRows, ExtraRows*) out of the report
JSON, so blanking it produced misleading zero counts alongside
has_mismatch=true; and '{}' is non-NULL, unlike vdt.report on a LEFT JOIN with
no matching row.
Strip only the large row-sample arrays via JSON_REMOVE while preserving the
counters. Counts stay accurate, JSON_REMOVE(NULL, ...) returns NULL so the
no-row case matches the plain-column behavior, and the sampled rows (the
unbounded, blob-carrying part that can exceed gRPC message limits) are still
kept out of the response.
Verified on MySQL 8.0.46 and 5.7.44. Mirrors vitessio#20870.
Signed-off-by: Pedro Albuquerque <pedro.albuquerque@slack-corp.com>
Align the Summary-Only Responses subsection with the revised vitessio/vitess#20870 implementation: --only-summary strips only the three sampled-row arrays (MismatchedRowsSample, ExtraRowsSourceSample, ExtraRowsTargetSample) via JSON_REMOVE, preserving the report's scalar counters and other summary fields so counts stay accurate. Removes the stale first-revision claims that the report was emptied to {} and never read from MySQL.
| func vdiffSummaryQuery(onlySummary bool) string { | ||
| if onlySummary { | ||
| return sqlVDiffSummaryOnly | ||
| } | ||
| return sqlVDiffSummary | ||
| } |
There was a problem hiding this comment.
Nit, but summary feels very overloaded here to the point that it loses all meaning. Maybe we could at least call sqlVDiffSummaryOnly something like sqlVDiffSummaryMinimal or sqlVDiffSummaryWithoutSamples? Or maybe generally we use Full and Minimal like we have in some code here.
mattlord
left a comment
There was a problem hiding this comment.
This LGTM, but I think our current overloaded use of summary is a bit unintuitive and ambiguous. I think that the client option/flag could be --no-samples versus --only-summary, or even --minimal or something. What do you think? I would not call the flag portion a blocker, but I do think we can at least improve this aspect in the code throughout as my inline comment talks to.
100% @mattlord, thanks for the review. I think |
5d44ad5 to
4a06ba1
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 16 changed files in this pull request and generated no new comments.
Files not reviewed (3)
- go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go: Generated file
- go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go: Generated file
- go/vt/proto/vtctldata/vtctldata.pb.go: Generated file
Address review feedback that summary was overloaded/ambiguous. Rename throughout: the --only-summary flag to --no-samples, the only_summary proto field (VDiffShowRequest, VDiffReportOptions) to no_samples, the Go OnlySummary to NoSamples, and sqlVDiffSummaryOnly to sqlVDiffSummaryNoSamples. Regenerated proto bindings (Go + vtadmin). Behavior unchanged; the name now describes what it does (strip the report's row-sample arrays, keep the counters). Signed-off-by: Pedro Albuquerque <pedro.albuquerque@slack-corp.com>
Per AGENTS.md (do not add explanatory comments unless asked). Signed-off-by: Pedro Albuquerque <pedro.albuquerque@slack-corp.com>
mattlord
left a comment
There was a problem hiding this comment.
Non-blocking: I think we should probably restore the release-note entry in changelog/25.0/25.0.0/summary.md since this is a new feature rather than a bug fix. It seems like commit 6614ddf removed the --only-summary entry during the rename without adding an updated --no-samples version, so the new flag and intentional vdiff create --wait --format json output change are no longer documented for operators. Otherwise, this looks good to me. It's also a minor thing so it doesn't HAVE to be in the release summary. Thanks again, @pedroalb !
If you merge in upstream main then the static code checks workflow should pass.
Signed-off-by: Pedro Albuquerque <pedro.albuquerque@slack-corp.com>
…ummary-only Signed-off-by: Pedro Albuquerque <pedro.albuquerque@slack-corp.com>
Done - restored the release note and merged in upstream main. Thanks for the review, @mattlord!
unfortunately the static code checks are still failing |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 16 changed files in this pull request and generated no new comments.
Files not reviewed (3)
- go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go: Generated file
- go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go: Generated file
- go/vt/proto/vtctldata/vtctldata.pb.go: Generated file
Description
vdiff showalways reads the per-table diff report from_vt.vdiff_table.reportand returns it in the response, regardless of client--format. The report's row-sample arrays (MismatchedRowsSample,ExtraRowsSourceSample,ExtraRowsTargetSample) carry actual sampled row data, including large blob/JSON columns, and becausevdiff showfans out to every target shard's primary and aggregates, the response can exceed gRPC message limits on large diffs. Callers that only need progress, counts and thehas_mismatchflag still pay the full cost of the samples.This PR adds an opt-in
no_samplesoption, threaded from a new client flag--no-samplesthroughVDiffShowRequest.no_samples(vtctld) toVDiffReportOptions.no_samples(tablet). When set, the tablet's summary query strips the sample arrays from the report withJSON_REMOVEwhile preserving the scalar counters, so the large sampled rows are neither read into the response nor sent to vtctld, and the reported counts stay accurate.Fixes #20869.
Behavior
--no-samplesunset): unchanged — the full report is returned as today.--no-samples: the response carries all summary fields (state, table rows, rows compared, started/completed timestamps,has_mismatch) and the report's scalar counters (ProcessedRows,MatchingRows,MismatchedRows,ExtraRowsSource,ExtraRowsTarget), but with the sampled-row arrays removed.vdiff create --waitusesno_samplesfor its internal progress polls, so the wait loop no longer transfers the sampled rows on every interval and can't be killed by the gRPC limit on large diffs. Text output is unchanged (the samples are never rendered in the wait summary); with--format jsonthe per-interval output no longer includes the sampled rows (they remain available viavdiff show --verboseonce the diff completes).Design note: why strip samples rather than blank the whole report
The first revision of this PR replaced the whole report with a literal
'{}'. Review (thanks @chatgpt-codex-connector, @Copilot) surfaced two problems with that:BuildSummary(go/vt/vtctl/workflow/vdiff.go) parses the per-table/per-shardProcessedRows,MatchingRows,MismatchedRows, andExtraRows*counters out of the report JSON. Blanking the report to{}made those parse as zero, so a summary could reportHasMismatch: truealongsideMismatchedRows: 0andProcessedRows: 0— internally inconsistent output.vdt.report as reportis NULL when theLEFT JOINproduces novdiff_tablerow; a literal'{}'is not, silently changing behavior for that case.Stripping only the sample arrays with
JSON_REMOVE(vdt.report, '$.MismatchedRowsSample', '$.ExtraRowsSourceSample', '$.ExtraRowsTargetSample')fixes both: the counters thatBuildSummaryneeds are preserved so counts stay accurate, andJSON_REMOVE(NULL, ...)returns NULL so the no-row case matches the plain-column behavior. It also removes exactly the part that causes the gRPC-size problem (the sampled rows are the only unbounded, blob-carrying portion of the report), so the original goal is still met.Note this means the report column is still read by MySQL to evaluate
JSON_REMOVE; what is avoided is transferring the large sampled rows into the response and on to vtctld. The diff-time coordinator memory cost is a separate concern tracked in #19735.Implementation notes
vdiffSummaryColsandvdiffSummaryFromconstants;sqlVDiffSummaryappendsvdt.report as reportandsqlVDiffSummaryNoSamplesappends theJSON_REMOVE(...)expression. The two variants can therefore only differ in the report select-expression — every other column stays identical, so no-samples can never silently drop other summary data.getVDiffSummarytakes the request's*VDiffReportOptionsand picks the query viavdiffSummaryQuery(reportOpts.GetNoSamples()).Testing
TestVDiffSummaryQuery(tabletmanager/vdiff) asserts structurally (not by brittle full-string match) that: both variants keep the%abind placeholders; the full variant returns the report as-is and does not useJSON_REMOVE; the no-samples variant strips exactly the three sample arrays; and the two variants are identical after swapping the report select-expression, so no other column or clause can silently differ.TestPerformVDiffActionshow-by-uuid cases assert thathandleShowActionruns theJSON_REMOVEquery whenno_samplesis set and the full-report query otherwise (exercising the real request path, not just the query strings).TestVDiffShow(workflow) asserts thatVDiffShowforwardsno_samplesinto the tabletVDiffRequestsent to every target primary, for both flag values.TestShowNoSamplesFlagguards the--no-samplesflag registration (default and usage).JSON_REMOVEsemantics were verified on real MySQL 8.0.46 and 5.7.44, including the full summary query against a populated_vt.vdiff/vdiff_tableschema: samples stripped, all scalar counters preserved, and theLEFT JOIN-with-no-row case returns a NULL report on both versions.make proto.go buildof the affected packages,go vet,gofmt, andgolangci-lint(via pre-commit) are clean.Backport rationale
This is a small, opt-in, backward-compatible change (default behavior is unchanged) that fixes a real operational failure:
vdiff showcan currently fail outright when the aggregated report exceeds gRPC message limits on large diffs, leaving operators with no way to read the summary or mismatch state. Because that failure blocks monitoring of in-flight VDiffs on affected keyspaces, it is worth backporting to the currently supported release branches (release-23.0,release-24.0) so operators on those releases can retrieve summaries for large diffs without upgrading. The change is confined to the vdiff show path and touches no default behavior, so backport risk is low.AI Disclosure
This change was co-authored with Claude Code, which helped with implementation and testing.