github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/interleavedpartitioned/interleavedpartitioned.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 interleavedpartitioned 12 13 import ( 14 "context" 15 gosql "database/sql" 16 "fmt" 17 "math/rand" 18 "strings" 19 "time" 20 21 "github.com/cockroachdb/cockroach-go/crdb" 22 "github.com/cockroachdb/cockroach/pkg/col/coldata" 23 "github.com/cockroachdb/cockroach/pkg/sql/types" 24 "github.com/cockroachdb/cockroach/pkg/util/bufalloc" 25 "github.com/cockroachdb/cockroach/pkg/util/log" 26 "github.com/cockroachdb/cockroach/pkg/util/randutil" 27 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 28 "github.com/cockroachdb/cockroach/pkg/workload" 29 "github.com/cockroachdb/cockroach/pkg/workload/histogram" 30 "github.com/cockroachdb/errors" 31 "github.com/spf13/pflag" 32 ) 33 34 const ( 35 zoneLocationsStmt = ` 36 UPSERT INTO system.locations VALUES 37 ('zone', $1, 33.0641249, -80.0433347), 38 ('zone', $2, 45.6319052, -121.2010282), 39 ('zone', $3, 41.238785 , -95.854239) 40 ` 41 42 nodeIDQuery = ` 43 SELECT DISTINCT node_id 44 FROM crdb_internal.node_build_info 45 ` 46 47 // Table Schemas 48 // TODO(bram): Deletes are very slow due to contention. We could create a 49 // partitioned index on session, but to do so would require creating a 50 // computed column (for east/west) and creating the index on that column and 51 // created. 52 sessionSchema = ` 53 ( 54 session_id STRING(100) PRIMARY KEY, 55 affiliate STRING(100) NOT NULL, 56 channel STRING(50) NOT NULL, 57 language STRING(20) NOT NULL, 58 created TIMESTAMP NOT NULL, 59 updated TIMESTAMP NOT NULL, 60 status STRING(20) NOT NULL, 61 platform STRING(50) NOT NULL, 62 query_id STRING(100) NOT NULL 63 ) PARTITION BY RANGE (session_id) ( 64 PARTITION east VALUES FROM ('E-') TO ('F-'), 65 PARTITION west VALUES FROM ('W-') TO ('X-'), 66 PARTITION central VALUES FROM ('C-') TO ('D-') 67 )` 68 genericChildSchema = ` 69 ( 70 session_id STRING(100) NOT NULL, 71 id STRING(50) NOT NULL, 72 value STRING(50) NOT NULL, 73 created TIMESTAMP NOT NULL, 74 updated TIMESTAMP NOT NULL, 75 PRIMARY KEY (session_id, id), 76 FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE 77 ) INTERLEAVE IN PARENT sessions(session_id)` 78 deviceSchema = ` 79 ( 80 session_id STRING(100) NOT NULL, 81 id STRING(100) NOT NULL, 82 device_id STRING(50), 83 name STRING(50), 84 make STRING(50), 85 macaddress STRING(50), 86 model STRING(50), 87 serial_number STRING(50), 88 created TIMESTAMP NOT NULL, 89 updated TIMESTAMP NOT NULL, 90 PRIMARY KEY (session_id, id), 91 FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE 92 ) INTERLEAVE IN PARENT sessions(session_id) 93 ` 94 querySchema = ` 95 ( 96 session_id STRING(100) NOT NULL, 97 id STRING(50) NOT NULL, 98 created TIMESTAMP NOT NULL, 99 updated TIMESTAMP NOT NULL, 100 PRIMARY KEY (session_id, id), 101 FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE 102 ) INTERLEAVE IN PARENT sessions(session_id) 103 ` 104 105 // Insert Queries 106 insertQuery = `INSERT INTO sessions( 107 session_id, 108 affiliate, 109 channel, 110 language, 111 created, 112 updated, 113 status, 114 platform, 115 query_id 116 ) VALUES ($1, $2, $3, $4, now(), now(), $5, $6, $7) 117 ` 118 insertQueryCustomers = ` 119 INSERT INTO customers(session_id, id, value, created, updated) 120 VALUES ($1, $2, $3, now(), now()) 121 ` 122 insertQueryVariants = ` 123 INSERT INTO variants(session_id, id, value, created, updated) 124 VALUES ($1, $2, $3, now(), now()) 125 ` 126 insertQueryParameters = ` 127 INSERT INTO parameters(session_id, id, value, created, updated) 128 VALUES ($1, $2, $3, now(), now()) 129 ` 130 insertQueryDevices = ` 131 INSERT INTO devices( 132 session_id, 133 id, 134 device_id, 135 name, 136 make, 137 macaddress, 138 model, 139 serial_number, 140 created, 141 updated 142 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, now(), now()) 143 ` 144 insertQueryQuery = ` 145 INSERT INTO queries(session_id, id, created, updated) 146 VALUES ($1, $2, now(), now()) 147 ` 148 149 // Delete queries 150 deleteWestQuery = ` 151 DELETE FROM sessions 152 WHERE session_id LIKE 'W-%' 153 AND created < now() - interval '1' minute 154 LIMIT $1 155 ` 156 deleteEastQuery = ` 157 DELETE FROM sessions 158 WHERE session_id LIKE 'E-%' 159 AND created < now() - interval '1' minute 160 LIMIT $1 161 ` 162 163 // Retrieve queries 164 retrieveQuery1 = ` 165 SELECT session_id, affiliate, channel, created, language, status, platform, query_id, updated 166 FROM sessions 167 WHERE session_id = $1 168 ` 169 retrieveQuery2 = ` 170 SELECT 171 device.id, 172 device.session_id, 173 device.created, 174 device.name, 175 device.model, 176 device.macaddress, 177 device.serial_number, 178 device.make, 179 device.updated, 180 session.session_id, 181 session.affiliate, 182 session.created, 183 session.channel, 184 session.language, 185 session.status, 186 session.platform, 187 session.query_id, 188 session.updated 189 FROM sessions as session 190 LEFT OUTER JOIN devices AS device 191 ON session.session_id = device.session_id 192 WHERE session.session_id = $1 193 ` 194 retrieveQuery3 = ` 195 UPDATE sessions 196 SET updated = now() 197 WHERE session_id = $1 198 ` 199 retrieveQuery4 = ` 200 SELECT session_id, id, id, session_id, created, value, updated 201 FROM customers 202 WHERE session_id = $1 203 ` 204 retrieveQuery5 = ` 205 SELECT session_id, id, id, session_id, created, value, updated 206 FROM parameters 207 WHERE session_id = $1 208 ` 209 retrieveQuery6 = ` 210 SELECT session_id, id, id, session_id, created, value, updated 211 FROM variants 212 WHERE session_id = $1 213 ` 214 retrieveQuery7 = ` 215 SELECT d.session_id AS device_session_id, 216 d.created AS device_created, 217 d.device_id AS device_id, 218 d.make AS make, 219 d.model AS model, 220 d.name AS name, 221 d.serial_number AS name, 222 d.updated AS device_updated, 223 s.session_id AS session_id, 224 s.affiliate AS affiliate, 225 s.channel AS channel, 226 s.created AS session_created, 227 s.language AS language, 228 s.platform AS platform, 229 s.query_id AS query_id, 230 s.status AS status, 231 s.updated AS session_updated 232 FROM devices AS d 233 LEFT JOIN sessions AS s 234 ON d.session_id = s.session_id 235 WHERE d.session_id = $1 236 ` 237 238 // Update Queries 239 updateQuery1 = ` 240 UPDATE sessions 241 SET query_id = $1, updated = now() 242 WHERE session_id = $2 243 ` 244 updateQuery2 = ` 245 UPDATE sessions 246 SET status = $1, updated = now() 247 WHERE session_id = $2 248 ` 249 250 // Fetch random session ID queries. 251 findSessionIDQuery1 = ` 252 SELECT session_id 253 FROM sessions 254 WHERE session_id > $1 255 LIMIT 1 256 ` 257 findSessionIDQuery2 = ` 258 SELECT session_id 259 FROM sessions 260 WHERE session_id > $1 261 LIMIT 1 262 ` 263 ) 264 265 var ( 266 retrieveQueries = []string{ 267 retrieveQuery1, 268 retrieveQuery2, 269 retrieveQuery3, 270 retrieveQuery4, 271 retrieveQuery5, 272 retrieveQuery6, 273 retrieveQuery7, 274 } 275 276 // All retrieve queries are run in an update operation before the update 277 // queries. 278 updateQueries = []string{ 279 updateQuery1, 280 updateQuery2, 281 } 282 ) 283 284 func init() { 285 workload.Register(interleavedPartitionedMeta) 286 } 287 288 type interleavedPartitioned struct { 289 flags workload.Flags 290 connFlags *workload.ConnFlags 291 292 locality string 293 294 // data distribution flags 295 customersPerSession int 296 devicesPerSession int 297 variantsPerSession int 298 parametersPerSession int 299 queriesPerSession int 300 301 // flags for initial db loading 302 initEastPercent int 303 initSessions int 304 305 // operation flags 306 insertPercent int 307 insertLocalPercent int 308 retrievePercent int 309 retrieveLocalPercent int 310 updatePercent int 311 updateLocalPercent int 312 313 // info for delete jobs 314 deletes bool // set based on zone, not a flag 315 rowsPerDelete int 316 317 // zones 318 eastZoneName string 319 westZoneName string 320 centralZoneName string 321 322 // prepared statements 323 retrieveStatements []*gosql.Stmt 324 updateStatements []*gosql.Stmt 325 insertStatement *gosql.Stmt 326 insertCustomerStatement *gosql.Stmt 327 insertDeviceStatement *gosql.Stmt 328 insertVariantStatement *gosql.Stmt 329 insertParameterStatement *gosql.Stmt 330 insertQueryStatement *gosql.Stmt 331 deleteEastStatement *gosql.Stmt 332 deleteWestStatement *gosql.Stmt 333 findSessionIDStatement1 *gosql.Stmt 334 findSessionIDStatement2 *gosql.Stmt 335 } 336 337 var interleavedPartitionedMeta = workload.Meta{ 338 Name: `interleavedpartitioned`, 339 Description: `Tests the performance of tables that are both interleaved and partitioned`, 340 Version: `1.0.0`, 341 New: func() workload.Generator { 342 g := &interleavedPartitioned{} 343 g.flags.FlagSet = pflag.NewFlagSet(`interleavedpartitioned`, pflag.ContinueOnError) 344 g.flags.Meta = map[string]workload.FlagMeta{ 345 `batch`: {RuntimeOnly: true}, 346 } 347 g.flags.IntVar(&g.initSessions, `init-sessions`, 1000, `Number of sessions (rows in the parent table) to create during initialization`) 348 g.flags.IntVar(&g.customersPerSession, `customers-per-session`, 2, `Number of customers associated with each session`) 349 g.flags.IntVar(&g.devicesPerSession, `devices-per-session`, 2, `Number of devices associated with each session`) 350 g.flags.IntVar(&g.variantsPerSession, `variants-per-session`, 5, `Number of variants associated with each session`) 351 g.flags.IntVar(&g.parametersPerSession, `parameters-per-session`, 1, `Number of parameters associated with each session`) 352 g.flags.IntVar(&g.queriesPerSession, `queries-per-session`, 1, `Number of queries associated with each session`) 353 g.flags.IntVar(&g.initEastPercent, `init-east-percent`, 50, `Percentage (0-100) of sessions that are in us-east used when initializing rows only`) 354 g.flags.IntVar(&g.insertPercent, `insert-percent`, 70, `Percentage (0-100) of operations that are inserts`) 355 g.flags.IntVar(&g.insertLocalPercent, `insert-local-percent`, 100, `Percentage of insert operations that are local`) 356 g.flags.IntVar(&g.retrievePercent, `retrieve-percent`, 20, `Percentage (0-100) of operations that are retrieval queries`) 357 g.flags.IntVar(&g.retrieveLocalPercent, `retrieve-local-percent`, 100, `Percentage of retrieve operations that are local`) 358 g.flags.IntVar(&g.updatePercent, `update-percent`, 10, `Percentage (0-100) of operations that are update queries`) 359 g.flags.IntVar(&g.updateLocalPercent, `update-local-percent`, 100, `Percentage of update operations that are local`) 360 g.flags.IntVar(&g.rowsPerDelete, `rows-per-delete`, 1, `Number of rows per delete operation`) 361 g.flags.StringVar(&g.eastZoneName, `east-zone-name`, `us-east1-b`, `Name of the zone to be used as east`) 362 g.flags.StringVar(&g.westZoneName, `west-zone-name`, `us-west1-b`, `Name of the zone to be used as west`) 363 g.flags.StringVar(&g.centralZoneName, `central-zone-name`, `us-central1-a`, `Name of the zone to be used as central`) 364 g.flags.StringVar(&g.locality, `locality`, ``, `Which locality is the workload running in? (east,west,central)`) 365 g.connFlags = workload.NewConnFlags(&g.flags) 366 return g 367 }, 368 } 369 370 // Meta implements the Generator interface. 371 func (w *interleavedPartitioned) Meta() workload.Meta { return interleavedPartitionedMeta } 372 373 // Flags implements the Flagser interface. 374 func (w *interleavedPartitioned) Flags() workload.Flags { return w.flags } 375 376 // Tables implements the Generator interface. 377 func (w *interleavedPartitioned) Tables() []workload.Table { 378 sessionsTable := workload.Table{ 379 Name: `sessions`, 380 Schema: sessionSchema, 381 InitialRows: workload.Tuples( 382 w.initSessions, 383 w.sessionsInitialRow, 384 ), 385 } 386 customerTable := workload.Table{ 387 Name: `customers`, 388 Schema: genericChildSchema, 389 InitialRows: workload.BatchedTuples{ 390 NumBatches: w.initSessions, 391 FillBatch: w.childInitialRowBatchFunc(2, w.customersPerSession), 392 }, 393 } 394 devicesTable := workload.Table{ 395 Name: `devices`, 396 Schema: deviceSchema, 397 InitialRows: workload.BatchedTuples{ 398 NumBatches: w.initSessions, 399 FillBatch: w.deviceInitialRowBatch, 400 }, 401 } 402 variantsTable := workload.Table{ 403 Name: `variants`, 404 Schema: genericChildSchema, 405 InitialRows: workload.BatchedTuples{ 406 NumBatches: w.initSessions, 407 FillBatch: w.childInitialRowBatchFunc(3, w.variantsPerSession), 408 }, 409 } 410 parametersTable := workload.Table{ 411 Name: `parameters`, 412 Schema: genericChildSchema, 413 InitialRows: workload.BatchedTuples{ 414 NumBatches: w.initSessions, 415 FillBatch: w.childInitialRowBatchFunc(4, w.parametersPerSession), 416 }, 417 } 418 queriesTable := workload.Table{ 419 Name: `queries`, 420 Schema: querySchema, 421 InitialRows: workload.BatchedTuples{ 422 NumBatches: w.initSessions, 423 FillBatch: w.queryInitialRowBatch, 424 }, 425 } 426 return []workload.Table{ 427 sessionsTable, customerTable, devicesTable, variantsTable, parametersTable, queriesTable, 428 } 429 } 430 431 // Ops implements the Opser interface. 432 func (w *interleavedPartitioned) Ops( 433 urls []string, reg *histogram.Registry, 434 ) (workload.QueryLoad, error) { 435 sqlDatabase, err := workload.SanitizeUrls(w, ``, urls) 436 if err != nil { 437 return workload.QueryLoad{}, err 438 } 439 440 db, err := gosql.Open(`cockroach`, strings.Join(urls, ` `)) 441 if err != nil { 442 return workload.QueryLoad{}, err 443 } 444 445 db.SetMaxOpenConns(w.connFlags.Concurrency + 1) 446 db.SetMaxIdleConns(w.connFlags.Concurrency + 1) 447 448 ql := workload.QueryLoad{ 449 SQLDatabase: sqlDatabase, 450 } 451 452 workerCount := w.connFlags.Concurrency 453 if w.deletes { 454 // Only run a single worker function if performing deletes. 455 workerCount = 1 456 } 457 458 // Prepare the queries, stmts are safe for concurrent use. 459 w.retrieveStatements = make([]*gosql.Stmt, len(retrieveQueries)) 460 for i, query := range retrieveQueries { 461 var err error 462 w.retrieveStatements[i], err = db.Prepare(query) 463 if err != nil { 464 return workload.QueryLoad{}, err 465 } 466 } 467 w.updateStatements = make([]*gosql.Stmt, len(updateQueries)) 468 for i, query := range updateQueries { 469 var err error 470 w.updateStatements[i], err = db.Prepare(query) 471 if err != nil { 472 return workload.QueryLoad{}, err 473 } 474 } 475 w.insertStatement, err = db.Prepare(insertQuery) 476 if err != nil { 477 return workload.QueryLoad{}, err 478 } 479 w.insertCustomerStatement, err = db.Prepare(insertQueryCustomers) 480 if err != nil { 481 return workload.QueryLoad{}, err 482 } 483 w.insertDeviceStatement, err = db.Prepare(insertQueryDevices) 484 if err != nil { 485 return workload.QueryLoad{}, err 486 } 487 w.insertVariantStatement, err = db.Prepare(insertQueryVariants) 488 if err != nil { 489 return workload.QueryLoad{}, err 490 } 491 w.insertParameterStatement, err = db.Prepare(insertQueryParameters) 492 if err != nil { 493 return workload.QueryLoad{}, err 494 } 495 w.insertQueryStatement, err = db.Prepare(insertQueryQuery) 496 if err != nil { 497 return workload.QueryLoad{}, err 498 } 499 w.deleteEastStatement, err = db.Prepare(deleteEastQuery) 500 if err != nil { 501 return workload.QueryLoad{}, err 502 } 503 w.deleteWestStatement, err = db.Prepare(deleteWestQuery) 504 if err != nil { 505 return workload.QueryLoad{}, err 506 } 507 w.findSessionIDStatement1, err = db.Prepare(findSessionIDQuery1) 508 if err != nil { 509 return workload.QueryLoad{}, err 510 } 511 w.findSessionIDStatement2, err = db.Prepare(findSessionIDQuery2) 512 if err != nil { 513 return workload.QueryLoad{}, err 514 } 515 516 for i := 0; i < workerCount; i++ { 517 workerID := i 518 ql.WorkerFns = append(ql.WorkerFns, func(ctx context.Context) error { 519 rng := rand.New(rand.NewSource(timeutil.Now().Add(time.Hour * time.Duration(i)).UnixNano())) 520 521 hists := reg.GetHandle() 522 if w.deletes { 523 return w.deleteFunc(ctx, hists, rng) 524 } 525 526 operation := rng.Intn(100) 527 switch { 528 case operation < w.insertPercent: // insert 529 return w.insertFunc(ctx, db, hists, rng, workerID) 530 case operation < w.insertPercent+w.retrievePercent: // retrieve 531 return w.retrieveFunc(ctx, hists, rng) 532 case operation < w.insertPercent+w.retrievePercent+w.updatePercent: // update 533 return w.updateFunc(ctx, hists, rng) 534 default: // No operation. 535 return nil 536 } 537 }) 538 } 539 540 return ql, nil 541 } 542 543 func (w *interleavedPartitioned) deleteFunc( 544 ctx context.Context, hists *histogram.Histograms, rng *rand.Rand, 545 ) error { 546 start := timeutil.Now() 547 var statement *gosql.Stmt 548 // Prepare the statements. 549 if rng.Intn(2) > 0 { 550 statement = w.deleteEastStatement 551 } else { 552 statement = w.deleteWestStatement 553 } 554 // Execute the statements. 555 if _, err := statement.ExecContext(ctx, w.rowsPerDelete); err != nil { 556 return err 557 } 558 // Record Stats. 559 elapsed := timeutil.Since(start) 560 hists.Get(`delete`).Record(elapsed) 561 return nil 562 } 563 564 func (w *interleavedPartitioned) insertFunc( 565 ctx context.Context, db *gosql.DB, hists *histogram.Histograms, rng *rand.Rand, workerID int, 566 ) error { 567 start := timeutil.Now() 568 // Execute the transaction. 569 if err := crdb.ExecuteTx( 570 context.Background(), 571 db, 572 nil, /* txopts */ 573 func(tx *gosql.Tx) error { 574 // Get the node id. 575 var nodeID int 576 if err := tx.QueryRow(nodeIDQuery).Scan(&nodeID); err != nil { 577 return err 578 } 579 580 sessionID := randomSessionIDForInsert(rng, w.locality, w.insertLocalPercent, nodeID, workerID) 581 args := []interface{}{ 582 sessionID, // session_id 583 randString(rng, 100), // affiliate 584 randString(rng, 50), // channel 585 randString(rng, 20), // language 586 randString(rng, 20), // status 587 randString(rng, 50), // platform 588 randString(rng, 100), // query_id 589 } 590 if _, err := tx.StmtContext(ctx, w.insertStatement).ExecContext(ctx, args...); err != nil { 591 return err 592 } 593 for i := 0; i < w.customersPerSession; i++ { 594 args := []interface{}{ 595 sessionID, // session_id 596 randString(rng, 50), // id 597 randString(rng, 50), // value 598 } 599 if _, err := tx.StmtContext(ctx, w.insertCustomerStatement).ExecContext(ctx, args...); err != nil { 600 return err 601 } 602 } 603 for i := 0; i < w.devicesPerSession; i++ { 604 args := []interface{}{ 605 sessionID, // session_id 606 randString(rng, 100), // id 607 randString(rng, 50), // device_id 608 randString(rng, 50), // name 609 randString(rng, 50), // make 610 randString(rng, 50), // macaddress 611 randString(rng, 50), // model 612 randString(rng, 50), // serial_number 613 } 614 if _, err := tx.StmtContext(ctx, w.insertDeviceStatement).ExecContext(ctx, args...); err != nil { 615 return err 616 } 617 } 618 for i := 0; i < w.variantsPerSession; i++ { 619 args := []interface{}{ 620 sessionID, // session_id 621 randString(rng, 50), // id 622 randString(rng, 50), // value 623 } 624 if _, err := tx.StmtContext(ctx, w.insertVariantStatement).ExecContext(ctx, args...); err != nil { 625 return err 626 } 627 } 628 for i := 0; i < w.parametersPerSession; i++ { 629 args := []interface{}{ 630 sessionID, // session_id 631 randString(rng, 50), // id 632 randString(rng, 50), // value 633 } 634 if _, err := tx.StmtContext(ctx, w.insertParameterStatement).ExecContext(ctx, args...); err != nil { 635 return err 636 } 637 } 638 for i := 0; i < w.queriesPerSession; i++ { 639 args := []interface{}{ 640 sessionID, // session_id 641 randString(rng, 50), // id 642 } 643 if _, err := tx.StmtContext(ctx, w.insertQueryStatement).ExecContext(ctx, args...); err != nil { 644 return err 645 } 646 } 647 return nil 648 }); err != nil { 649 return err 650 } 651 // Record Stats. 652 elapsed := timeutil.Since(start) 653 hists.Get(`insert`).Record(elapsed) 654 return nil 655 } 656 657 func (w *interleavedPartitioned) fetchSessionID( 658 ctx context.Context, 659 rng *rand.Rand, 660 hists *histogram.Histograms, 661 locality string, 662 localPercent int, 663 ) (string, error) { 664 start := timeutil.Now() 665 baseSessionID := randomSessionID(rng, locality, localPercent) 666 var sessionID string 667 if err := w.findSessionIDStatement1.QueryRowContext(ctx, baseSessionID).Scan(&sessionID); err != nil && !errors.Is(err, gosql.ErrNoRows) { 668 return "", err 669 } 670 // Didn't find a next session ID, let's try the other way. 671 if len(sessionID) == 0 { 672 if err := w.findSessionIDStatement2.QueryRowContext(ctx, baseSessionID).Scan(&sessionID); err != nil && !errors.Is(err, gosql.ErrNoRows) { 673 return "", err 674 } 675 } 676 elapsed := timeutil.Since(start) 677 if len(sessionID) == 0 { 678 hists.Get(`findNoID`).Record(elapsed) 679 } else { 680 hists.Get(`findID`).Record(elapsed) 681 } 682 return sessionID, nil 683 } 684 685 func (w *interleavedPartitioned) retrieveFunc( 686 ctx context.Context, hists *histogram.Histograms, rng *rand.Rand, 687 ) error { 688 sessionID, err := w.fetchSessionID(ctx, rng, hists, w.locality, w.retrieveLocalPercent) 689 if err != nil { 690 return err 691 } 692 //Could not find a session ID, skip this operation. 693 if len(sessionID) == 0 { 694 return nil 695 } 696 697 start := timeutil.Now() 698 699 // Execute the queries. 700 for i, statement := range w.retrieveStatements { 701 if _, err := statement.ExecContext(ctx, sessionID); err != nil { 702 return errors.Wrapf(err, "error with query: %s", retrieveQueries[i]) 703 } 704 } 705 // Record Stats. 706 elapsed := timeutil.Since(start) 707 hists.Get(`retrieve`).Record(elapsed) 708 return nil 709 } 710 711 func (w *interleavedPartitioned) updateFunc( 712 ctx context.Context, hists *histogram.Histograms, rng *rand.Rand, 713 ) error { 714 sessionID, err := w.fetchSessionID(ctx, rng, hists, w.locality, w.updateLocalPercent) 715 if err != nil { 716 return err 717 } 718 //Could not find a session ID, skip this operation. 719 if len(sessionID) == 0 { 720 return nil 721 } 722 723 start := timeutil.Now() 724 // Execute the statements. 725 for i, statement := range w.retrieveStatements { 726 if _, err = statement.ExecContext(ctx, sessionID); err != nil { 727 return errors.Wrapf(err, "error with query: %s", retrieveQueries[i]) 728 } 729 } 730 for i, statement := range w.updateStatements { 731 if _, err = statement.ExecContext(ctx, randString(rng, 20), sessionID); err != nil { 732 return errors.Wrapf(err, "error with query: %s", updateQueries[i]) 733 } 734 } 735 // Record Stats. 736 elapsed := timeutil.Since(start) 737 hists.Get(`updates`).Record(elapsed) 738 return nil 739 } 740 741 // Hooks implements the Hookser interface. 742 func (w *interleavedPartitioned) Hooks() workload.Hooks { 743 return workload.Hooks{ 744 PreLoad: func(db *gosql.DB) error { 745 if _, err := db.Exec( 746 zoneLocationsStmt, w.eastZoneName, w.westZoneName, w.centralZoneName, 747 ); err != nil { 748 return err 749 } 750 if _, err := db.Exec( 751 fmt.Sprintf( 752 "ALTER PARTITION west OF TABLE sessions CONFIGURE ZONE USING"+ 753 " lease_preferences = '[[+zone=%[1]s]]', "+ 754 "constraints = '{+zone=%[1]s : 1}', num_replicas = 3", 755 w.westZoneName, 756 ), 757 ); err != nil { 758 return errors.Wrapf(err, "could not set zone for partition west") 759 } 760 if _, err := db.Exec( 761 fmt.Sprintf( 762 "ALTER PARTITION east OF TABLE sessions CONFIGURE ZONE USING"+ 763 " lease_preferences = '[[+zone=%[1]s]]', "+ 764 "constraints = '{+zone=%[1]s : 1}', num_replicas = 3", 765 w.eastZoneName, 766 ), 767 ); err != nil { 768 return errors.Wrapf(err, "could not set zone for partition east") 769 } 770 return nil 771 }, 772 Validate: func() error { 773 switch w.locality { 774 case `east`, `west`: 775 case `central`: 776 w.deletes = true 777 w.insertPercent = 0 778 w.retrievePercent = 0 779 w.updatePercent = 0 780 log.Info(context.Background(), 781 "locality is set to central, turning deletes on and everything else off", 782 ) 783 return nil 784 785 default: 786 return errors.New("invalid locality (needs to be east, west, or central)") 787 } 788 if w.insertPercent+w.retrievePercent+w.updatePercent != 100 { 789 return errors.New( 790 "operation percents ({insert,retrieve,delete}-percent flags) must add up to 100", 791 ) 792 } 793 return nil 794 }, 795 } 796 } 797 798 func (w *interleavedPartitioned) sessionsInitialRow(rowIdx int) []interface{} { 799 rng := rand.New(rand.NewSource(int64(rowIdx))) 800 // Set the time for the now string to be minus 5 mins so delete operations can 801 // start right away. 802 nowString := timeutil.Now().Add(time.Minute * time.Duration(-5)).UTC().Format(time.RFC3339) 803 sessionID := randomSessionID(rng, `east`, w.initEastPercent) 804 return []interface{}{ 805 sessionID, // session_id 806 randString(rng, 100), // affiliate 807 randString(rng, 50), // channel 808 randString(rng, 20), // language 809 nowString, // created 810 nowString, // updated 811 randString(rng, 20), // status 812 randString(rng, 50), // platform 813 randString(rng, 100), // query_id 814 } 815 } 816 817 var childTypes = []*types.T{ 818 types.Bytes, 819 types.Bytes, 820 types.Bytes, 821 types.Bytes, 822 types.Bytes, 823 } 824 825 func (w *interleavedPartitioned) childInitialRowBatchFunc( 826 rngFactor int64, nPerBatch int, 827 ) func(int, coldata.Batch, *bufalloc.ByteAllocator) { 828 return func(sessionRowIdx int, cb coldata.Batch, a *bufalloc.ByteAllocator) { 829 sessionRNG := rand.New(rand.NewSource(int64(sessionRowIdx))) 830 sessionID := randomSessionID(sessionRNG, `east`, w.initEastPercent) 831 nowString := timeutil.Now().UTC().Format(time.RFC3339) 832 rng := rand.New(rand.NewSource(int64(sessionRowIdx) + rngFactor)) 833 834 cb.Reset(childTypes, nPerBatch, coldata.StandardColumnFactory) 835 sessionIDCol := cb.ColVec(0).Bytes() 836 idCol := cb.ColVec(1).Bytes() 837 valueCol := cb.ColVec(2).Bytes() 838 createdCol := cb.ColVec(3).Bytes() 839 updatedCol := cb.ColVec(4).Bytes() 840 for rowIdx := 0; rowIdx < nPerBatch; rowIdx++ { 841 sessionIDCol.Set(rowIdx, []byte(sessionID)) 842 idCol.Set(rowIdx, []byte(randString(rng, 50))) 843 valueCol.Set(rowIdx, []byte(randString(rng, 50))) 844 createdCol.Set(rowIdx, []byte(nowString)) 845 updatedCol.Set(rowIdx, []byte(nowString)) 846 } 847 } 848 } 849 850 var deviceTypes = []*types.T{ 851 types.Bytes, 852 types.Bytes, 853 types.Bytes, 854 types.Bytes, 855 types.Bytes, 856 types.Bytes, 857 types.Bytes, 858 types.Bytes, 859 types.Bytes, 860 types.Bytes, 861 } 862 863 func (w *interleavedPartitioned) deviceInitialRowBatch( 864 sessionRowIdx int, cb coldata.Batch, a *bufalloc.ByteAllocator, 865 ) { 866 rng := rand.New(rand.NewSource(int64(sessionRowIdx) * 64)) 867 sessionRNG := rand.New(rand.NewSource(int64(sessionRowIdx))) 868 sessionID := randomSessionID(sessionRNG, `east`, w.initEastPercent) 869 nowString := timeutil.Now().UTC().Format(time.RFC3339) 870 871 cb.Reset(deviceTypes, w.devicesPerSession, coldata.StandardColumnFactory) 872 sessionIDCol := cb.ColVec(0).Bytes() 873 idCol := cb.ColVec(1).Bytes() 874 deviceIDCol := cb.ColVec(2).Bytes() 875 nameCol := cb.ColVec(3).Bytes() 876 makeCol := cb.ColVec(4).Bytes() 877 macaddressCol := cb.ColVec(5).Bytes() 878 modelCol := cb.ColVec(6).Bytes() 879 serialNumberCol := cb.ColVec(7).Bytes() 880 createdCol := cb.ColVec(8).Bytes() 881 updatedCol := cb.ColVec(9).Bytes() 882 for rowIdx := 0; rowIdx < w.devicesPerSession; rowIdx++ { 883 sessionIDCol.Set(rowIdx, []byte(sessionID)) 884 idCol.Set(rowIdx, []byte(randString(rng, 100))) 885 deviceIDCol.Set(rowIdx, []byte(randString(rng, 50))) 886 nameCol.Set(rowIdx, []byte(randString(rng, 50))) 887 makeCol.Set(rowIdx, []byte(randString(rng, 50))) 888 macaddressCol.Set(rowIdx, []byte(randString(rng, 50))) 889 modelCol.Set(rowIdx, []byte(randString(rng, 50))) 890 serialNumberCol.Set(rowIdx, []byte(randString(rng, 50))) 891 createdCol.Set(rowIdx, []byte(nowString)) 892 updatedCol.Set(rowIdx, []byte(nowString)) 893 } 894 } 895 896 var queryTypes = []*types.T{ 897 types.Bytes, 898 types.Bytes, 899 types.Bytes, 900 types.Bytes, 901 } 902 903 func (w *interleavedPartitioned) queryInitialRowBatch( 904 sessionRowIdx int, cb coldata.Batch, a *bufalloc.ByteAllocator, 905 ) { 906 rng := rand.New(rand.NewSource(int64(sessionRowIdx) * 64)) 907 sessionRNG := rand.New(rand.NewSource(int64(sessionRowIdx))) 908 sessionID := randomSessionID(sessionRNG, `east`, w.initEastPercent) 909 nowString := timeutil.Now().UTC().Format(time.RFC3339) 910 911 cb.Reset(queryTypes, w.queriesPerSession, coldata.StandardColumnFactory) 912 sessionIDCol := cb.ColVec(0).Bytes() 913 idCol := cb.ColVec(1).Bytes() 914 createdCol := cb.ColVec(2).Bytes() 915 updatedCol := cb.ColVec(3).Bytes() 916 for rowIdx := 0; rowIdx < w.queriesPerSession; rowIdx++ { 917 sessionIDCol.Set(rowIdx, []byte(sessionID)) 918 idCol.Set(rowIdx, []byte(randString(rng, 50))) 919 createdCol.Set(rowIdx, []byte(nowString)) 920 updatedCol.Set(rowIdx, []byte(nowString)) 921 } 922 } 923 924 func randomSessionID(rng *rand.Rand, locality string, localPercent int) string { 925 return randomSessionIDForInsert(rng, locality, localPercent, 0 /* nodeID */, 0 /* workerID */) 926 } 927 928 func randomSessionIDForInsert( 929 rng *rand.Rand, locality string, localPercent int, nodeID int, workerID int, 930 ) string { 931 // Is this a local operation? As in an east node accessing east data. 932 local := rng.Intn(100) < localPercent 933 // There have been some issues of session ID collisions so by adding the node 934 // and worker IDs is an attempt to minimize that. If they still occur, it must 935 // point to a serious issue and having the IDs should help identify it. 936 if (local && locality == `east`) || (!local && locality == `west`) { 937 return fmt.Sprintf("E-%s-n%dw%d", randString(rng, 90), nodeID, workerID) 938 } 939 return fmt.Sprintf("W-%s-n%dw%d", randString(rng, 90), nodeID, workerID) 940 } 941 942 func randString(rng *rand.Rand, length int) string { 943 return string(randutil.RandBytes(rng, length)) 944 }