github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/ledger/ledger.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 ledger
    12  
    13  import (
    14  	gosql "database/sql"
    15  	"hash/fnv"
    16  	"math/rand"
    17  	"strings"
    18  	"sync"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    21  	"github.com/cockroachdb/cockroach/pkg/workload"
    22  	"github.com/cockroachdb/cockroach/pkg/workload/histogram"
    23  	"github.com/cockroachdb/errors"
    24  	"github.com/spf13/pflag"
    25  )
    26  
    27  type ledger struct {
    28  	flags     workload.Flags
    29  	connFlags *workload.ConnFlags
    30  
    31  	seed              int64
    32  	customers         int
    33  	interleaved       bool
    34  	inlineArgs        bool
    35  	splits            int
    36  	fks               bool
    37  	historicalBalance bool
    38  	mix               string
    39  
    40  	txs  []tx
    41  	deck []int // contains indexes into the txs slice
    42  
    43  	reg      *histogram.Registry
    44  	rngPool  *sync.Pool
    45  	hashPool *sync.Pool
    46  }
    47  
    48  func init() {
    49  	workload.Register(ledgerMeta)
    50  }
    51  
    52  var ledgerMeta = workload.Meta{
    53  	Name:        `ledger`,
    54  	Description: `Ledger simulates an accounting system using double-entry bookkeeping`,
    55  	Version:     `1.0.0`,
    56  	New: func() workload.Generator {
    57  		g := &ledger{}
    58  		g.flags.FlagSet = pflag.NewFlagSet(`ledger`, pflag.ContinueOnError)
    59  		g.connFlags = workload.NewConnFlags(&g.flags)
    60  		g.flags.Int64Var(&g.seed, `seed`, 1, `Random number generator seed`)
    61  		g.flags.IntVar(&g.customers, `customers`, 1000, `Number of customers`)
    62  		g.flags.BoolVar(&g.interleaved, `interleaved`, false, `Use interleaved tables`)
    63  		g.flags.BoolVar(&g.inlineArgs, `inline-args`, false, `Use inline query arguments`)
    64  		g.flags.IntVar(&g.splits, `splits`, 0, `Number of splits to perform before starting normal operations`)
    65  		g.flags.BoolVar(&g.fks, `fks`, true, `Add the foreign keys`)
    66  		g.flags.BoolVar(&g.historicalBalance, `historical-balance`, false, `Perform balance txns using historical reads`)
    67  		g.flags.StringVar(&g.mix, `mix`,
    68  			`balance=50,withdrawal=37,deposit=12,reversal=0`,
    69  			`Weights for the transaction mix.`)
    70  		return g
    71  	},
    72  }
    73  
    74  // FromFlags returns a new ledger Generator configured with the given flags.
    75  func FromFlags(flags ...string) workload.Generator {
    76  	return workload.FromFlags(ledgerMeta, flags...)
    77  }
    78  
    79  // Meta implements the Generator interface.
    80  func (*ledger) Meta() workload.Meta { return ledgerMeta }
    81  
    82  // Flags implements the Flagser interface.
    83  func (w *ledger) Flags() workload.Flags { return w.flags }
    84  
    85  // Hooks implements the Hookser interface.
    86  func (w *ledger) Hooks() workload.Hooks {
    87  	return workload.Hooks{
    88  		Validate: func() error {
    89  			if w.interleaved {
    90  				return errors.Errorf("interleaved tables are not yet supported")
    91  			}
    92  			return initializeMix(w)
    93  		},
    94  		PostLoad: func(sqlDB *gosql.DB) error {
    95  			if w.fks {
    96  				fkStmts := []string{
    97  					`create index entry_auto_index_fk_customer on entry (customer_id ASC)`,
    98  					`create index entry_auto_index_fk_transaction on entry (transaction_id ASC)`,
    99  					`alter table entry add foreign key (customer_id) references customer (id)`,
   100  					`alter table entry add foreign key (transaction_id) references transaction (external_id)`,
   101  				}
   102  				for _, fkStmt := range fkStmts {
   103  					if _, err := sqlDB.Exec(fkStmt); err != nil {
   104  						return err
   105  					}
   106  				}
   107  			}
   108  			return nil
   109  		},
   110  	}
   111  }
   112  
   113  // Tables implements the Generator interface.
   114  func (w *ledger) Tables() []workload.Table {
   115  	if w.rngPool == nil {
   116  		w.rngPool = &sync.Pool{
   117  			New: func() interface{} { return rand.New(rand.NewSource(timeutil.Now().UnixNano())) },
   118  		}
   119  	}
   120  	if w.hashPool == nil {
   121  		w.hashPool = &sync.Pool{
   122  			New: func() interface{} { return fnv.New64() },
   123  		}
   124  	}
   125  
   126  	customer := workload.Table{
   127  		Name:   `customer`,
   128  		Schema: ledgerCustomerSchema,
   129  		InitialRows: workload.TypedTuples(
   130  			w.customers,
   131  			ledgerCustomerTypes,
   132  			w.ledgerCustomerInitialRow,
   133  		),
   134  		Splits: workload.Tuples(
   135  			numTxnsPerCustomer*w.splits,
   136  			w.ledgerCustomerSplitRow,
   137  		),
   138  	}
   139  	transaction := workload.Table{
   140  		Name:   `transaction`,
   141  		Schema: ledgerTransactionSchema,
   142  		InitialRows: workload.TypedTuples(
   143  			numTxnsPerCustomer*w.customers,
   144  			ledgerTransactionColTypes,
   145  			w.ledgerTransactionInitialRow,
   146  		),
   147  		Splits: workload.Tuples(
   148  			w.splits,
   149  			w.ledgerTransactionSplitRow,
   150  		),
   151  	}
   152  	entry := workload.Table{
   153  		Name:   `entry`,
   154  		Schema: ledgerEntrySchema,
   155  		InitialRows: workload.Tuples(
   156  			numEntriesPerCustomer*w.customers,
   157  			w.ledgerEntryInitialRow,
   158  		),
   159  		Splits: workload.Tuples(
   160  			numEntriesPerCustomer*w.splits,
   161  			w.ledgerEntrySplitRow,
   162  		),
   163  	}
   164  	session := workload.Table{
   165  		Name:   `session`,
   166  		Schema: ledgerSessionSchema,
   167  		InitialRows: workload.Tuples(
   168  			w.customers,
   169  			w.ledgerSessionInitialRow,
   170  		),
   171  		Splits: workload.Tuples(
   172  			w.splits,
   173  			w.ledgerSessionSplitRow,
   174  		),
   175  	}
   176  	return []workload.Table{
   177  		customer, transaction, entry, session,
   178  	}
   179  }
   180  
   181  // Ops implements the Opser interface.
   182  func (w *ledger) Ops(urls []string, reg *histogram.Registry) (workload.QueryLoad, error) {
   183  	sqlDatabase, err := workload.SanitizeUrls(w, w.connFlags.DBOverride, urls)
   184  	if err != nil {
   185  		return workload.QueryLoad{}, err
   186  	}
   187  	db, err := gosql.Open(`cockroach`, strings.Join(urls, ` `))
   188  	if err != nil {
   189  		return workload.QueryLoad{}, err
   190  	}
   191  	// Allow a maximum of concurrency+1 connections to the database.
   192  	db.SetMaxOpenConns(w.connFlags.Concurrency + 1)
   193  	db.SetMaxIdleConns(w.connFlags.Concurrency + 1)
   194  
   195  	w.reg = reg
   196  	ql := workload.QueryLoad{SQLDatabase: sqlDatabase}
   197  	now := timeutil.Now().UnixNano()
   198  	for i := 0; i < w.connFlags.Concurrency; i++ {
   199  		worker := &worker{
   200  			config:   w,
   201  			hists:    reg.GetHandle(),
   202  			db:       db,
   203  			rng:      rand.New(rand.NewSource(now + int64(i))),
   204  			deckPerm: append([]int(nil), w.deck...),
   205  			permIdx:  len(w.deck),
   206  		}
   207  		ql.WorkerFns = append(ql.WorkerFns, worker.run)
   208  	}
   209  	return ql, nil
   210  }