github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/roachtest/tpcc.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package main 12 13 import ( 14 "context" 15 "fmt" 16 "io/ioutil" 17 "math" 18 "math/rand" 19 "os" 20 "path/filepath" 21 "runtime" 22 "strings" 23 "time" 24 25 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 26 "github.com/cockroachdb/cockroach/pkg/util/binfetcher" 27 "github.com/cockroachdb/cockroach/pkg/util/search" 28 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 29 "github.com/cockroachdb/cockroach/pkg/util/version" 30 "github.com/cockroachdb/cockroach/pkg/workload/histogram" 31 "github.com/cockroachdb/cockroach/pkg/workload/tpcc" 32 "github.com/cockroachdb/errors" 33 "github.com/cockroachdb/ttycolor" 34 "github.com/lib/pq" 35 ) 36 37 type tpccOptions struct { 38 Warehouses int 39 Extra string 40 Chaos func() Chaos // for late binding of stopper 41 During func(context.Context) error // for running a function during the test 42 Duration time.Duration 43 44 // The CockroachDB versions to deploy. The first one indicates the first node, 45 // etc. To use the main binary, specify "". When Versions is nil, it defaults 46 // to "" for all nodes. When it is specified, len(Versions) needs to match the 47 // number of CRDB nodes in the cluster. 48 // 49 // TODO(tbg): for better coverage at scale of the migration process, we 50 // should also be doing a rolling-restart into the new binary while the 51 // cluster is running, but that feels like jamming too much into the tpcc 52 // setup. 53 Versions []string 54 } 55 56 // tpccFixturesCmd generates the command string to load tpcc data for the 57 // specified warehouse count into a cluster using either `fixtures import` 58 // or `fixtures load` depending on the cloud. 59 func tpccFixturesCmd(t *test, cloud string, warehouses int, extraArgs string) string { 60 var command string 61 switch cloud { 62 case gce: 63 // TODO(nvanbenschoten): We could switch to import for both clouds. 64 // At the moment, import is still a little unstable and load is still 65 // marginally faster. 66 command = "./workload fixtures load" 67 fixtureWarehouses := -1 68 for _, w := range []int{1, 10, 100, 1000, 2000, 5000, 10000} { 69 if w >= warehouses { 70 fixtureWarehouses = w 71 break 72 } 73 } 74 if fixtureWarehouses == -1 { 75 t.Fatalf("could not find fixture big enough for %d warehouses", warehouses) 76 } 77 warehouses = fixtureWarehouses 78 case aws, azure: 79 // For fixtures import, use the version built into the cockroach binary 80 // so the tpcc workload-versions match on release branches. 81 command = "./cockroach workload fixtures import" 82 default: 83 t.Fatalf("unknown cloud: %q", cloud) 84 } 85 return fmt.Sprintf("%s tpcc --warehouses=%d %s {pgurl:1}", 86 command, warehouses, extraArgs) 87 } 88 89 func setupTPCC( 90 ctx context.Context, t *test, c *cluster, warehouses int, versions []string, 91 ) (crdbNodes, workloadNode nodeListOption) { 92 crdbNodes = c.Range(1, c.spec.NodeCount-1) 93 workloadNode = c.Node(c.spec.NodeCount) 94 if c.isLocal() { 95 warehouses = 1 96 } 97 98 if n := len(versions); n == 0 { 99 versions = make([]string, c.spec.NodeCount-1) 100 } else if n != c.spec.NodeCount-1 { 101 t.Fatalf("must specify Versions for all %d nodes: %v", c.spec.NodeCount-1, versions) 102 } 103 104 { 105 var regularNodes []option 106 for i, v := range versions { 107 if v == "" { 108 regularNodes = append(regularNodes, c.Node(i+1)) 109 } else { 110 // NB: binfetcher caches the downloaded files. 111 binary, err := binfetcher.Download(ctx, binfetcher.Options{ 112 Binary: "cockroach", 113 Version: v, 114 GOOS: ifLocal(runtime.GOOS, "linux"), 115 GOARCH: "amd64", 116 }) 117 if err != nil { 118 t.Fatalf("while fetching %s: %s", v, err) 119 } 120 c.Put(ctx, binary, "./cockroach", c.Node(i+1)) 121 } 122 } 123 c.Put(ctx, cockroach, "./cockroach", regularNodes...) 124 } 125 126 // Fixture import needs ./cockroach workload on workloadNode. 127 c.Put(ctx, cockroach, "./cockroach", workloadNode) 128 c.Put(ctx, workload, "./workload", workloadNode) 129 130 func() { 131 db := c.Conn(ctx, 1) 132 defer db.Close() 133 c.Start(ctx, t, crdbNodes, startArgsDontEncrypt) 134 waitForFullReplication(t, c.Conn(ctx, crdbNodes[0])) 135 t.Status("loading fixture") 136 c.Run(ctx, workloadNode, tpccFixturesCmd(t, cloud, warehouses, "")) 137 t.Status("") 138 }() 139 return crdbNodes, workloadNode 140 } 141 142 func runTPCC(ctx context.Context, t *test, c *cluster, opts tpccOptions) { 143 rampDuration := 5 * time.Minute 144 if c.isLocal() { 145 opts.Warehouses = 1 146 opts.Duration = time.Minute 147 rampDuration = 30 * time.Second 148 } 149 crdbNodes, workloadNode := setupTPCC(ctx, t, c, opts.Warehouses, opts.Versions) 150 t.Status("waiting") 151 m := newMonitor(ctx, c, crdbNodes) 152 m.Go(func(ctx context.Context) error { 153 t.WorkerStatus("running tpcc") 154 cmd := fmt.Sprintf( 155 "./workload run tpcc --warehouses=%d --histograms="+perfArtifactsDir+"/stats.json "+ 156 opts.Extra+" --ramp=%s --duration=%s {pgurl:1-%d}", 157 opts.Warehouses, rampDuration, opts.Duration, c.spec.NodeCount-1) 158 c.Run(ctx, workloadNode, cmd) 159 return nil 160 }) 161 if opts.Chaos != nil { 162 chaos := opts.Chaos() 163 m.Go(chaos.Runner(c, m)) 164 } 165 if opts.During != nil { 166 m.Go(opts.During) 167 } 168 m.Wait() 169 170 c.Run(ctx, workloadNode, fmt.Sprintf( 171 "./workload check tpcc --warehouses=%d {pgurl:1}", opts.Warehouses)) 172 } 173 174 // tpccSupportedWarehouses returns our claim for the maximum number of tpcc 175 // warehouses we support for a given hardware configuration. 176 // 177 // These should be added to periodically. Ideally when tpccbench finds major 178 // performance movement, but at the least for every major release. 179 var tpccSupportedWarehouses = []struct { 180 hardware string 181 v *version.Version 182 warehouses int 183 }{ 184 // We append "-0" to the version so that we capture all prereleases of the 185 // specified version. Otherwise, "v2.1.0" would compare greater than 186 // "v2.1.0-alpha.x". 187 {hardware: "gce-n4cpu16", v: version.MustParse(`v2.1.0-0`), warehouses: 1300}, 188 {hardware: "gce-n4cpu16", v: version.MustParse(`v19.1.0-0`), warehouses: 1250}, 189 {hardware: "aws-n4cpu16", v: version.MustParse(`v19.1.0-0`), warehouses: 2100}, 190 191 // TODO(tbg): this number is copied from gce-n4cpu16. The real number should be a 192 // little higher, find out what it is. 193 {hardware: "gce-n5cpu16", v: version.MustParse(`v19.1.0-0`), warehouses: 1300}, 194 // Ditto. 195 {hardware: "gce-n5cpu16", v: version.MustParse(`v2.1.0-0`), warehouses: 1300}, 196 } 197 198 func maxSupportedTPCCWarehouses(buildVersion version.Version, cloud string, nodes clusterSpec) int { 199 var v *version.Version 200 var warehouses int 201 hardware := fmt.Sprintf(`%s-%s`, cloud, &nodes) 202 for _, x := range tpccSupportedWarehouses { 203 if x.hardware != hardware { 204 continue 205 } 206 if buildVersion.AtLeast(x.v) && (v == nil || buildVersion.AtLeast(v)) { 207 v = x.v 208 warehouses = x.warehouses 209 } 210 } 211 if v == nil { 212 panic(fmt.Sprintf(`could not find max tpcc warehouses for %s`, hardware)) 213 } 214 return warehouses 215 } 216 217 func registerTPCC(r *testRegistry) { 218 headroomSpec := makeClusterSpec(4, cpu(16)) 219 r.Add(testSpec{ 220 // w=headroom runs tpcc for a semi-extended period with some amount of 221 // headroom, more closely mirroring a real production deployment than 222 // running with the max supported warehouses. 223 Name: "tpcc/headroom/" + headroomSpec.String(), 224 Owner: OwnerKV, 225 // TODO(dan): Backfill tpccSupportedWarehouses and remove this "v2.1.0" 226 // minimum on gce. 227 MinVersion: maxVersion("v2.1.0", maybeMinVersionForFixturesImport(cloud)), 228 Tags: []string{`default`, `release_qualification`}, 229 Cluster: headroomSpec, 230 Run: func(ctx context.Context, t *test, c *cluster) { 231 maxWarehouses := maxSupportedTPCCWarehouses(r.buildVersion, cloud, t.spec.Cluster) 232 headroomWarehouses := int(float64(maxWarehouses) * 0.7) 233 t.l.Printf("computed headroom warehouses of %d\n", headroomWarehouses) 234 runTPCC(ctx, t, c, tpccOptions{ 235 Warehouses: headroomWarehouses, 236 Duration: 120 * time.Minute, 237 }) 238 }, 239 }) 240 mixedHeadroomSpec := makeClusterSpec(5, cpu(16)) 241 242 // TODO(tbg): rewrite and extend this using the harness in versionupgrade.go. 243 r.Add(testSpec{ 244 // mixed-headroom is similar to w=headroom, but with an additional node 245 // and on a mixed version cluster. It simulates a real production 246 // deployment in the middle of the migration into a new cluster version. 247 Name: "tpcc/mixed-headroom/" + mixedHeadroomSpec.String(), 248 Owner: OwnerKV, 249 // TODO(dan): Backfill tpccSupportedWarehouses and remove this "v2.1.0" 250 // minimum on gce. 251 MinVersion: maxVersion("v2.1.0", maybeMinVersionForFixturesImport(cloud)), 252 // TODO(tbg): add release_qualification tag once we know the test isn't 253 // buggy. 254 Tags: []string{`default`}, 255 Cluster: mixedHeadroomSpec, 256 Run: func(ctx context.Context, t *test, c *cluster) { 257 maxWarehouses := maxSupportedTPCCWarehouses(r.buildVersion, cloud, t.spec.Cluster) 258 headroomWarehouses := int(float64(maxWarehouses) * 0.7) 259 oldV, err := PredecessorVersion(r.buildVersion) 260 if err != nil { 261 t.Fatal(err) 262 } 263 // Make a git tag out of the version. 264 oldV = "v" + oldV 265 t.l.Printf("computed headroom warehouses of %d; running mixed with %s\n", headroomWarehouses, oldV) 266 runTPCC(ctx, t, c, tpccOptions{ 267 Warehouses: headroomWarehouses, 268 Duration: 120 * time.Minute, 269 Versions: []string{oldV, "", oldV, ""}, 270 }) 271 // TODO(tbg): run another TPCC with the final binaries here and 272 // teach TPCC to re-use the dataset (seems easy enough) to at least 273 // somewhat test the full migration at scale? 274 }, 275 }) 276 r.Add(testSpec{ 277 Name: "tpcc-nowait/nodes=3/w=1", 278 Owner: OwnerKV, 279 MinVersion: maybeMinVersionForFixturesImport(cloud), 280 Cluster: makeClusterSpec(4, cpu(16)), 281 Run: func(ctx context.Context, t *test, c *cluster) { 282 runTPCC(ctx, t, c, tpccOptions{ 283 Warehouses: 1, 284 Duration: 10 * time.Minute, 285 Extra: "--wait=false", 286 }) 287 }, 288 }) 289 r.Add(testSpec{ 290 Name: "weekly/tpcc/headroom", 291 Owner: OwnerKV, 292 MinVersion: maybeMinVersionForFixturesImport(cloud), 293 Tags: []string{`weekly`}, 294 Cluster: makeClusterSpec(4, cpu(16)), 295 Timeout: time.Duration(6*24)*time.Hour + time.Duration(10)*time.Minute, 296 Run: func(ctx context.Context, t *test, c *cluster) { 297 warehouses := 1000 298 runTPCC(ctx, t, c, tpccOptions{ 299 Warehouses: warehouses, 300 Duration: 6 * 24 * time.Hour, 301 }) 302 }, 303 }) 304 305 r.Add(testSpec{ 306 Name: "tpcc/w=100/nodes=3/chaos=true", 307 Owner: OwnerKV, 308 Cluster: makeClusterSpec(4), 309 MinVersion: maybeMinVersionForFixturesImport(cloud), 310 Run: func(ctx context.Context, t *test, c *cluster) { 311 duration := 30 * time.Minute 312 runTPCC(ctx, t, c, tpccOptions{ 313 Warehouses: 100, 314 Duration: duration, 315 Extra: "--wait=false --tolerate-errors", 316 Chaos: func() Chaos { 317 return Chaos{ 318 Timer: Periodic{ 319 Period: 45 * time.Second, 320 DownTime: 10 * time.Second, 321 }, 322 Target: func() nodeListOption { return c.Node(1 + rand.Intn(c.spec.NodeCount-1)) }, 323 Stopper: time.After(duration), 324 DrainAndQuit: false, 325 } 326 }, 327 }) 328 }, 329 }) 330 331 // Run a few representative tpccbench specs in CI. 332 registerTPCCBenchSpec(r, tpccBenchSpec{ 333 Nodes: 3, 334 CPUs: 4, 335 336 LoadWarehouses: 1000, 337 EstimatedMax: gceOrAws(cloud, 400, 600), 338 }) 339 registerTPCCBenchSpec(r, tpccBenchSpec{ 340 Nodes: 3, 341 CPUs: 16, 342 343 LoadWarehouses: gceOrAws(cloud, 2000, 3000), 344 EstimatedMax: gceOrAws(cloud, 1600, 2500), 345 }) 346 registerTPCCBenchSpec(r, tpccBenchSpec{ 347 Nodes: 12, 348 CPUs: 16, 349 350 LoadWarehouses: gceOrAws(cloud, 8000, 10000), 351 EstimatedMax: gceOrAws(cloud, 7000, 8000), 352 353 Tags: []string{`weekly`}, 354 }) 355 registerTPCCBenchSpec(r, tpccBenchSpec{ 356 Nodes: 6, 357 CPUs: 16, 358 Distribution: multiZone, 359 360 LoadWarehouses: 5000, 361 EstimatedMax: 2500, 362 }) 363 registerTPCCBenchSpec(r, tpccBenchSpec{ 364 Nodes: 9, 365 CPUs: 4, 366 Distribution: multiRegion, 367 LoadConfig: multiLoadgen, 368 369 LoadWarehouses: 5000, 370 EstimatedMax: 3000, 371 372 MinVersion: "v19.1.0", 373 }) 374 registerTPCCBenchSpec(r, tpccBenchSpec{ 375 Nodes: 9, 376 CPUs: 4, 377 Chaos: true, 378 LoadConfig: singlePartitionedLoadgen, 379 380 LoadWarehouses: 2000, 381 EstimatedMax: 900, 382 383 MinVersion: "v19.1.0", 384 }) 385 } 386 387 func maxVersion(vers ...string) string { 388 var max *version.Version 389 for _, v := range vers { 390 v, err := version.Parse(v) 391 if err != nil { 392 continue 393 } 394 if max == nil || v.AtLeast(max) { 395 max = v 396 } 397 } 398 if max == nil { 399 return "" 400 } 401 return max.String() 402 } 403 404 func gceOrAws(cloud string, gce, aws int) int { 405 if cloud == "aws" { 406 return aws 407 } 408 return gce 409 } 410 411 func maybeMinVersionForFixturesImport(cloud string) string { 412 const minVersionForFixturesImport = "v19.1.0" 413 if cloud == "aws" { 414 return minVersionForFixturesImport 415 } 416 return "" 417 } 418 419 // tpccBenchDistribution represents a distribution of nodes in a tpccbench 420 // cluster. 421 type tpccBenchDistribution int 422 423 const ( 424 // All nodes are within the same zone. 425 singleZone tpccBenchDistribution = iota 426 // Nodes are distributed across 3 zones, all in the same region. 427 multiZone 428 // Nodes are distributed across 3 regions. 429 multiRegion 430 ) 431 432 func (d tpccBenchDistribution) zones() []string { 433 switch d { 434 case singleZone: 435 return []string{"us-central1-b"} 436 case multiZone: 437 return []string{"us-central1-a", "us-central1-b", "us-central1-c"} 438 case multiRegion: 439 return []string{"us-east1-b", "us-west1-b", "europe-west2-b"} 440 default: 441 panic("unexpected") 442 } 443 } 444 445 // tpccBenchLoadConfig represents configurations of load generators in a 446 // tpccbench spec. 447 type tpccBenchLoadConfig int 448 449 const ( 450 // A single load generator is run. 451 singleLoadgen tpccBenchLoadConfig = iota 452 // A single load generator is run with partitioning enabled. 453 singlePartitionedLoadgen 454 // A load generator is run in each zone. 455 multiLoadgen 456 ) 457 458 // numLoadNodes returns the number of load generator nodes that the load 459 // configuration requires for the given node distribution. 460 func (l tpccBenchLoadConfig) numLoadNodes(d tpccBenchDistribution) int { 461 switch l { 462 case singleLoadgen: 463 return 1 464 case singlePartitionedLoadgen: 465 return 1 466 case multiLoadgen: 467 return len(d.zones()) 468 default: 469 panic("unexpected") 470 } 471 } 472 473 type tpccBenchSpec struct { 474 Nodes int 475 CPUs int 476 Chaos bool 477 Distribution tpccBenchDistribution 478 LoadConfig tpccBenchLoadConfig 479 480 // The number of warehouses to load into the cluster before beginning 481 // benchmarking. Should be larger than EstimatedMax and should be a 482 // value that is unlikely to be achievable. 483 LoadWarehouses int 484 // An estimate of the maximum number of warehouses achievable in the 485 // cluster config. The closer this is to the actual max achievable 486 // warehouse count, the faster the benchmark will be in producing a 487 // result. This can be adjusted over time as performance characteristics 488 // change (i.e. CockroachDB gets faster!). 489 EstimatedMax int 490 491 // MinVersion to pass to testRegistry.Add. 492 MinVersion string 493 // Tags to pass to testRegistry.Add. 494 Tags []string 495 } 496 497 // partitions returns the number of partitions specified to the load generator. 498 func (s tpccBenchSpec) partitions() int { 499 switch s.LoadConfig { 500 case singleLoadgen: 501 return 0 502 case singlePartitionedLoadgen: 503 return s.Nodes / 3 504 case multiLoadgen: 505 return len(s.Distribution.zones()) 506 default: 507 panic("unexpected") 508 } 509 } 510 511 // startOpts returns any extra start options that the spec requires. 512 func (s tpccBenchSpec) startOpts() []option { 513 opts := []option{startArgsDontEncrypt} 514 if s.LoadConfig == singlePartitionedLoadgen { 515 opts = append(opts, racks(s.partitions())) 516 } 517 return opts 518 } 519 520 func registerTPCCBenchSpec(r *testRegistry, b tpccBenchSpec) { 521 nameParts := []string{ 522 "tpccbench", 523 fmt.Sprintf("nodes=%d", b.Nodes), 524 fmt.Sprintf("cpu=%d", b.CPUs), 525 } 526 if b.Chaos { 527 nameParts = append(nameParts, "chaos") 528 } 529 530 opts := []createOption{cpu(b.CPUs)} 531 switch b.Distribution { 532 case singleZone: 533 // No specifier. 534 case multiZone: 535 nameParts = append(nameParts, "multi-az") 536 opts = append(opts, geo(), zones(strings.Join(b.Distribution.zones(), ","))) 537 case multiRegion: 538 nameParts = append(nameParts, "multi-region") 539 opts = append(opts, geo(), zones(strings.Join(b.Distribution.zones(), ","))) 540 default: 541 panic("unexpected") 542 } 543 544 switch b.LoadConfig { 545 case singleLoadgen: 546 // No specifier. 547 case singlePartitionedLoadgen: 548 nameParts = append(nameParts, "partition") 549 case multiLoadgen: 550 // No specifier. 551 default: 552 panic("unexpected") 553 } 554 555 name := strings.Join(nameParts, "/") 556 557 numNodes := b.Nodes + b.LoadConfig.numLoadNodes(b.Distribution) 558 nodes := makeClusterSpec(numNodes, opts...) 559 560 minVersion := b.MinVersion 561 if minVersion == "" { 562 minVersion = maybeMinVersionForFixturesImport(cloud) 563 } 564 565 r.Add(testSpec{ 566 Name: name, 567 Owner: OwnerKV, 568 Cluster: nodes, 569 MinVersion: minVersion, 570 Tags: b.Tags, 571 Run: func(ctx context.Context, t *test, c *cluster) { 572 runTPCCBench(ctx, t, c, b) 573 }, 574 }) 575 } 576 577 // loadTPCCBench loads a TPCC dataset for the specific benchmark spec. The 578 // function is idempotent and first checks whether a compatible dataset exists, 579 // performing an expensive dataset restore only if it doesn't. 580 func loadTPCCBench( 581 ctx context.Context, t *test, c *cluster, b tpccBenchSpec, roachNodes, loadNode nodeListOption, 582 ) error { 583 db := c.Conn(ctx, 1) 584 defer db.Close() 585 586 // Check if the dataset already exists and is already large enough to 587 // accommodate this benchmarking. If so, we can skip the fixture RESTORE. 588 if _, err := db.ExecContext(ctx, `USE tpcc`); err == nil { 589 t.l.Printf("found existing tpcc database\n") 590 591 var curWarehouses int 592 if err := db.QueryRowContext(ctx, 593 `SELECT count(*) FROM tpcc.warehouse`, 594 ).Scan(&curWarehouses); err != nil { 595 return err 596 } 597 if curWarehouses >= b.LoadWarehouses { 598 // The cluster has enough warehouses. Nothing to do. 599 return nil 600 } 601 602 // If the dataset exists but is not large enough, wipe the cluster 603 // before restoring. 604 c.Wipe(ctx, roachNodes) 605 c.Start(ctx, t, append(b.startOpts(), roachNodes)...) 606 } else if pqErr := (*pq.Error)(nil); !(errors.As(err, &pqErr) && 607 string(pqErr.Code) == pgcode.InvalidCatalogName) { 608 return err 609 } 610 611 // Increase job leniency to prevent restarts due to node liveness. 612 if _, err := db.Exec(` 613 SET CLUSTER SETTING jobs.registry.leniency = '5m'; 614 `); err != nil { 615 t.Fatal(err) 616 } 617 618 var loadArgs string 619 var rebalanceWait time.Duration 620 switch b.LoadConfig { 621 case singleLoadgen: 622 loadArgs = `--scatter --checks=false` 623 rebalanceWait = time.Duration(b.LoadWarehouses/250) * time.Minute 624 case singlePartitionedLoadgen: 625 loadArgs = fmt.Sprintf(`--scatter --checks=false --partitions=%d`, b.partitions()) 626 rebalanceWait = time.Duration(b.LoadWarehouses/125) * time.Minute 627 case multiLoadgen: 628 loadArgs = fmt.Sprintf(`--scatter --checks=false --partitions=%d --zones="%s"`, 629 b.partitions(), strings.Join(b.Distribution.zones(), ",")) 630 rebalanceWait = time.Duration(b.LoadWarehouses/50) * time.Minute 631 default: 632 panic("unexpected") 633 } 634 635 // Load the corresponding fixture. 636 t.l.Printf("restoring tpcc fixture\n") 637 waitForFullReplication(t, db) 638 cmd := tpccFixturesCmd(t, cloud, b.LoadWarehouses, loadArgs) 639 if err := c.RunE(ctx, loadNode, cmd); err != nil { 640 return err 641 } 642 if rebalanceWait == 0 || len(roachNodes) <= 3 { 643 return nil 644 } 645 646 t.l.Printf("waiting %v for rebalancing\n", rebalanceWait) 647 _, err := db.ExecContext(ctx, `SET CLUSTER SETTING kv.snapshot_rebalance.max_rate='128MiB'`) 648 if err != nil { 649 return err 650 } 651 652 // Split and scatter the tables. Ramp up to the expected load in the desired 653 // distribution. This should allow for load-based rebalancing to help 654 // distribute load. Optionally pass some load configuration-specific flags. 655 cmd = fmt.Sprintf("./workload run tpcc --warehouses=%d --workers=%d --max-rate=%d "+ 656 "--wait=false --duration=%s --scatter --tolerate-errors {pgurl%s}", 657 b.LoadWarehouses, b.LoadWarehouses, b.LoadWarehouses/2, rebalanceWait, roachNodes) 658 if out, err := c.RunWithBuffer(ctx, c.l, loadNode, cmd); err != nil { 659 return errors.Wrapf(err, "failed with output %q", string(out)) 660 } 661 662 _, err = db.ExecContext(ctx, `SET CLUSTER SETTING kv.snapshot_rebalance.max_rate='2MiB'`) 663 return err 664 } 665 666 // tpccbench is a suite of benchmarking tools that run TPC-C against CockroachDB 667 // clusters in different configurations. The tools search for the maximum number 668 // of warehouses that a load generator can run TPC-C against while still 669 // maintaining a minimum acceptable throughput. This maximum warehouse value is 670 // directly comparable to other runs of the tool in the same cluster config, and 671 // expresses how well CockroachDB performance scales. 672 // 673 // In order to run a benchmark spec, the tool must first load a TPC-C dataset 674 // large enough to accommodate it. This can take a while, so it is recommended 675 // to use a combination of `--cluster=<cluster>` and `--wipe=false` flags to 676 // limit the loading phase to the first run of the tool. Subsequent runs will be 677 // able to avoid the dataset restore as long as they are not wiped. This allows 678 // for quick iteration on experimental changes. 679 // 680 // It can also be useful to omit the `--cluster` flag during the first run of 681 // the tool to allow roachtest to create the correct set of VMs required by the 682 // test. The `--wipe` flag will prevent this cluster from being destroyed, so it 683 // can then be used during future runs. 684 func runTPCCBench(ctx context.Context, t *test, c *cluster, b tpccBenchSpec) { 685 // Determine the nodes in each load group. A load group consists of a set of 686 // Cockroach nodes and a single load generator. 687 numLoadGroups := b.LoadConfig.numLoadNodes(b.Distribution) 688 numZones := len(b.Distribution.zones()) 689 loadGroups := makeLoadGroups(c, numZones, b.Nodes, numLoadGroups) 690 roachNodes := loadGroups.roachNodes() 691 loadNodes := loadGroups.loadNodes() 692 c.Put(ctx, cockroach, "./cockroach", roachNodes) 693 // Fixture import needs ./cockroach workload on loadNodes[0], 694 // and if we use haproxy (see below) we need it on the others 695 // as well. 696 c.Put(ctx, cockroach, "./cockroach", loadNodes) 697 c.Put(ctx, workload, "./workload", loadNodes) 698 c.Start(ctx, t, append(b.startOpts(), roachNodes)...) 699 700 useHAProxy := b.Chaos 701 const restartWait = 15 * time.Second 702 { 703 // Wait after restarting nodes before applying load. This lets 704 // things settle down to avoid unexpected cluster states. 705 time.Sleep(restartWait) 706 if useHAProxy { 707 if len(loadNodes) > 1 { 708 t.Fatal("distributed chaos benchmarking not supported") 709 } 710 t.Status("installing haproxy") 711 if err := c.Install(ctx, t.l, loadNodes, "haproxy"); err != nil { 712 t.Fatal(err) 713 } 714 c.Run(ctx, loadNodes, "./cockroach gen haproxy --insecure --url {pgurl:1}") 715 // Increase the maximum connection limit to ensure that no TPC-C 716 // load gen workers get stuck during connection initialization. 717 // 10k warehouses requires at least 20,000 connections, so add a 718 // bit of breathing room and check the warehouse count. 719 c.Run(ctx, loadNodes, "sed -i 's/maxconn [0-9]\\+/maxconn 21000/' haproxy.cfg") 720 if b.LoadWarehouses > 1e4 { 721 t.Fatal("HAProxy config supports up to 10k warehouses") 722 } 723 c.Run(ctx, loadNodes, "haproxy -f haproxy.cfg -D") 724 } 725 726 m := newMonitor(ctx, c, roachNodes) 727 m.Go(func(ctx context.Context) error { 728 t.Status("setting up dataset") 729 return loadTPCCBench(ctx, t, c, b, roachNodes, c.Node(loadNodes[0])) 730 }) 731 m.Wait() 732 } 733 734 // Search between 1 and b.LoadWarehouses for the largest number of 735 // warehouses that can be operated on while sustaining a throughput 736 // threshold, set to a fraction of max tpmC. 737 precision := int(math.Max(1.0, float64(b.LoadWarehouses/200))) 738 initStepSize := precision 739 740 // Create a temp directory to store the local copy of results from the 741 // workloads. 742 resultsDir, err := ioutil.TempDir("", "roachtest-tpcc") 743 if err != nil { 744 t.Fatal(errors.Wrap(err, "failed to create temp dir")) 745 } 746 defer func() { _ = os.RemoveAll(resultsDir) }() 747 s := search.NewLineSearcher(1, b.LoadWarehouses, b.EstimatedMax, initStepSize, precision) 748 if res, err := s.Search(func(warehouses int) (bool, error) { 749 m := newMonitor(ctx, c, roachNodes) 750 // Restart the cluster before each iteration to help eliminate 751 // inter-trial interactions. 752 m.ExpectDeaths(int32(len(roachNodes))) 753 c.Stop(ctx, roachNodes) 754 c.Start(ctx, t, append(b.startOpts(), roachNodes)...) 755 time.Sleep(restartWait) 756 757 // Set up the load generation configuration. 758 rampDur := 5 * time.Minute 759 loadDur := 10 * time.Minute 760 loadDone := make(chan time.Time, numLoadGroups) 761 762 // If we're running chaos in this configuration, modify this config. 763 if b.Chaos { 764 // Increase the load generation duration. 765 loadDur = 10 * time.Minute 766 767 // Kill one node at a time. 768 ch := Chaos{ 769 Timer: Periodic{Period: 90 * time.Second, DownTime: 5 * time.Second}, 770 Target: roachNodes.randNode, 771 Stopper: loadDone, 772 } 773 m.Go(ch.Runner(c, m)) 774 } 775 if b.Distribution == multiRegion { 776 rampDur = 3 * time.Minute 777 loadDur = 15 * time.Minute 778 } 779 780 // If we're running multiple load generators, run them in parallel and then 781 // aggregate resultChan. In order to process the results we need to copy 782 // over the histograms. Create a temp dir which will contain the fetched 783 // data. 784 resultChan := make(chan *tpcc.Result, numLoadGroups) 785 for groupIdx, group := range loadGroups { 786 // Copy for goroutine 787 groupIdx := groupIdx 788 group := group 789 m.Go(func(ctx context.Context) error { 790 sqlGateways := group.roachNodes 791 if useHAProxy { 792 sqlGateways = group.loadNodes 793 } 794 795 extraFlags := "" 796 activeWarehouses := warehouses 797 switch b.LoadConfig { 798 case singleLoadgen: 799 // Nothing. 800 case singlePartitionedLoadgen: 801 extraFlags = fmt.Sprintf(` --partitions=%d`, b.partitions()) 802 case multiLoadgen: 803 extraFlags = fmt.Sprintf(` --partitions=%d --partition-affinity=%d`, 804 b.partitions(), groupIdx) 805 activeWarehouses = warehouses / numLoadGroups 806 default: 807 panic("unexpected") 808 } 809 810 t.Status(fmt.Sprintf("running benchmark, warehouses=%d", warehouses)) 811 histogramsPath := fmt.Sprintf("%s/warehouses=%d/stats.json", perfArtifactsDir, activeWarehouses) 812 cmd := fmt.Sprintf("./workload run tpcc --warehouses=%d --active-warehouses=%d "+ 813 "--tolerate-errors --scatter --ramp=%s --duration=%s%s {pgurl%s} "+ 814 "--histograms=%s", 815 b.LoadWarehouses, activeWarehouses, rampDur, 816 loadDur, extraFlags, sqlGateways, histogramsPath) 817 err := c.RunE(ctx, group.loadNodes, cmd) 818 loadDone <- timeutil.Now() 819 if err != nil { 820 return errors.Wrapf(err, "error running tpcc load generator") 821 } 822 roachtestHistogramsPath := filepath.Join(resultsDir, fmt.Sprintf("%d.%d-stats.json", warehouses, groupIdx)) 823 if err := c.Get( 824 ctx, t.l, histogramsPath, roachtestHistogramsPath, group.loadNodes, 825 ); err != nil { 826 t.Fatal(err) 827 } 828 snapshots, err := histogram.DecodeSnapshots(roachtestHistogramsPath) 829 if err != nil { 830 return errors.Wrapf(err, "failed to decode histogram snapshots") 831 } 832 result := tpcc.NewResultWithSnapshots(activeWarehouses, 0, snapshots) 833 resultChan <- result 834 return nil 835 }) 836 } 837 if err = m.WaitE(); err != nil { 838 return false, err 839 } 840 close(resultChan) 841 var results []*tpcc.Result 842 for partial := range resultChan { 843 results = append(results, partial) 844 } 845 res := tpcc.MergeResults(results...) 846 failErr := res.FailureError() 847 // Print the result. 848 if failErr == nil { 849 ttycolor.Stdout(ttycolor.Green) 850 t.l.Printf("--- PASS: tpcc %d resulted in %.1f tpmC (%.1f%% of max tpmC)\n\n", 851 warehouses, res.TpmC(), res.Efficiency()) 852 } else { 853 ttycolor.Stdout(ttycolor.Red) 854 t.l.Printf("--- FAIL: tpcc %d resulted in %.1f tpmC and failed due to %v", 855 warehouses, res.TpmC(), failErr) 856 } 857 ttycolor.Stdout(ttycolor.Reset) 858 return failErr == nil, nil 859 }); err != nil { 860 t.Fatal(err) 861 } else { 862 ttycolor.Stdout(ttycolor.Green) 863 t.l.Printf("------\nMAX WAREHOUSES = %d\n------\n\n", res) 864 ttycolor.Stdout(ttycolor.Reset) 865 } 866 } 867 868 func registerTPCCBench(r *testRegistry) { 869 specs := []tpccBenchSpec{ 870 { 871 Nodes: 3, 872 CPUs: 4, 873 874 LoadWarehouses: 1000, 875 EstimatedMax: 325, 876 }, 877 { 878 Nodes: 3, 879 CPUs: 16, 880 881 LoadWarehouses: 2000, 882 EstimatedMax: 1300, 883 }, 884 // objective 1, key result 1. 885 { 886 Nodes: 30, 887 CPUs: 16, 888 889 LoadWarehouses: 10000, 890 EstimatedMax: 5300, 891 }, 892 // objective 1, key result 2. 893 { 894 Nodes: 18, 895 CPUs: 16, 896 LoadConfig: singlePartitionedLoadgen, 897 898 LoadWarehouses: 10000, 899 EstimatedMax: 8000, 900 }, 901 // objective 2, key result 1. 902 { 903 Nodes: 7, 904 CPUs: 16, 905 Chaos: true, 906 907 LoadWarehouses: 5000, 908 EstimatedMax: 2000, 909 }, 910 // objective 3, key result 1. 911 { 912 Nodes: 3, 913 CPUs: 16, 914 Distribution: multiZone, 915 916 LoadWarehouses: 2000, 917 EstimatedMax: 1000, 918 }, 919 // objective 3, key result 2. 920 { 921 Nodes: 9, 922 CPUs: 16, 923 Distribution: multiRegion, 924 LoadConfig: multiLoadgen, 925 926 LoadWarehouses: 12000, 927 EstimatedMax: 8000, 928 929 MinVersion: "v19.1.0", 930 }, 931 // objective 4, key result 2. 932 { 933 Nodes: 64, 934 CPUs: 16, 935 936 LoadWarehouses: 50000, 937 EstimatedMax: 40000, 938 }, 939 940 // See https://github.com/cockroachdb/cockroach/issues/31409 for the next three specs. 941 { 942 Nodes: 6, 943 CPUs: 16, 944 945 LoadWarehouses: 5000, 946 EstimatedMax: 3000, 947 LoadConfig: singlePartitionedLoadgen, 948 }, 949 { 950 Nodes: 12, 951 CPUs: 16, 952 953 LoadWarehouses: 10000, 954 EstimatedMax: 6000, 955 LoadConfig: singlePartitionedLoadgen, 956 }, 957 { 958 Nodes: 24, 959 CPUs: 16, 960 961 LoadWarehouses: 20000, 962 EstimatedMax: 12000, 963 LoadConfig: singlePartitionedLoadgen, 964 }, 965 966 // Requested by @awoods87. 967 { 968 Nodes: 11, 969 CPUs: 32, 970 971 LoadWarehouses: 10000, 972 EstimatedMax: 8000, 973 }, 974 } 975 976 for _, b := range specs { 977 registerTPCCBenchSpec(r, b) 978 } 979 }