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

VDiff: Fix vtctldclient limit bug #14778

Merged
merged 10 commits into from
Dec 14, 2023
Merged
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
28 changes: 19 additions & 9 deletions go/cmd/vtctldclient/command/vreplication/vdiff/vdiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ var (
TargetCells []string
TabletTypes []topodatapb.TabletType
Tables []string
Limit uint32 // We only accept positive values but pass on an int64
Limit int64
Copy link
Member

Choose a reason for hiding this comment

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

If we want to accept only positive values, should this not be uint64?? Either that, or we'll need to add validation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was the idea initially, but vtctlclient allows it and 0 is equivalent to any negative value. We can't use uint64 as we'll then allow larger values than the backend supports. I was on the fence so left it. I can add a quick check for positive values though if you like that more.

Copy link
Member

Choose a reason for hiding this comment

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

What does it mean that "0 is equivalent to any negative value"? What behavior do you get with a value of 0 for any of these flags?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As an example: https://github.com/vitessio/vitess/blob/main/go/vt/vttablet/tabletmanager/vdiff/table_differ.go#L534-L538

But no matter as I added checks to prevent negative values from the client.

FilteredReplicationWaitTime time.Duration
DebugQuery bool
MaxReportSampleRows uint32 // We only accept positive values but pass on an int64
MaxReportSampleRows int64
OnlyPKs bool
UpdateTableStats bool
MaxExtraRowsToCompare uint32 // We only accept positive values but pass on an int64
MaxExtraRowsToCompare int64
Wait bool
WaitUpdateInterval time.Duration
AutoRetry bool
Expand Down Expand Up @@ -113,6 +113,16 @@ var (
createOptions.Tables[i] = strings.TrimSpace(table)
}
}
// Enforce non-negative values for limits and max options.
if createOptions.Limit < 1 {
mattlord marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("--limit must be a positive value")
}
if createOptions.MaxReportSampleRows < 0 {
return fmt.Errorf("--max-report-sample-rows must not be a negative value")
}
if createOptions.MaxExtraRowsToCompare < 0 {
return fmt.Errorf("--max-extra-rows-to-compare must not be a negative value")
}
return nil
}

Expand Down Expand Up @@ -271,16 +281,16 @@ func commandCreate(cmd *cobra.Command, args []string) error {
TabletTypes: createOptions.TabletTypes,
TabletSelectionPreference: tsp,
Tables: createOptions.Tables,
Limit: int64(createOptions.Limit),
Limit: createOptions.Limit,
FilteredReplicationWaitTime: protoutil.DurationToProto(createOptions.FilteredReplicationWaitTime),
DebugQuery: createOptions.DebugQuery,
OnlyPKs: createOptions.OnlyPKs,
UpdateTableStats: createOptions.UpdateTableStats,
MaxExtraRowsToCompare: int64(createOptions.MaxExtraRowsToCompare),
MaxExtraRowsToCompare: createOptions.MaxExtraRowsToCompare,
Wait: createOptions.Wait,
WaitUpdateInterval: protoutil.DurationToProto(createOptions.WaitUpdateInterval),
AutoRetry: createOptions.AutoRetry,
MaxReportSampleRows: int64(createOptions.MaxReportSampleRows),
MaxReportSampleRows: createOptions.MaxReportSampleRows,
})

if err != nil {
Expand Down Expand Up @@ -863,12 +873,12 @@ func registerCommands(root *cobra.Command) {
create.Flags().Var((*topoprotopb.TabletTypeListFlag)(&createOptions.TabletTypes), "tablet-types", "Tablet types to use on the source and target.")
create.Flags().BoolVar(&common.CreateOptions.TabletTypesInPreferenceOrder, "tablet-types-in-preference-order", true, "When performing source tablet selection, look for candidates in the type order as they are listed in the tablet-types flag.")
create.Flags().DurationVar(&createOptions.FilteredReplicationWaitTime, "filtered-replication-wait-time", 30*time.Second, "Specifies the maximum time to wait, in seconds, for replication to catch up when syncing tablet streams.")
create.Flags().Uint32Var(&createOptions.Limit, "limit", math.MaxUint32, "Max rows to stop comparing after.")
create.Flags().Int64Var(&createOptions.Limit, "limit", math.MaxInt64, "Max rows to stop comparing after.")
create.Flags().BoolVar(&createOptions.DebugQuery, "debug-query", false, "Adds a mysql query to the report that can be used for further debugging.")
create.Flags().Uint32Var(&createOptions.MaxReportSampleRows, "max-report-sample-rows", 10, "Maximum number of row differences to report (0 for all differences). NOTE: when increasing this value it is highly recommended to also specify --only-pks")
create.Flags().Int64Var(&createOptions.MaxReportSampleRows, "max-report-sample-rows", 10, "Maximum number of row differences to report (0 for all differences). NOTE: when increasing this value it is highly recommended to also specify --only-pks")
create.Flags().BoolVar(&createOptions.OnlyPKs, "only-pks", false, "When reporting missing rows, only show primary keys in the report.")
create.Flags().StringSliceVar(&createOptions.Tables, "tables", nil, "Only run vdiff for these tables in the workflow.")
create.Flags().Uint32Var(&createOptions.MaxExtraRowsToCompare, "max-extra-rows-to-compare", 1000, "If there are collation differences between the source and target, you can have rows that are identical but simply returned in a different order from MySQL. We will do a second pass to compare the rows for any actual differences in this case and this flag allows you to control the resources used for this operation.")
create.Flags().Int64Var(&createOptions.MaxExtraRowsToCompare, "max-extra-rows-to-compare", 1000, "If there are collation differences between the source and target, you can have rows that are identical but simply returned in a different order from MySQL. We will do a second pass to compare the rows for any actual differences in this case and this flag allows you to control the resources used for this operation.")
create.Flags().BoolVar(&createOptions.Wait, "wait", false, "When creating or resuming a vdiff, wait for it to finish before exiting.")
create.Flags().DurationVar(&createOptions.WaitUpdateInterval, "wait-update-interval", time.Duration(1*time.Minute), "When waiting on a vdiff to finish, check and display the current status this often.")
create.Flags().BoolVar(&createOptions.AutoRetry, "auto-retry", true, "Should this vdiff automatically retry and continue in case of recoverable errors.")
Expand Down
111 changes: 92 additions & 19 deletions go/test/endtoend/vreplication/vdiff2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ import (
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
"golang.org/x/exp/maps"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"

"vitess.io/vitess/go/test/endtoend/cluster"
"vitess.io/vitess/go/vt/sqlparser"

tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
)

type testCase struct {
Expand All @@ -40,10 +46,11 @@ type testCase struct {
retryInsert string
resume bool // test resume functionality with this workflow
// If testing resume, what new rows should be diff'd. These rows must have a PK > all initial rows and retry rows.
resumeInsert string
stop bool // test stop functionality with this workflow
testCLIErrors bool // test CLI errors against this workflow (only needs to be done once)
testCLICreateWait bool // test CLI create and wait until done against this workflow (only needs to be done once)
resumeInsert string
stop bool // test stop functionality with this workflow
testCLIErrors bool // test CLI errors against this workflow (only needs to be done once)
testCLICreateWait bool // test CLI create and wait until done against this workflow (only needs to be done once)
testCLIFlagHandling bool // test vtctldclient flag handling from end-to-end
}

const (
Expand All @@ -55,21 +62,22 @@ const (

var testCases = []*testCase{
{
name: "MoveTables/unsharded to two shards",
workflow: "p1c2",
typ: "MoveTables",
sourceKs: "product",
targetKs: "customer",
sourceShards: "0",
targetShards: "-80,80-",
tabletBaseID: 200,
tables: "customer,Lead,Lead-1",
autoRetryError: true,
retryInsert: `insert into customer(cid, name, typ) values(1991234, 'Testy McTester', 'soho')`,
resume: true,
resumeInsert: `insert into customer(cid, name, typ) values(1992234, 'Testy McTester (redux)', 'enterprise')`,
testCLIErrors: true, // test for errors in the simplest workflow
testCLICreateWait: true, // test wait on create feature against simplest workflow
name: "MoveTables/unsharded to two shards",
workflow: "p1c2",
typ: "MoveTables",
sourceKs: "product",
targetKs: "customer",
sourceShards: "0",
targetShards: "-80,80-",
tabletBaseID: 200,
tables: "customer,Lead,Lead-1",
autoRetryError: true,
retryInsert: `insert into customer(cid, name, typ) values(1991234, 'Testy McTester', 'soho')`,
resume: true,
resumeInsert: `insert into customer(cid, name, typ) values(1992234, 'Testy McTester (redux)', 'enterprise')`,
testCLIErrors: true, // test for errors in the simplest workflow
testCLICreateWait: true, // test wait on create feature against simplest workflow
testCLIFlagHandling: true, // test flag handling end-to-end against simplest workflow
},
{
name: "Reshard Merge/split 2 to 3",
Expand Down Expand Up @@ -207,6 +215,9 @@ func testWorkflow(t *testing.T, vc *VitessCluster, tc *testCase, tks *Keyspace,
if tc.testCLIErrors {
testCLIErrors(t, ksWorkflow, allCellNames)
}
if tc.testCLIFlagHandling {
testCLIFlagHandling(t, tc.targetKs, tc.workflow, cells[0])
}

testDelete(t, ksWorkflow, allCellNames)

Expand Down Expand Up @@ -242,6 +253,68 @@ func testCLIErrors(t *testing.T, ksWorkflow, cells string) {
})
}

// testCLIFlagHandling tests that the vtctldclient CLI flags are handled correctly
// from vtctldclient->vtctld->vttablet->mysqld.
func testCLIFlagHandling(t *testing.T, targetKs, workflowName string, cell *Cell) {
expectedOptions := &tabletmanagerdatapb.VDiffOptions{
CoreOptions: &tabletmanagerdatapb.VDiffCoreOptions{
MaxRows: 999,
MaxExtraRowsToCompare: 777,
AutoRetry: true,
UpdateTableStats: true,
TimeoutSeconds: 60,
},
PickerOptions: &tabletmanagerdatapb.VDiffPickerOptions{
SourceCell: "zone1,zone2,zone3,zonefoosource",
TargetCell: "zone1,zone2,zone3,zonefootarget",
TabletTypes: "replica,primary,rdonly",
},
ReportOptions: &tabletmanagerdatapb.VDiffReportOptions{
MaxSampleRows: 888,
OnlyPks: true,
},
}

t.Run("Client flag handling", func(t *testing.T) {
res, err := vc.VtctldClient.ExecuteCommandWithOutput("vdiff", "--target-keyspace", targetKs, "--workflow", workflowName,
"create",
"--limit", fmt.Sprintf("%d", expectedOptions.CoreOptions.MaxRows),
"--max-report-sample-rows", fmt.Sprintf("%d", expectedOptions.ReportOptions.MaxSampleRows),
"--max-extra-rows-to-compare", fmt.Sprintf("%d", expectedOptions.CoreOptions.MaxExtraRowsToCompare),
"--filtered-replication-wait-time", fmt.Sprintf("%v", time.Duration(expectedOptions.CoreOptions.TimeoutSeconds)*time.Second),
"--source-cells", expectedOptions.PickerOptions.SourceCell,
"--target-cells", expectedOptions.PickerOptions.TargetCell,
"--tablet-types", expectedOptions.PickerOptions.TabletTypes,
fmt.Sprintf("--update-table-stats=%t", expectedOptions.CoreOptions.UpdateTableStats),
fmt.Sprintf("--auto-retry=%t", expectedOptions.CoreOptions.AutoRetry),
fmt.Sprintf("--only-pks=%t", expectedOptions.ReportOptions.OnlyPks),
"--tablet-types-in-preference-order=false", // So tablet_types should not start with "in_order:", which is the default
"--format=json") // So we can easily grab the UUID
require.NoError(t, err, "vdiff command failed: %s", res)
jsonRes := gjson.Parse(res)
vduuid, err := uuid.Parse(jsonRes.Get("UUID").String())
require.NoError(t, err, "invalid UUID: %s", jsonRes.Get("UUID").String())

// Confirm that the options were passed through and saved correctly.
query := sqlparser.BuildParsedQuery("select options from %s.vdiff where vdiff_uuid = %s",
sidecarDBIdentifier, encodeString(vduuid.String())).Query
tablets := vc.getVttabletsInKeyspace(t, cell, targetKs, "PRIMARY")
require.Greater(t, len(tablets), 0, "no primary tablets found in keyspace %s", targetKs)
tablet := maps.Values(tablets)[0]
qres, err := tablet.QueryTablet(query, targetKs, false)
require.NoError(t, err, "query %q failed: %v", query, err)
require.NotNil(t, qres, "query %q returned nil result", query) // Should never happen
require.Equal(t, 1, len(qres.Rows), "query %q returned %d rows, expected 1", query, len(qres.Rows))
require.Equal(t, 1, len(qres.Rows[0]), "query %q returned %d columns, expected 1", query, len(qres.Rows[0]))
storedOptions := &tabletmanagerdatapb.VDiffOptions{}
bytes, err := qres.Rows[0][0].ToBytes()
require.NoError(t, err, "failed to convert result %+v to bytes: %v", qres.Rows[0], err)
err = protojson.Unmarshal(bytes, storedOptions)
require.NoError(t, err, "failed to unmarshal result %s to a %T: %v", string(bytes), storedOptions, err)
require.True(t, proto.Equal(expectedOptions, storedOptions), "stored options %v != expected options %v", storedOptions, expectedOptions)
})
}

func testDelete(t *testing.T, ksWorkflow, cells string) {
t.Run("Delete", func(t *testing.T) {
// Let's be sure that we have at least 3 unique VDiffs.
Expand Down
18 changes: 9 additions & 9 deletions go/vt/vtctl/grpcvtctldclient/client_gen.go

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

10 changes: 5 additions & 5 deletions go/vt/vtctl/localvtctldclient/client_gen.go

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

2 changes: 1 addition & 1 deletion go/vt/vtctl/workflow/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1632,7 +1632,7 @@ func (s *Server) VDiffCreate(ctx context.Context, req *vtctldatapb.VDiffCreateRe
CoreOptions: &tabletmanagerdatapb.VDiffCoreOptions{
Tables: strings.Join(req.Tables, ","),
AutoRetry: req.AutoRetry,
MaxRows: req.MaxExtraRowsToCompare,
MaxRows: req.Limit,
TimeoutSeconds: req.FilteredReplicationWaitTime.Seconds,
MaxExtraRowsToCompare: req.MaxExtraRowsToCompare,
UpdateTableStats: req.UpdateTableStats,
Expand Down
Loading