github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/workloadsql/workloadsql.go (about)

     1  // Copyright 2019 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 workloadsql
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	gosql "database/sql"
    17  	"fmt"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/sql/lex"
    23  	"github.com/cockroachdb/cockroach/pkg/util/ctxgroup"
    24  	"github.com/cockroachdb/cockroach/pkg/util/log"
    25  	"github.com/cockroachdb/cockroach/pkg/util/version"
    26  	"github.com/cockroachdb/cockroach/pkg/workload"
    27  	"github.com/cockroachdb/errors"
    28  	"golang.org/x/time/rate"
    29  )
    30  
    31  // Setup creates the given tables and fills them with initial data.
    32  //
    33  // The size of the loaded data is returned in bytes, suitable for use with
    34  // SetBytes of benchmarks. The exact definition of this is deferred to the
    35  // InitialDataLoader implementation.
    36  func Setup(
    37  	ctx context.Context, db *gosql.DB, gen workload.Generator, l workload.InitialDataLoader,
    38  ) (int64, error) {
    39  	bytes, err := l.InitialDataLoad(ctx, db, gen)
    40  	if err != nil {
    41  		return 0, err
    42  	}
    43  
    44  	const splitConcurrency = 384 // TODO(dan): Don't hardcode this.
    45  	for _, table := range gen.Tables() {
    46  		if err := Split(ctx, db, table, splitConcurrency); err != nil {
    47  			return 0, err
    48  		}
    49  	}
    50  
    51  	var hooks workload.Hooks
    52  	if h, ok := gen.(workload.Hookser); ok {
    53  		hooks = h.Hooks()
    54  	}
    55  	if hooks.PostLoad != nil {
    56  		if err := hooks.PostLoad(db); err != nil {
    57  			return 0, errors.Wrapf(err, "Could not postload")
    58  		}
    59  	}
    60  
    61  	return bytes, nil
    62  }
    63  
    64  // maybeDisableMergeQueue disables the merge queue for versions prior to
    65  // 19.2.
    66  func maybeDisableMergeQueue(db *gosql.DB) error {
    67  	var ok bool
    68  	if err := db.QueryRow(
    69  		`SELECT count(*) > 0 FROM [ SHOW ALL CLUSTER SETTINGS ] AS _ (v) WHERE v = 'kv.range_merge.queue_enabled'`,
    70  	).Scan(&ok); err != nil || !ok {
    71  		return err
    72  	}
    73  	var versionStr string
    74  	err := db.QueryRow(
    75  		`SELECT value FROM crdb_internal.node_build_info WHERE field = 'Version'`,
    76  	).Scan(&versionStr)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	v, err := version.Parse(versionStr)
    81  	// If we can't parse the error then we'll assume that we should disable the
    82  	// queue. This happens in testing.
    83  	if err == nil && (v.Major() > 19 || (v.Major() == 19 && v.Minor() >= 2)) {
    84  		return nil
    85  	}
    86  	_, err = db.Exec("SET CLUSTER SETTING kv.range_merge.queue_enabled = false")
    87  	return err
    88  }
    89  
    90  // Split creates the range splits defined by the given table.
    91  func Split(ctx context.Context, db *gosql.DB, table workload.Table, concurrency int) error {
    92  	// Prevent the merge queue from immediately discarding our splits.
    93  	if err := maybeDisableMergeQueue(db); err != nil {
    94  		return err
    95  	}
    96  
    97  	if table.Splits.NumBatches <= 0 {
    98  		return nil
    99  	}
   100  	splitPoints := make([][]interface{}, 0, table.Splits.NumBatches)
   101  	for splitIdx := 0; splitIdx < table.Splits.NumBatches; splitIdx++ {
   102  		splitPoints = append(splitPoints, table.Splits.BatchRows(splitIdx)...)
   103  	}
   104  	sort.Sort(sliceSliceInterface(splitPoints))
   105  
   106  	type pair struct {
   107  		lo, hi int
   108  	}
   109  	splitCh := make(chan pair, len(splitPoints)/2+1)
   110  	splitCh <- pair{0, len(splitPoints)}
   111  	doneCh := make(chan struct{})
   112  
   113  	log.Infof(ctx, `starting %d splits`, len(splitPoints))
   114  	g := ctxgroup.WithContext(ctx)
   115  	// Rate limit splitting to prevent replica imbalance.
   116  	r := rate.NewLimiter(128, 1)
   117  	for i := 0; i < concurrency; i++ {
   118  		g.GoCtx(func(ctx context.Context) error {
   119  			var buf bytes.Buffer
   120  			for {
   121  				select {
   122  				case p, ok := <-splitCh:
   123  					if !ok {
   124  						return nil
   125  					}
   126  					if err := r.Wait(ctx); err != nil {
   127  						return err
   128  					}
   129  					m := (p.lo + p.hi) / 2
   130  					split := strings.Join(StringTuple(splitPoints[m]), `,`)
   131  
   132  					buf.Reset()
   133  					fmt.Fprintf(&buf, `ALTER TABLE %s SPLIT AT VALUES (%s)`, table.Name, split)
   134  					// If you're investigating an error coming out of this Exec, see the
   135  					// HACK comment in ColBatchToRows for some context that may (or may
   136  					// not) help you.
   137  					stmt := buf.String()
   138  					if _, err := db.Exec(stmt); err != nil {
   139  						return errors.Wrapf(err, "executing %s", stmt)
   140  					}
   141  
   142  					buf.Reset()
   143  					fmt.Fprintf(&buf, `ALTER TABLE %s SCATTER FROM (%s) TO (%s)`,
   144  						table.Name, split, split)
   145  					stmt = buf.String()
   146  					if _, err := db.Exec(stmt); err != nil {
   147  						// SCATTER can collide with normal replicate queue
   148  						// operations and fail spuriously, so only print the
   149  						// error.
   150  						log.Warningf(ctx, `%s: %v`, stmt, err)
   151  					}
   152  
   153  					select {
   154  					case doneCh <- struct{}{}:
   155  					case <-ctx.Done():
   156  						return ctx.Err()
   157  					}
   158  
   159  					if p.lo < m {
   160  						splitCh <- pair{p.lo, m}
   161  					}
   162  					if m+1 < p.hi {
   163  						splitCh <- pair{m + 1, p.hi}
   164  					}
   165  				case <-ctx.Done():
   166  					return ctx.Err()
   167  				}
   168  
   169  			}
   170  		})
   171  	}
   172  	g.GoCtx(func(ctx context.Context) error {
   173  		finished := 0
   174  		for finished < len(splitPoints) {
   175  			select {
   176  			case <-doneCh:
   177  				finished++
   178  				if finished%1000 == 0 {
   179  					log.Infof(ctx, "finished %d of %d splits", finished, len(splitPoints))
   180  				}
   181  			case <-ctx.Done():
   182  				return ctx.Err()
   183  			}
   184  		}
   185  		close(splitCh)
   186  		return nil
   187  	})
   188  	return g.Wait()
   189  }
   190  
   191  // StringTuple returns the given datums as strings suitable for use in directly
   192  // in SQL.
   193  //
   194  // TODO(dan): Remove this once SCATTER supports placeholders.
   195  func StringTuple(datums []interface{}) []string {
   196  	s := make([]string, len(datums))
   197  	for i, datum := range datums {
   198  		if datum == nil {
   199  			s[i] = `NULL`
   200  			continue
   201  		}
   202  		switch x := datum.(type) {
   203  		case int:
   204  			s[i] = strconv.Itoa(x)
   205  		case int64:
   206  			s[i] = strconv.FormatInt(x, 10)
   207  		case uint64:
   208  			s[i] = strconv.FormatUint(x, 10)
   209  		case string:
   210  			s[i] = lex.EscapeSQLString(x)
   211  		case float64:
   212  			s[i] = fmt.Sprintf(`%f`, x)
   213  		case []byte:
   214  			// See the HACK comment in ColBatchToRows.
   215  			s[i] = lex.EscapeSQLString(string(x))
   216  		default:
   217  			panic(fmt.Sprintf("unsupported type %T: %v", x, x))
   218  		}
   219  	}
   220  	return s
   221  }
   222  
   223  type sliceSliceInterface [][]interface{}
   224  
   225  func (s sliceSliceInterface) Len() int      { return len(s) }
   226  func (s sliceSliceInterface) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   227  func (s sliceSliceInterface) Less(i, j int) bool {
   228  	for offset := 0; ; offset++ {
   229  		iLen, jLen := len(s[i]), len(s[j])
   230  		if iLen <= offset || jLen <= offset {
   231  			return iLen < jLen
   232  		}
   233  		var cmp int
   234  		switch x := s[i][offset].(type) {
   235  		case int:
   236  			if y := s[j][offset].(int); x < y {
   237  				return true
   238  			} else if x > y {
   239  				return false
   240  			}
   241  			continue
   242  		case int64:
   243  			if y := s[j][offset].(int64); x < y {
   244  				return true
   245  			} else if x > y {
   246  				return false
   247  			}
   248  			continue
   249  		case float64:
   250  			if y := s[j][offset].(float64); x < y {
   251  				return true
   252  			} else if x > y {
   253  				return false
   254  			}
   255  			continue
   256  		case uint64:
   257  			if y := s[j][offset].(uint64); x < y {
   258  				return true
   259  			} else if x > y {
   260  				return false
   261  			}
   262  			continue
   263  		case string:
   264  			cmp = strings.Compare(x, s[j][offset].(string))
   265  		case []byte:
   266  			cmp = bytes.Compare(x, s[j][offset].([]byte))
   267  		default:
   268  			panic(fmt.Sprintf("unsupported type %T: %v", x, x))
   269  		}
   270  		if cmp < 0 {
   271  			return true
   272  		} else if cmp > 0 {
   273  			return false
   274  		}
   275  	}
   276  }