Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions go/cmd/vtctldclient/command/vreplication/vdiff/vdiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ var (
}{}

showOptions = struct {
Arg string
Verbose bool
Arg string
Verbose bool
SummaryOnly bool
}{}

stopOptions = struct {
Expand Down Expand Up @@ -625,6 +626,7 @@ func commandShow(cmd *cobra.Command, args []string) error {
Workflow: common.BaseOptions.Workflow,
TargetKeyspace: common.BaseOptions.TargetKeyspace,
Arg: showOptions.Arg,
SummaryOnly: showOptions.SummaryOnly,
})

if err != nil {
Expand Down Expand Up @@ -691,6 +693,7 @@ func registerCommands(root *cobra.Command) {
base.AddCommand(resume)

show.Flags().BoolVar(&showOptions.Verbose, "verbose", false, "Show verbose output in summaries")
show.Flags().BoolVar(&showOptions.SummaryOnly, "summary-only", false, "Strip the per-table report's sampled-row arrays (sample row diffs) from each target while keeping the scalar counters and other summary state. Avoids transferring large reports from target primaries where they can exceed gRPC message limits.")
base.AddCommand(show)

stop.Flags().StringSliceVar(&stopOptions.TargetShards, "target-shards", nil, "The target shards to stop the vdiff on; default is all shards.")
Expand Down
710 changes: 364 additions & 346 deletions go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

485 changes: 250 additions & 235 deletions go/vt/proto/vtctldata/vtctldata.pb.go

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions go/vt/proto/vtctldata/vtctldata_vtproto.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions go/vt/vtctl/workflow/vdiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,15 @@ func (s *Server) VDiffShow(ctx context.Context, req *vtctldatapb.VDiffShowReques
Action: string(vdiff.ShowAction),
ActionArg: req.Arg,
}
// Forward summary_only to each target before the fan-out so the report body
// is never selected on the target primaries and thus never sent to vtctld.
if req.GetSummaryOnly() {
tabletreq.Options = &tabletmanagerdatapb.VDiffOptions{
ReportOptions: &tabletmanagerdatapb.VDiffReportOptions{
SummaryOnly: true,
},
}
}

ts, err := s.buildTrafficSwitcher(ctx, req.TargetKeyspace, req.Workflow)
if err != nil {
Expand Down
22 changes: 19 additions & 3 deletions go/vt/vttablet/tabletmanager/vdiff/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,27 @@ func (vde *Engine) PerformVDiffAction(ctx context.Context, req *tabletmanagerdat
return resp, nil
}

func (vde *Engine) getVDiffSummary(vdiffID int64, dbClient binlogplayer.DBClient) (*query.QueryResult, error) {
// vdiffSummaryQuery returns the summary query to run. When summaryOnly is true
// it returns the variant that strips the row-sample arrays from the per-table
// report, keeping the scalar counters. The samples are the part that can grow
// very large (they carry sampled row data, including large blob/JSON columns)
// and, fanned out across many target shards, can push the aggregated response
// past gRPC message limits. Stripping them lets callers that only need the
// vdiff/table state, counts and has_mismatch avoid transferring that data while
// keeping the summary counters accurate. All other summary columns are
// unaffected.
func vdiffSummaryQuery(summaryOnly bool) string {
if summaryOnly {
return sqlVDiffSummaryOnly
}
return sqlVDiffSummary
}

func (vde *Engine) getVDiffSummary(vdiffID int64, dbClient binlogplayer.DBClient, reportOpts *tabletmanagerdatapb.VDiffReportOptions) (*query.QueryResult, error) {
var qr *sqltypes.Result
var err error

query, err := sqlparser.ParseAndBind(sqlVDiffSummary, sqltypes.Int64BindVariable(vdiffID), sqltypes.StringBindVariable(vde.dbName))
query, err := sqlparser.ParseAndBind(vdiffSummaryQuery(reportOpts.GetSummaryOnly()), sqltypes.Int64BindVariable(vdiffID), sqltypes.StringBindVariable(vde.dbName))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -336,7 +352,7 @@ func (vde *Engine) handleShowAction(ctx context.Context, dbClient binlogplayer.D
case 1:
row := qr.Named().Row()
vdiffID, _ := row["id"].ToInt64()
summary, err := vde.getVDiffSummary(vdiffID, dbClient)
summary, err := vde.getVDiffSummary(vdiffID, dbClient, req.GetOptions().GetReportOptions())
resp.Output = summary
if err != nil {
return err
Expand Down
42 changes: 42 additions & 0 deletions go/vt/vttablet/tabletmanager/vdiff/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"
"reflect"
"strings"
"testing"
"time"

Expand All @@ -35,6 +36,47 @@ import (
vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
)

// TestVDiffSummaryQuery asserts that the two summary-query variants differ only
// in how they project the report column: the full variant returns the stored
// report as-is, while the summary-only variant strips just the row-sample
// arrays and keeps the scalar counters. The assertions are structural (split on
// the report select-expression and compare the surrounding column list and
// FROM/WHERE) so harmless SQL formatting changes don't break the test.
func TestVDiffSummaryQuery(t *testing.T) {
const (
fullReportExpr = `vdt.report as report`
summaryReportExpr = `JSON_REMOVE(vdt.report, '$.MismatchedRowsSample', '$.ExtraRowsSourceSample', '$.ExtraRowsTargetSample') as report`
)

full := vdiffSummaryQuery(false)
summary := vdiffSummaryQuery(true)

// Both must be bindable (report/columns aside, the FROM/WHERE has the %a
// placeholders that ParseAndBind fills).
require.Contains(t, full, "%a", "bind placeholders must be preserved for ParseAndBind")
require.Contains(t, summary, "%a", "bind placeholders must be preserved for ParseAndBind")

// Each variant uses its own report select-expression and not the other's.
require.Contains(t, full, fullReportExpr, "full query must select the stored report as-is")
require.NotContains(t, full, "JSON_REMOVE", "full query must not strip the report")
require.Contains(t, summary, summaryReportExpr, "summary-only query must strip the row-sample arrays from the report")

// The summary-only variant must strip the sample arrays but must not touch
// the scalar counters, so the summary counts stay accurate.
for _, sample := range []string{"MismatchedRowsSample", "ExtraRowsSourceSample", "ExtraRowsTargetSample"} {
require.Contains(t, summaryReportExpr, sample, "expected sample array %q to be stripped", sample)
}

// The two variants must be identical everywhere except the report
// select-expression: swapping in the full expression must reproduce the
// full query exactly, so no other column or clause can silently differ.
require.Equal(t,
full,
strings.Replace(summary, summaryReportExpr, fullReportExpr, 1),
"summary-only must differ from the full query only in the report select-expression",
)
}

func TestPerformVDiffAction(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
Expand Down
18 changes: 15 additions & 3 deletions go/vt/vttablet/tabletmanager/vdiff/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,24 @@ const (
where vd.keyspace = %a and vd.workflow = %a and vd.db_name = %a`
sqlDeleteVDiffByUUID = `delete from vd, vdt using _vt.vdiff as vd left join _vt.vdiff_table as vdt on (vd.id = vdt.vdiff_id)
where vd.vdiff_uuid = %a and vd.db_name = %a`
sqlVDiffSummary = `select vd.state as vdiff_state, vd.last_error as last_error, vdt.table_name as table_name,
// The vdiff summary query is composed from shared pieces so the column list
// and FROM/WHERE clauses are defined exactly once. The two variants differ
// only in the report select-expression: sqlVDiffSummary returns the stored
// per-table report as-is, while sqlVDiffSummaryOnly strips the potentially
// very large row-sample arrays from the report (the part that can push the
// aggregated response past gRPC message limits) while preserving the scalar
// counters (ProcessedRows, MatchingRows, MismatchedRows, ExtraRows*) so the
// summary counts stay accurate. JSON_REMOVE returns NULL when the report is
// NULL (no joined vdiff_table row), matching the plain-column behavior. Go
// concatenates these string constants at compile time.
vdiffSummaryCols = `select vd.state as vdiff_state, vd.last_error as last_error, vdt.table_name as table_name,
vd.vdiff_uuid as 'uuid', vdt.state as table_state, vdt.table_rows as table_rows,
vd.started_at as started_at, vdt.rows_compared as rows_compared, vd.completed_at as completed_at,
IF(vdt.mismatch = 1, 1, 0) as has_mismatch, vdt.report as report
from _vt.vdiff as vd left join _vt.vdiff_table as vdt on (vd.id = vdt.vdiff_id)
IF(vdt.mismatch = 1, 1, 0) as has_mismatch, `
vdiffSummaryFrom = ` from _vt.vdiff as vd left join _vt.vdiff_table as vdt on (vd.id = vdt.vdiff_id)
where vd.id = %a and vd.db_name = %a`
sqlVDiffSummary = vdiffSummaryCols + `vdt.report as report` + vdiffSummaryFrom
sqlVDiffSummaryOnly = vdiffSummaryCols + `JSON_REMOVE(vdt.report, '$.MismatchedRowsSample', '$.ExtraRowsSourceSample', '$.ExtraRowsTargetSample') as report` + vdiffSummaryFrom
// sqlUpdateVDiffState has a penultimate placeholder for any additional columns you want to update, e.g. `, foo = 1`.
// It also truncates the error if needed to ensure that we can save the state when the error text is very long.
sqlUpdateVDiffState = "update _vt.vdiff set state = %s, last_error = left(%s, 1024) %s where id = %d and db_name = %s"
Expand Down
9 changes: 9 additions & 0 deletions proto/tabletmanagerdata.proto
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,15 @@ message VDiffReportOptions {
string format = 3;
int64 max_sample_rows = 4;
int64 row_diff_column_truncate_at = 5;
// summary_only strips the per-table report's sampled-row arrays
// (MismatchedRowsSample, ExtraRowsSourceSample, ExtraRowsTargetSample) from a
// show response while preserving the scalar counters (ProcessedRows,
// MatchingRows, MismatchedRows, ExtraRows*) and all other summary state. The
// sampled rows carry actual row data (e.g. large blob/JSON columns) and,
// aggregated across target shards, can push the response past gRPC message
// limits; stripping them lets callers avoid transferring that data while
// keeping the reported counts accurate.
bool summary_only = 6;
}

message VDiffCoreOptions {
Expand Down
6 changes: 6 additions & 0 deletions proto/vtctldata.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,12 @@ message VDiffShowRequest {
string target_keyspace = 2;
// This will be 'all', 'last', or a UUID.
string arg = 3;
// summary_only requests that each target strip the per-table report's
// sampled-row arrays (sample row diffs) while preserving the scalar counters
// and all other summary state. This avoids transferring the sampled rows
// (e.g. for tables with large blob/JSON rows), which aggregated across target
// primaries can exceed gRPC message limits, while keeping counts accurate.
bool summary_only = 4;
}

message VDiffShowResponse {
Expand Down
12 changes: 12 additions & 0 deletions web/vtadmin/src/proto/vtadmin.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading