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 }