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  }