vitess.io/vitess@v0.16.2/go/test/endtoend/onlineddl/vrepl_stress_suite/onlineddl_vrepl_stress_suite_test.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 /* 18 This endtoend suite tests VReplication based Online DDL under stress (concurrent INSERT/UPDATE/DELETE queries), 19 and looks for before/after table data to be identical. 20 This suite specifically targets choice of unique key: PRIMARY vs non-PRIMARY, numeric, textual, compound. 21 The scenarios caused by this suite cause VReplication to iterate the table in: 22 - expected incremental order (id) 23 - expected decremental order (negative id values) 24 - random order (random textual checksums) 25 - other 26 */ 27 28 package vreplstress 29 30 import ( 31 "context" 32 "flag" 33 "fmt" 34 "math/rand" 35 "os" 36 "path" 37 "strings" 38 "sync" 39 "sync/atomic" 40 "testing" 41 "time" 42 43 "vitess.io/vitess/go/mysql" 44 "vitess.io/vitess/go/timer" 45 "vitess.io/vitess/go/vt/log" 46 "vitess.io/vitess/go/vt/schema" 47 48 "vitess.io/vitess/go/test/endtoend/cluster" 49 "vitess.io/vitess/go/test/endtoend/onlineddl" 50 51 "github.com/stretchr/testify/assert" 52 "github.com/stretchr/testify/require" 53 ) 54 55 type testcase struct { 56 // name is a human readable name for the test 57 name string 58 // prepareStatement modifies the table (direct strategy) before it gets populated 59 prepareStatement string 60 // alterStatement is the online statement used in the test 61 alterStatement string 62 // expectFailure is typically false. If true then we do not compare table data results 63 expectFailure bool 64 // expectAddedUniqueKeys is the number of added constraints 65 expectAddedUniqueKeys int64 66 // expectRemovedUniqueKeys is the number of alleviated constraints 67 expectRemovedUniqueKeys int64 68 // autoIncInsert is a special case where we don't generate id values. It's a specific test case. 69 autoIncInsert bool 70 } 71 72 var ( 73 clusterInstance *cluster.LocalProcessCluster 74 vtParams mysql.ConnParams 75 evaluatedMysqlParams *mysql.ConnParams 76 77 directDDLStrategy = "direct" 78 onlineDDLStrategy = "vitess -vreplication-test-suite -skip-topo" 79 hostname = "localhost" 80 keyspaceName = "ks" 81 cell = "zone1" 82 shards []cluster.Shard 83 opOrder int64 84 opOrderMutex sync.Mutex 85 schemaChangeDirectory = "" 86 tableName = "stress_test" 87 afterTableName = "stress_test_after" 88 cleanupStatements = []string{ 89 `DROP TABLE IF EXISTS stress_test`, 90 `DROP TABLE IF EXISTS stress_test_before`, 91 `DROP TABLE IF EXISTS stress_test_after`, 92 } 93 createStatement = ` 94 CREATE TABLE stress_test ( 95 id bigint not null, 96 id_negative bigint not null, 97 rand_text varchar(40) not null default '', 98 rand_num bigint unsigned not null, 99 nullable_num int default null, 100 op_order bigint unsigned not null default 0, 101 hint_col varchar(64) not null default '', 102 created_timestamp timestamp not null default current_timestamp, 103 updates int unsigned not null default 0, 104 PRIMARY KEY (id), 105 KEY id_idx(id) 106 ) ENGINE=InnoDB 107 ` 108 testCases = []testcase{ 109 { 110 name: "trivial PK", 111 prepareStatement: "", 112 alterStatement: "engine=innodb", 113 }, 114 { 115 name: "autoinc PK", 116 prepareStatement: "modify id bigint not null auto_increment", 117 alterStatement: "engine=innodb", 118 autoIncInsert: true, 119 }, 120 { 121 name: "UK similar to PK, no PK", 122 prepareStatement: "add unique key id_uidx(id)", 123 alterStatement: "drop primary key", 124 }, 125 { 126 name: "negative PK", 127 prepareStatement: "drop primary key, add primary key (id_negative)", 128 alterStatement: "engine=innodb", 129 }, 130 { 131 name: "negative UK, no PK", 132 prepareStatement: "add unique key negative_uidx(id_negative)", 133 alterStatement: "drop primary key", 134 expectRemovedUniqueKeys: 1, 135 }, 136 { 137 name: "negative UK, different PK", 138 prepareStatement: "add unique key negative_uidx(id_negative)", 139 alterStatement: "drop primary key, add primary key(rand_text(40))", 140 expectAddedUniqueKeys: 1, 141 expectRemovedUniqueKeys: 1, 142 }, 143 { 144 name: "text UK, no PK", 145 prepareStatement: "add unique key text_uidx(rand_text(40))", 146 alterStatement: "drop primary key", 147 expectRemovedUniqueKeys: 1, 148 }, 149 { 150 name: "text UK, different PK", 151 prepareStatement: "add unique key text_uidx(rand_text(40))", 152 alterStatement: "drop primary key, add primary key (id, id_negative)", 153 expectRemovedUniqueKeys: 1, 154 }, 155 { 156 name: "compound UK 1 by text, no PK", 157 prepareStatement: "add unique key compound_uidx(rand_text(40), id_negative)", 158 alterStatement: "drop primary key", 159 expectRemovedUniqueKeys: 1, 160 }, 161 { 162 name: "compound UK 2 by negative, no PK", 163 prepareStatement: "add unique key compound_uidx(id_negative, rand_text(40))", 164 alterStatement: "drop primary key", 165 expectRemovedUniqueKeys: 1, 166 }, 167 { 168 name: "compound UK 3 by ascending int, no PK", 169 prepareStatement: "add unique key compound_uidx(id, rand_num, rand_text(40))", 170 alterStatement: "drop primary key", 171 expectRemovedUniqueKeys: 1, 172 }, 173 { 174 name: "compound UK 4 by rand int, no PK", 175 prepareStatement: "add unique key compound_uidx(rand_num, rand_text(40))", 176 alterStatement: "drop primary key", 177 expectRemovedUniqueKeys: 1, 178 }, 179 { 180 name: "compound UK 5 by rand int, different PK", 181 prepareStatement: "add unique key compound_uidx(rand_num, rand_text(40))", 182 alterStatement: "drop primary key, add primary key (id, id_negative)", 183 expectRemovedUniqueKeys: 1, 184 }, 185 { 186 name: "multiple UK choices 1", 187 prepareStatement: "add unique key compound_uidx(rand_num, rand_text(40)), add unique key negative_uidx(id_negative)", 188 alterStatement: "drop primary key, add primary key(updates, id)", 189 expectRemovedUniqueKeys: 1, 190 }, 191 { 192 name: "multiple UK choices 2", 193 prepareStatement: "add unique key compound_uidx(rand_num, rand_text(40)), add unique key negative_uidx(id_negative)", 194 alterStatement: "drop primary key, add primary key(id, id_negative)", 195 expectRemovedUniqueKeys: 1, 196 }, 197 { 198 name: "multiple UK choices including nullable with PK", 199 prepareStatement: "add unique key compound_uidx(rand_num, rand_text(40)), add unique key nullable_uidx(nullable_num, id_negative), add unique key negative_uidx(id_negative)", 200 alterStatement: "drop primary key, drop key negative_uidx, add primary key(id_negative)", 201 expectRemovedUniqueKeys: 1, 202 }, 203 { 204 name: "multiple UK choices including nullable", 205 prepareStatement: "add unique key compound_uidx(rand_num, rand_text(40)), add unique key nullable_uidx(nullable_num, id_negative), add unique key negative_uidx(id_negative)", 206 alterStatement: "drop primary key, add primary key(updates, id)", 207 expectRemovedUniqueKeys: 1, 208 }, 209 { 210 name: "different PRIMARY KEY", 211 prepareStatement: "", 212 alterStatement: "drop primary key, add primary key(id_negative)", 213 expectAddedUniqueKeys: 1, 214 expectRemovedUniqueKeys: 1, 215 }, 216 { 217 name: "extended PRIMARY KEY", 218 prepareStatement: "", 219 alterStatement: "drop primary key, add primary key(id, id_negative)", 220 expectRemovedUniqueKeys: 1, 221 }, 222 { 223 name: "extended PRIMARY KEY, different order", 224 prepareStatement: "", 225 alterStatement: "drop primary key, add primary key(id_negative, id)", 226 expectRemovedUniqueKeys: 1, 227 }, 228 { 229 name: "reduced PRIMARY KEY", 230 prepareStatement: "drop primary key, add primary key(id, id_negative)", 231 alterStatement: "drop primary key, add primary key(id)", 232 expectAddedUniqueKeys: 1, 233 }, 234 { 235 name: "reduced PRIMARY KEY 2", 236 prepareStatement: "drop primary key, add primary key(id, id_negative)", 237 alterStatement: "drop primary key, add primary key(id_negative)", 238 expectAddedUniqueKeys: 1, 239 }, 240 { 241 name: "different PRIMARY KEY, text", 242 prepareStatement: "", 243 alterStatement: "drop primary key, add primary key(rand_text(40))", 244 expectAddedUniqueKeys: 1, 245 expectRemovedUniqueKeys: 1, 246 }, 247 { 248 name: "different PRIMARY KEY, rand", 249 prepareStatement: "", 250 alterStatement: "drop primary key, add primary key(rand_num, rand_text(40))", 251 expectAddedUniqueKeys: 1, 252 expectRemovedUniqueKeys: 1, 253 }, 254 { 255 name: "different PRIMARY KEY, from negative to int", 256 prepareStatement: "drop primary key, add primary key(id_negative)", 257 alterStatement: "drop primary key, add primary key(id)", 258 expectAddedUniqueKeys: 1, 259 expectRemovedUniqueKeys: 1, 260 }, 261 { 262 name: "different PRIMARY KEY, from text to int", 263 prepareStatement: "drop primary key, add primary key(rand_text(40))", 264 alterStatement: "drop primary key, add primary key(id)", 265 expectAddedUniqueKeys: 1, 266 expectRemovedUniqueKeys: 1, 267 }, 268 { 269 name: "different PRIMARY KEY, from text to rand", 270 prepareStatement: "drop primary key, add primary key(rand_text(40))", 271 alterStatement: "drop primary key, add primary key(rand_num, rand_text(40))", 272 expectRemovedUniqueKeys: 1, 273 }, 274 { 275 name: "partially shared PRIMARY KEY 1", 276 prepareStatement: "drop primary key, add primary key(id, id_negative)", 277 alterStatement: "drop primary key, add primary key(id, rand_text(40))", 278 expectAddedUniqueKeys: 1, 279 expectRemovedUniqueKeys: 1, 280 }, 281 { 282 name: "partially shared PRIMARY KEY 2", 283 prepareStatement: "drop primary key, add primary key(id, id_negative)", 284 alterStatement: "drop primary key, add primary key(id_negative, rand_text(40))", 285 expectAddedUniqueKeys: 1, 286 expectRemovedUniqueKeys: 1, 287 }, 288 { 289 name: "partially shared PRIMARY KEY 3", 290 prepareStatement: "drop primary key, add primary key(id, id_negative)", 291 alterStatement: "drop primary key, add primary key(rand_text(40), id)", 292 expectAddedUniqueKeys: 1, 293 expectRemovedUniqueKeys: 1, 294 }, 295 { 296 name: "partially shared PRIMARY KEY 4", 297 prepareStatement: "drop primary key, add primary key(id_negative, id)", 298 alterStatement: "drop primary key, add primary key(rand_text(40), id)", 299 expectAddedUniqueKeys: 1, 300 expectRemovedUniqueKeys: 1, 301 }, 302 { 303 name: "different PRIMARY KEY vs UNIQUE KEY", 304 prepareStatement: "", 305 alterStatement: "drop primary key, add unique key(id_negative)", 306 expectAddedUniqueKeys: 1, 307 expectRemovedUniqueKeys: 1, 308 }, 309 { 310 name: "no shared UK, multiple options", 311 prepareStatement: "add unique key negative_uidx(id_negative)", 312 alterStatement: "drop primary key, drop key negative_uidx, add primary key(rand_text(40)), add unique key negtext_uidx(id_negative, rand_text(40))", 313 expectAddedUniqueKeys: 1, 314 expectRemovedUniqueKeys: 2, 315 }, 316 { 317 name: "fail; no uk on target", 318 prepareStatement: "", 319 alterStatement: "drop primary key", 320 expectFailure: true, 321 }, 322 { 323 name: "fail; only nullable shared uk", 324 prepareStatement: "add unique key nullable_uidx(nullable_num)", 325 alterStatement: "drop primary key", 326 expectFailure: true, 327 }, 328 } 329 alterHintStatement = ` 330 alter table stress_test modify hint_col varchar(64) not null default '%s' 331 ` 332 333 insertRowAutoIncStatement = ` 334 INSERT IGNORE INTO stress_test (id, id_negative, rand_text, rand_num, op_order) VALUES (NULL, %d, concat(left(md5(%d), 8), '_', %d), floor(rand()*1000000), %d) 335 ` 336 insertRowStatement = ` 337 INSERT IGNORE INTO stress_test (id, id_negative, rand_text, rand_num, op_order) VALUES (%d, %d, concat(left(md5(%d), 8), '_', %d), floor(rand()*1000000), %d) 338 ` 339 updateRowStatement = ` 340 UPDATE stress_test SET op_order=%d, updates=updates+1 WHERE id=%d 341 ` 342 deleteRowStatement = ` 343 DELETE FROM stress_test WHERE id=%d 344 ` 345 selectCountFromTable = ` 346 SELECT count(*) as c FROM stress_test 347 ` 348 selectCountFromTableBefore = ` 349 SELECT count(*) as c FROM stress_test_before 350 ` 351 selectCountFromTableAfter = ` 352 SELECT count(*) as c FROM stress_test_after 353 ` 354 selectMaxOpOrderFromTableBefore = ` 355 SELECT MAX(op_order) as m FROM stress_test_before 356 ` 357 selectMaxOpOrderFromTableAfter = ` 358 SELECT MAX(op_order) as m FROM stress_test_after 359 ` 360 selectBeforeTable = ` 361 SELECT * FROM stress_test_before order by id, id_negative, rand_text, rand_num 362 ` 363 selectAfterTable = ` 364 SELECT * FROM stress_test_after order by id, id_negative, rand_text, rand_num 365 ` 366 truncateStatement = ` 367 TRUNCATE TABLE stress_test 368 ` 369 ) 370 371 const ( 372 maxTableRows = 4096 373 maxConcurrency = 15 374 singleConnectionSleepInterval = 5 * time.Millisecond 375 periodicSleepPercent = 10 // in the range (0,100). 10 means 10% sleep time throught the stress load. 376 waitForStatusTimeout = 180 * time.Second 377 ) 378 379 func resetOpOrder() { 380 opOrderMutex.Lock() 381 defer opOrderMutex.Unlock() 382 opOrder = 0 383 } 384 385 func nextOpOrder() int64 { 386 opOrderMutex.Lock() 387 defer opOrderMutex.Unlock() 388 opOrder++ 389 return opOrder 390 } 391 392 func getTablet() *cluster.Vttablet { 393 return clusterInstance.Keyspaces[0].Shards[0].Vttablets[0] 394 } 395 396 func mysqlParams() *mysql.ConnParams { 397 if evaluatedMysqlParams != nil { 398 return evaluatedMysqlParams 399 } 400 evaluatedMysqlParams = &mysql.ConnParams{ 401 Uname: "vt_dba", 402 UnixSocket: path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d", getTablet().TabletUID), "/mysql.sock"), 403 DbName: fmt.Sprintf("vt_%s", keyspaceName), 404 } 405 return evaluatedMysqlParams 406 } 407 408 func TestMain(m *testing.M) { 409 defer cluster.PanicHandler(nil) 410 flag.Parse() 411 412 exitcode, err := func() (int, error) { 413 clusterInstance = cluster.NewCluster(cell, hostname) 414 schemaChangeDirectory = path.Join("/tmp", fmt.Sprintf("schema_change_dir_%d", clusterInstance.GetAndReserveTabletUID())) 415 defer os.RemoveAll(schemaChangeDirectory) 416 defer clusterInstance.Teardown() 417 418 if _, err := os.Stat(schemaChangeDirectory); os.IsNotExist(err) { 419 _ = os.Mkdir(schemaChangeDirectory, 0700) 420 } 421 422 clusterInstance.VtctldExtraArgs = []string{ 423 "--schema_change_dir", schemaChangeDirectory, 424 "--schema_change_controller", "local", 425 "--schema_change_check_interval", "1", 426 } 427 428 // --vstream_packet_size is set to a small value that ensures we get multiple stream iterations, 429 // thereby examining lastPK on vcopier side. We will be iterating tables using non-PK order throughout 430 // this test suite, and so the low setting ensures we hit the more interesting code paths. 431 clusterInstance.VtTabletExtraArgs = []string{ 432 "--enable-lag-throttler", 433 "--throttle_threshold", "1s", 434 "--heartbeat_enable", 435 "--heartbeat_interval", "250ms", 436 "--heartbeat_on_demand_duration", "5s", 437 "--migration_check_interval", "5s", 438 "--vstream_packet_size", "4096", // Keep this value small and below 10k to ensure multilple vstream iterations 439 "--watch_replication_stream", 440 } 441 clusterInstance.VtGateExtraArgs = []string{ 442 "--ddl_strategy", "online", 443 } 444 445 if err := clusterInstance.StartTopo(); err != nil { 446 return 1, err 447 } 448 449 // Start keyspace 450 keyspace := &cluster.Keyspace{ 451 Name: keyspaceName, 452 } 453 454 // No need for replicas in this stress test 455 if err := clusterInstance.StartKeyspace(*keyspace, []string{"1"}, 0, false); err != nil { 456 return 1, err 457 } 458 459 vtgateInstance := clusterInstance.NewVtgateInstance() 460 // Start vtgate 461 if err := vtgateInstance.Setup(); err != nil { 462 return 1, err 463 } 464 // ensure it is torn down during cluster TearDown 465 clusterInstance.VtgateProcess = *vtgateInstance 466 vtParams = mysql.ConnParams{ 467 Host: clusterInstance.Hostname, 468 Port: clusterInstance.VtgateMySQLPort, 469 } 470 471 return m.Run(), nil 472 }() 473 if err != nil { 474 fmt.Printf("%v\n", err) 475 os.Exit(1) 476 } else { 477 os.Exit(exitcode) 478 } 479 480 } 481 482 func TestSchemaChange(t *testing.T) { 483 defer cluster.PanicHandler(t) 484 485 shards = clusterInstance.Keyspaces[0].Shards 486 require.Equal(t, 1, len(shards)) 487 488 for _, testcase := range testCases { 489 require.NotEmpty(t, testcase.name) 490 t.Run(testcase.name, func(t *testing.T) { 491 t.Run("cancel pending migrations", func(t *testing.T) { 492 cancelQuery := "alter vitess_migration cancel all" 493 r := onlineddl.VtgateExecQuery(t, &vtParams, cancelQuery, "") 494 if r.RowsAffected > 0 { 495 fmt.Printf("# Cancelled migrations (for debug purposes): %d\n", r.RowsAffected) 496 } 497 }) 498 t.Run("create schema", func(t *testing.T) { 499 assert.Equal(t, 1, len(clusterInstance.Keyspaces[0].Shards)) 500 testWithInitialSchema(t) 501 }) 502 t.Run("prepare table", func(t *testing.T) { 503 if testcase.prepareStatement != "" { 504 fullStatement := fmt.Sprintf("alter table %s %s", tableName, testcase.prepareStatement) 505 onlineddl.VtgateExecDDL(t, &vtParams, directDDLStrategy, fullStatement, "") 506 } 507 }) 508 t.Run("init table data", func(t *testing.T) { 509 initTable(t) 510 }) 511 t.Run("migrate", func(t *testing.T) { 512 require.NotEmpty(t, testcase.alterStatement) 513 514 hintText := fmt.Sprintf("hint-after-alter-%d", rand.Int31n(int32(maxTableRows))) 515 hintStatement := fmt.Sprintf(alterHintStatement, hintText) 516 fullStatement := fmt.Sprintf("%s, %s", hintStatement, testcase.alterStatement) 517 518 ctx, cancel := context.WithCancel(context.Background()) 519 var wg sync.WaitGroup 520 wg.Add(1) 521 go func() { 522 defer wg.Done() 523 runMultipleConnections(ctx, t, testcase.autoIncInsert) 524 }() 525 uuid := testOnlineDDLStatement(t, fullStatement, onlineDDLStrategy, "vtgate", hintText) 526 expectStatus := schema.OnlineDDLStatusComplete 527 if testcase.expectFailure { 528 expectStatus = schema.OnlineDDLStatusFailed 529 } 530 status := onlineddl.WaitForMigrationStatus(t, &vtParams, shards, uuid, waitForStatusTimeout, expectStatus) 531 fmt.Printf("# Migration status (for debug purposes): <%s>\n", status) 532 onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, expectStatus) 533 cancel() // will cause runMultipleConnections() to terminate 534 wg.Wait() 535 if !testcase.expectFailure { 536 testCompareBeforeAfterTables(t, testcase.autoIncInsert) 537 } 538 539 rs := onlineddl.ReadMigrations(t, &vtParams, uuid) 540 for _, row := range rs.Named().Rows { 541 assert.Equal(t, testcase.expectAddedUniqueKeys, row.AsInt64("added_unique_keys", 0), "expectAddedUniqueKeys") 542 assert.Equal(t, testcase.expectRemovedUniqueKeys, row.AsInt64("removed_unique_keys", 0), "expectRemovedUniqueKeys") 543 } 544 }) 545 }) 546 } 547 } 548 549 func testWithInitialSchema(t *testing.T) { 550 // Create the stress table 551 for _, statement := range cleanupStatements { 552 err := clusterInstance.VtctlclientProcess.ApplySchema(keyspaceName, statement) 553 require.Nil(t, err) 554 } 555 err := clusterInstance.VtctlclientProcess.ApplySchema(keyspaceName, createStatement) 556 require.Nil(t, err) 557 558 // Check if table is created 559 checkTable(t, tableName) 560 } 561 562 // testOnlineDDLStatement runs an online DDL, ALTER statement 563 func testOnlineDDLStatement(t *testing.T, alterStatement string, ddlStrategy string, executeStrategy string, expectHint string) (uuid string) { 564 if executeStrategy == "vtgate" { 565 row := onlineddl.VtgateExecDDL(t, &vtParams, ddlStrategy, alterStatement, "").Named().Row() 566 if row != nil { 567 uuid = row.AsString("uuid", "") 568 } 569 } else { 570 var err error 571 uuid, err = clusterInstance.VtctlclientProcess.ApplySchemaWithOutput(keyspaceName, alterStatement, cluster.VtctlClientParams{DDLStrategy: ddlStrategy}) 572 assert.NoError(t, err) 573 } 574 uuid = strings.TrimSpace(uuid) 575 fmt.Println("# Generated UUID (for debug purposes):") 576 fmt.Printf("<%s>\n", uuid) 577 578 strategySetting, err := schema.ParseDDLStrategy(ddlStrategy) 579 assert.NoError(t, err) 580 581 status := schema.OnlineDDLStatusComplete 582 if !strategySetting.Strategy.IsDirect() { 583 status = onlineddl.WaitForMigrationStatus(t, &vtParams, shards, uuid, waitForStatusTimeout, schema.OnlineDDLStatusComplete, schema.OnlineDDLStatusFailed) 584 fmt.Printf("# Migration status (for debug purposes): <%s>\n", status) 585 } 586 587 if expectHint != "" && status == schema.OnlineDDLStatusComplete { 588 checkMigratedTable(t, afterTableName, expectHint) 589 } 590 return uuid 591 } 592 593 // checkTable checks the number of tables in the first two shards. 594 func checkTable(t *testing.T, showTableName string) { 595 for i := range clusterInstance.Keyspaces[0].Shards { 596 checkTablesCount(t, clusterInstance.Keyspaces[0].Shards[i].Vttablets[0], showTableName, 1) 597 } 598 } 599 600 // checkTablesCount checks the number of tables in the given tablet 601 func checkTablesCount(t *testing.T, tablet *cluster.Vttablet, showTableName string, expectCount int) { 602 query := fmt.Sprintf(`show tables like '%%%s%%';`, showTableName) 603 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 604 defer cancel() 605 rowcount := 0 606 for { 607 queryResult, err := tablet.VttabletProcess.QueryTablet(query, keyspaceName, true) 608 require.Nil(t, err) 609 rowcount = len(queryResult.Rows) 610 if rowcount > 0 { 611 break 612 } 613 614 select { 615 case <-time.After(time.Second): 616 case <-ctx.Done(): 617 break 618 } 619 } 620 assert.Equal(t, expectCount, rowcount) 621 } 622 623 // checkMigratedTables checks the CREATE STATEMENT of a table after migration 624 func checkMigratedTable(t *testing.T, tableName, expectHint string) { 625 for i := range clusterInstance.Keyspaces[0].Shards { 626 createStatement := getCreateTableStatement(t, clusterInstance.Keyspaces[0].Shards[i].Vttablets[0], tableName) 627 assert.Contains(t, createStatement, expectHint) 628 } 629 } 630 631 // getCreateTableStatement returns the CREATE TABLE statement for a given table 632 func getCreateTableStatement(t *testing.T, tablet *cluster.Vttablet, tableName string) (statement string) { 633 queryResult, err := tablet.VttabletProcess.QueryTablet(fmt.Sprintf("show create table %s;", tableName), keyspaceName, true) 634 require.Nil(t, err) 635 636 assert.Equal(t, len(queryResult.Rows), 1) 637 assert.Equal(t, len(queryResult.Rows[0]), 2) // table name, create statement 638 statement = queryResult.Rows[0][1].ToString() 639 return statement 640 } 641 642 func generateInsert(t *testing.T, conn *mysql.Conn, autoIncInsert bool) error { 643 id := rand.Int31n(int32(maxTableRows)) 644 query := fmt.Sprintf(insertRowStatement, id, -id, id, id, nextOpOrder()) 645 if autoIncInsert { 646 id = rand.Int31() 647 query = fmt.Sprintf(insertRowAutoIncStatement, -id, id, id, nextOpOrder()) 648 } 649 qr, err := conn.ExecuteFetch(query, 1000, true) 650 if err == nil && qr != nil { 651 assert.Less(t, qr.RowsAffected, uint64(2)) 652 } 653 return err 654 } 655 656 func generateUpdate(t *testing.T, conn *mysql.Conn) error { 657 id := rand.Int31n(int32(maxTableRows)) 658 query := fmt.Sprintf(updateRowStatement, nextOpOrder(), id) 659 qr, err := conn.ExecuteFetch(query, 1000, true) 660 if err == nil && qr != nil { 661 assert.Less(t, qr.RowsAffected, uint64(2)) 662 } 663 return err 664 } 665 666 func generateDelete(t *testing.T, conn *mysql.Conn) error { 667 id := rand.Int31n(int32(maxTableRows)) 668 query := fmt.Sprintf(deleteRowStatement, id) 669 qr, err := conn.ExecuteFetch(query, 1000, true) 670 if err == nil && qr != nil { 671 assert.Less(t, qr.RowsAffected, uint64(2)) 672 } 673 return err 674 } 675 676 func runSingleConnection(ctx context.Context, t *testing.T, autoIncInsert bool, done *int64) { 677 log.Infof("Running single connection") 678 conn, err := mysql.Connect(ctx, &vtParams) 679 require.Nil(t, err) 680 defer conn.Close() 681 682 _, err = conn.ExecuteFetch("set autocommit=1", 1, false) 683 require.Nil(t, err) 684 _, err = conn.ExecuteFetch("set transaction isolation level read committed", 1, false) 685 require.Nil(t, err) 686 _, err = conn.ExecuteFetch("set innodb_lock_wait_timeout=1", 1, false) 687 require.Nil(t, err) 688 689 periodicRest := timer.NewRateLimiter(time.Second) 690 defer periodicRest.Stop() 691 for { 692 if atomic.LoadInt64(done) == 1 { 693 log.Infof("Terminating single connection") 694 return 695 } 696 switch rand.Int31n(3) { 697 case 0: 698 err = generateInsert(t, conn, autoIncInsert) 699 case 1: 700 err = generateUpdate(t, conn) 701 case 2: 702 err = generateDelete(t, conn) 703 } 704 if err != nil { 705 if strings.Contains(err.Error(), "doesn't exist") { 706 // Table renamed to _before, due to -vreplication-test-suite flag 707 err = nil 708 } 709 if sqlErr, ok := err.(*mysql.SQLError); ok { 710 switch sqlErr.Number() { 711 case mysql.ERLockDeadlock: 712 // That's fine. We create a lot of contention; some transactions will deadlock and 713 // rollback. It happens, and we can ignore those and keep on going. 714 err = nil 715 } 716 } 717 } 718 assert.Nil(t, err) 719 time.Sleep(singleConnectionSleepInterval) 720 // Most o fthe time, we want the load to be high, so as to create real stress and potentially 721 // expose bugs in vreplication (the objective of this test!). 722 // However, some platforms (GitHub CI) can suffocate from this load. We choose to keep the load 723 // high, when it runs, but then also take a periodic break and let the system recover. 724 // We prefer this over reducing the load in general. In our method here, we have full load 90% of 725 // the time, then relaxation 10% of the time. 726 periodicRest.Do(func() error { 727 time.Sleep(time.Second * periodicSleepPercent / 100) 728 return nil 729 }) 730 } 731 } 732 733 func runMultipleConnections(ctx context.Context, t *testing.T, autoIncInsert bool) { 734 log.Infof("Running multiple connections") 735 var done int64 736 var wg sync.WaitGroup 737 for i := 0; i < maxConcurrency; i++ { 738 wg.Add(1) 739 go func() { 740 defer wg.Done() 741 runSingleConnection(ctx, t, autoIncInsert, &done) 742 }() 743 } 744 <-ctx.Done() 745 atomic.StoreInt64(&done, 1) 746 log.Infof("Running multiple connections: done") 747 wg.Wait() 748 log.Infof("All connections cancelled") 749 } 750 751 func initTable(t *testing.T) { 752 log.Infof("initTable begin") 753 defer log.Infof("initTable complete") 754 755 ctx := context.Background() 756 conn, err := mysql.Connect(ctx, &vtParams) 757 require.Nil(t, err) 758 defer conn.Close() 759 760 resetOpOrder() 761 762 _, err = conn.ExecuteFetch(truncateStatement, 1000, true) 763 require.Nil(t, err) 764 765 for i := 0; i < maxTableRows/2; i++ { 766 generateInsert(t, conn, false) 767 } 768 for i := 0; i < maxTableRows/4; i++ { 769 generateUpdate(t, conn) 770 } 771 for i := 0; i < maxTableRows/4; i++ { 772 generateDelete(t, conn) 773 } 774 { 775 // Validate table is populated 776 rs, err := conn.ExecuteFetch(selectCountFromTable, 1000, true) 777 require.Nil(t, err) 778 row := rs.Named().Row() 779 require.NotNil(t, row) 780 781 count := row.AsInt64("c", 0) 782 require.NotZero(t, count) 783 require.Less(t, count, int64(maxTableRows)) 784 785 fmt.Printf("# count rows in table: %d\n", count) 786 } 787 } 788 789 // testCompareBeforeAfterTables validates that stress_test_before and stress_test_after contents are non empty and completely identical 790 func testCompareBeforeAfterTables(t *testing.T, autoIncInsert bool) { 791 var countBefore int64 792 { 793 // Validate after table is populated 794 rs := onlineddl.VtgateExecQuery(t, &vtParams, selectCountFromTableBefore, "") 795 row := rs.Named().Row() 796 require.NotNil(t, row) 797 798 countBefore = row.AsInt64("c", 0) 799 require.NotZero(t, countBefore) 800 if !autoIncInsert { 801 require.Less(t, countBefore, int64(maxTableRows)) 802 } 803 fmt.Printf("# count rows in table (before): %d\n", countBefore) 804 } 805 var countAfter int64 806 { 807 // Validate after table is populated 808 rs := onlineddl.VtgateExecQuery(t, &vtParams, selectCountFromTableAfter, "") 809 row := rs.Named().Row() 810 require.NotNil(t, row) 811 812 countAfter = row.AsInt64("c", 0) 813 require.NotZero(t, countAfter) 814 if !autoIncInsert { 815 require.Less(t, countAfter, int64(maxTableRows)) 816 } 817 fmt.Printf("# count rows in table (after): %d\n", countAfter) 818 } 819 { 820 rs := onlineddl.VtgateExecQuery(t, &vtParams, selectMaxOpOrderFromTableBefore, "") 821 row := rs.Named().Row() 822 require.NotNil(t, row) 823 824 maxOpOrder := row.AsInt64("m", 0) 825 fmt.Printf("# max op_order in table (before): %d\n", maxOpOrder) 826 } 827 { 828 rs := onlineddl.VtgateExecQuery(t, &vtParams, selectMaxOpOrderFromTableAfter, "") 829 row := rs.Named().Row() 830 require.NotNil(t, row) 831 832 maxOpOrder := row.AsInt64("m", 0) 833 fmt.Printf("# max op_order in table (after): %d\n", maxOpOrder) 834 } 835 836 { 837 selectBeforeFile := onlineddl.CreateTempScript(t, selectBeforeTable) 838 defer os.Remove(selectBeforeFile) 839 beforeOutput := onlineddl.MysqlClientExecFile(t, mysqlParams(), os.TempDir(), "", selectBeforeFile) 840 beforeOutput = strings.TrimSpace(beforeOutput) 841 require.NotEmpty(t, beforeOutput) 842 assert.Equal(t, countBefore, int64(len(strings.Split(beforeOutput, "\n")))) 843 844 selectAfterFile := onlineddl.CreateTempScript(t, selectAfterTable) 845 defer os.Remove(selectAfterFile) 846 afterOutput := onlineddl.MysqlClientExecFile(t, mysqlParams(), os.TempDir(), "", selectAfterFile) 847 afterOutput = strings.TrimSpace(afterOutput) 848 require.NotEmpty(t, afterOutput) 849 assert.Equal(t, countAfter, int64(len(strings.Split(afterOutput, "\n")))) 850 851 require.Equal(t, beforeOutput, afterOutput, "results mismatch: (%s) and (%s)", selectBeforeTable, selectAfterTable) 852 } 853 }