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 }