github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/tests/kv_test.go (about) 1 // Copyright 2016 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 tests_test 12 13 import ( 14 "bytes" 15 "context" 16 gosql "database/sql" 17 "fmt" 18 "math/rand" 19 "reflect" 20 "runtime" 21 "strings" 22 "testing" 23 24 "github.com/cockroachdb/cockroach/pkg/base" 25 kv2 "github.com/cockroachdb/cockroach/pkg/kv" 26 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 27 "github.com/cockroachdb/errors" 28 ) 29 30 type kvInterface interface { 31 Insert(rows, run int) error 32 Update(rows, run int) error 33 Delete(rows, run int) error 34 Scan(rows, run int) error 35 36 prep(rows int, initData bool) error 37 done() 38 } 39 40 // kvNative uses the native client package to implement kvInterface. 41 type kvNative struct { 42 db *kv2.DB 43 epoch int 44 prefix string 45 doneFn func() 46 } 47 48 func newKVNative(b *testing.B) kvInterface { 49 s, _, db := serverutils.StartServer(b, base.TestServerArgs{}) 50 51 // Note that using the local client.DB isn't a strictly fair 52 // comparison with SQL as we want these client requests to be sent 53 // over the network. 54 return &kvNative{ 55 db: db, 56 doneFn: func() { 57 s.Stopper().Stop(context.Background()) 58 }, 59 } 60 } 61 62 func (kv *kvNative) Insert(rows, run int) error { 63 firstRow := rows * run 64 lastRow := rows * (run + 1) 65 err := kv.db.Txn(context.Background(), func(ctx context.Context, txn *kv2.Txn) error { 66 b := txn.NewBatch() 67 for i := firstRow; i < lastRow; i++ { 68 b.Put(fmt.Sprintf("%s%08d", kv.prefix, i), i) 69 } 70 return txn.CommitInBatch(ctx, b) 71 }) 72 return err 73 } 74 75 func (kv *kvNative) Update(rows, run int) error { 76 perm := rand.Perm(rows) 77 err := kv.db.Txn(context.Background(), func(ctx context.Context, txn *kv2.Txn) error { 78 // Read all values in a batch. 79 b := txn.NewBatch() 80 for i := 0; i < rows; i++ { 81 b.Get(fmt.Sprintf("%s%08d", kv.prefix, perm[i])) 82 } 83 if err := txn.Run(ctx, b); err != nil { 84 return err 85 } 86 // Now add one to each value and add as puts to write batch. 87 wb := txn.NewBatch() 88 for i, result := range b.Results { 89 v := result.Rows[0].ValueInt() 90 wb.Put(fmt.Sprintf("%s%08d", kv.prefix, perm[i]), v+1) 91 } 92 return txn.CommitInBatch(ctx, wb) 93 }) 94 return err 95 } 96 97 func (kv *kvNative) Delete(rows, run int) error { 98 firstRow := rows * run 99 lastRow := rows * (run + 1) 100 err := kv.db.Txn(context.Background(), func(ctx context.Context, txn *kv2.Txn) error { 101 b := txn.NewBatch() 102 for i := firstRow; i < lastRow; i++ { 103 b.Del(fmt.Sprintf("%s%08d", kv.prefix, i)) 104 } 105 return txn.CommitInBatch(ctx, b) 106 }) 107 return err 108 } 109 110 func (kv *kvNative) Scan(rows, run int) error { 111 var kvs []kv2.KeyValue 112 err := kv.db.Txn(context.Background(), func(ctx context.Context, txn *kv2.Txn) error { 113 var err error 114 kvs, err = txn.Scan(ctx, fmt.Sprintf("%s%08d", kv.prefix, 0), fmt.Sprintf("%s%08d", kv.prefix, rows), int64(rows)) 115 return err 116 }) 117 if len(kvs) != rows { 118 return errors.Errorf("expected %d rows; got %d", rows, len(kvs)) 119 } 120 return err 121 } 122 123 func (kv *kvNative) prep(rows int, initData bool) error { 124 kv.epoch++ 125 kv.prefix = fmt.Sprintf("%d/", kv.epoch) 126 if !initData { 127 return nil 128 } 129 err := kv.db.Txn(context.Background(), func(ctx context.Context, txn *kv2.Txn) error { 130 b := txn.NewBatch() 131 for i := 0; i < rows; i++ { 132 b.Put(fmt.Sprintf("%s%08d", kv.prefix, i), i) 133 } 134 return txn.CommitInBatch(ctx, b) 135 }) 136 return err 137 } 138 139 func (kv *kvNative) done() { 140 kv.doneFn() 141 } 142 143 // kvSQL is a SQL-based implementation of the KV interface. 144 type kvSQL struct { 145 db *gosql.DB 146 buf bytes.Buffer 147 doneFn func() 148 } 149 150 func newKVSQL(b *testing.B) kvInterface { 151 s, db, _ := serverutils.StartServer(b, base.TestServerArgs{UseDatabase: "bench"}) 152 153 if _, err := db.Exec(`CREATE DATABASE IF NOT EXISTS bench`); err != nil { 154 b.Fatal(err) 155 } 156 157 kv := &kvSQL{} 158 kv.db = db 159 kv.doneFn = func() { 160 s.Stopper().Stop(context.Background()) 161 } 162 return kv 163 } 164 165 func (kv *kvSQL) Insert(rows, run int) error { 166 firstRow := rows * run 167 defer kv.buf.Reset() 168 kv.buf.WriteString(`INSERT INTO bench.kv VALUES `) 169 for i := 0; i < rows; i++ { 170 if i > 0 { 171 kv.buf.WriteString(", ") 172 } 173 fmt.Fprintf(&kv.buf, "('%08d', %d)", i+firstRow, i) 174 } 175 _, err := kv.db.Exec(kv.buf.String()) 176 return err 177 } 178 179 func (kv *kvSQL) Update(rows, run int) error { 180 perm := rand.Perm(rows) 181 defer kv.buf.Reset() 182 kv.buf.WriteString(`UPDATE bench.kv SET v = v + 1 WHERE k IN (`) 183 for j := 0; j < rows; j++ { 184 if j > 0 { 185 kv.buf.WriteString(", ") 186 } 187 fmt.Fprintf(&kv.buf, `'%08d'`, perm[j]) 188 } 189 kv.buf.WriteString(`)`) 190 _, err := kv.db.Exec(kv.buf.String()) 191 return err 192 } 193 194 func (kv *kvSQL) Delete(rows, run int) error { 195 firstRow := rows * run 196 defer kv.buf.Reset() 197 kv.buf.WriteString(`DELETE FROM bench.kv WHERE k IN (`) 198 for j := 0; j < rows; j++ { 199 if j > 0 { 200 kv.buf.WriteString(", ") 201 } 202 fmt.Fprintf(&kv.buf, `'%08d'`, j+firstRow) 203 } 204 kv.buf.WriteString(`)`) 205 _, err := kv.db.Exec(kv.buf.String()) 206 return err 207 } 208 209 func (kv *kvSQL) Scan(count, run int) error { 210 rows, err := kv.db.Query(fmt.Sprintf("SELECT * FROM bench.kv LIMIT %d", count)) 211 if err != nil { 212 return err 213 } 214 n := 0 215 for rows.Next() { 216 n++ 217 } 218 rows.Close() 219 if err := rows.Err(); err != nil { 220 return err 221 } 222 if n != count { 223 return errors.Errorf("unexpected result count: %d (expected %d)", n, count) 224 } 225 return nil 226 } 227 228 func (kv *kvSQL) prep(rows int, initData bool) error { 229 if _, err := kv.db.Exec(`DROP TABLE IF EXISTS bench.kv`); err != nil { 230 return err 231 } 232 schema := ` 233 CREATE TABLE IF NOT EXISTS bench.kv ( 234 k STRING PRIMARY KEY, 235 v INT, 236 FAMILY (k, v) 237 ) 238 ` 239 if _, err := kv.db.Exec(schema); err != nil { 240 return err 241 } 242 if !initData { 243 return nil 244 } 245 defer kv.buf.Reset() 246 kv.buf.WriteString(`INSERT INTO bench.kv VALUES `) 247 for i := 0; i < rows; i++ { 248 if i > 0 { 249 kv.buf.WriteString(", ") 250 } 251 fmt.Fprintf(&kv.buf, "('%08d', %d)", i, i) 252 } 253 _, err := kv.db.Exec(kv.buf.String()) 254 return err 255 } 256 257 func (kv *kvSQL) done() { 258 kv.doneFn() 259 } 260 261 func BenchmarkKV(b *testing.B) { 262 for i, opFn := range []func(kvInterface, int, int) error{ 263 kvInterface.Insert, 264 kvInterface.Update, 265 kvInterface.Delete, 266 kvInterface.Scan, 267 } { 268 opName := runtime.FuncForPC(reflect.ValueOf(opFn).Pointer()).Name() 269 opName = strings.TrimPrefix(opName, "github.com/cockroachdb/cockroach/pkg/sql/tests_test.kvInterface.") 270 b.Run(opName, func(b *testing.B) { 271 for _, kvFn := range []func(*testing.B) kvInterface{ 272 newKVNative, 273 newKVSQL, 274 } { 275 kvTyp := runtime.FuncForPC(reflect.ValueOf(kvFn).Pointer()).Name() 276 kvTyp = strings.TrimPrefix(kvTyp, "github.com/cockroachdb/cockroach/pkg/sql/tests_test.newKV") 277 b.Run(kvTyp, func(b *testing.B) { 278 for _, rows := range []int{1, 10, 100, 1000, 10000} { 279 b.Run(fmt.Sprintf("rows=%d", rows), func(b *testing.B) { 280 kv := kvFn(b) 281 defer kv.done() 282 283 if err := kv.prep(rows, i != 0 /* Insert */ && i != 2 /* Delete */); err != nil { 284 b.Fatal(err) 285 } 286 b.ResetTimer() 287 for i := 0; i < b.N; i++ { 288 if err := opFn(kv, rows, i); err != nil { 289 b.Fatal(err) 290 } 291 } 292 b.StopTimer() 293 }) 294 } 295 }) 296 } 297 }) 298 } 299 }