github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/queue/queue.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 queue 12 13 import ( 14 "bytes" 15 "context" 16 gosql "database/sql" 17 "fmt" 18 "strings" 19 "sync/atomic" 20 21 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 22 "github.com/cockroachdb/cockroach/pkg/workload" 23 "github.com/cockroachdb/cockroach/pkg/workload/histogram" 24 "github.com/spf13/pflag" 25 ) 26 27 const ( 28 queueSchema = `(ts BIGINT NOT NULL, id BIGINT NOT NULL, PRIMARY KEY(ts, id))` 29 ) 30 31 type queue struct { 32 flags workload.Flags 33 connFlags *workload.ConnFlags 34 batchSize int 35 } 36 37 func init() { 38 workload.Register(queueMeta) 39 } 40 41 var queueMeta = workload.Meta{ 42 Name: `queue`, 43 Description: `A simple queue-like application load: inserts into a table in sequence ` + 44 `(ordered by primary key), followed by the deletion of inserted rows starting from the ` + 45 `beginning of the sequence.`, 46 Version: `1.0.0`, 47 New: func() workload.Generator { 48 g := &queue{} 49 g.flags.FlagSet = pflag.NewFlagSet(`queue`, pflag.ContinueOnError) 50 g.connFlags = workload.NewConnFlags(&g.flags) 51 g.flags.IntVar(&g.batchSize, `batch`, 1, `Number of blocks to insert in a single SQL statement`) 52 return g 53 }, 54 } 55 56 // Meta implements the Generator interface. 57 func (*queue) Meta() workload.Meta { return queueMeta } 58 59 // Flags implements the Flagser interface. 60 func (w *queue) Flags() workload.Flags { return w.flags } 61 62 // Tables implements the Generator interface. 63 func (w *queue) Tables() []workload.Table { 64 table := workload.Table{ 65 Name: `queue`, 66 Schema: queueSchema, 67 } 68 return []workload.Table{table} 69 } 70 71 // Ops implements the Opser interface. 72 func (w *queue) Ops(urls []string, reg *histogram.Registry) (workload.QueryLoad, error) { 73 sqlDatabase, err := workload.SanitizeUrls(w, w.connFlags.DBOverride, urls) 74 if err != nil { 75 return workload.QueryLoad{}, err 76 } 77 db, err := gosql.Open(`cockroach`, strings.Join(urls, ` `)) 78 if err != nil { 79 return workload.QueryLoad{}, err 80 } 81 db.SetMaxOpenConns(w.connFlags.Concurrency + 1) 82 db.SetMaxIdleConns(w.connFlags.Concurrency + 1) 83 84 // Generate queue insert statement. 85 var buf bytes.Buffer 86 buf.WriteString(`INSERT INTO queue (ts, id) VALUES`) 87 for i := 0; i < w.batchSize; i++ { 88 j := i * 2 89 if i > 0 { 90 buf.WriteString(", ") 91 } 92 fmt.Fprintf(&buf, ` ($%d, $%d)`, j+1, j+2) 93 } 94 insertStmt, err := db.Prepare(buf.String()) 95 if err != nil { 96 return workload.QueryLoad{}, err 97 } 98 99 // Generate queue deletion statement. This is intentionally in a naive form 100 // for testing purposes. 101 deleteStmt, err := db.Prepare(`DELETE FROM queue WHERE ts < $1`) 102 if err != nil { 103 return workload.QueryLoad{}, err 104 } 105 106 seqFunc := makeSequenceFunc() 107 108 ql := workload.QueryLoad{SQLDatabase: sqlDatabase} 109 for i := 0; i < w.connFlags.Concurrency; i++ { 110 op := queueOp{ 111 workerID: i + 1, 112 config: w, 113 hists: reg.GetHandle(), 114 db: db, 115 insertStmt: insertStmt, 116 deleteStmt: deleteStmt, 117 getSeq: seqFunc, 118 } 119 ql.WorkerFns = append(ql.WorkerFns, op.run) 120 } 121 return ql, nil 122 } 123 124 // queueOp represents a single concurrent "worker" generating the workload. Each 125 // queueOp worker both inserts into the queue table *and* consumes (deletes) 126 // entries from the beginning of the queue. 127 type queueOp struct { 128 workerID int 129 config *queue 130 hists *histogram.Histograms 131 db *gosql.DB 132 insertStmt *gosql.Stmt 133 deleteStmt *gosql.Stmt 134 getSeq func() int 135 } 136 137 func (o *queueOp) run(ctx context.Context) error { 138 count := o.getSeq() 139 start := count * o.config.batchSize 140 end := start + o.config.batchSize 141 142 // Write batch. 143 params := make([]interface{}, 2*o.config.batchSize) 144 for i := 0; i < o.config.batchSize; i++ { 145 paramOffset := i * 2 146 params[paramOffset+0] = start + i 147 params[paramOffset+1] = o.workerID 148 } 149 startTime := timeutil.Now() 150 _, err := o.insertStmt.Exec(params...) 151 if err != nil { 152 return err 153 } 154 elapsed := timeutil.Since(startTime) 155 o.hists.Get("write").Record(elapsed) 156 157 // Delete batch which was just written. 158 startTime = timeutil.Now() 159 _, err = o.deleteStmt.Exec(end) 160 elapsed = timeutil.Since(startTime) 161 o.hists.Get(`delete`).Record(elapsed) 162 return err 163 } 164 165 func makeSequenceFunc() func() int { 166 i := int64(0) 167 return func() int { 168 return int(atomic.AddInt64(&i, 1)) 169 } 170 }