github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/contrib/integration/bank/main.go (about) 1 /* 2 * Copyright 2017-2018 Dgraph Labs, Inc. and Contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "bufio" 21 "bytes" 22 "context" 23 "encoding/json" 24 "flag" 25 "fmt" 26 "log" 27 "math/rand" 28 "sort" 29 "strings" 30 "sync" 31 "sync/atomic" 32 "time" 33 34 "github.com/dgraph-io/dgo" 35 "github.com/dgraph-io/dgo/protos/api" 36 "github.com/dgraph-io/dgo/x" 37 "google.golang.org/grpc" 38 ) 39 40 var ( 41 users = flag.Int("users", 100, "Number of accounts.") 42 conc = flag.Int("txns", 3, "Number of concurrent transactions per client.") 43 dur = flag.String("dur", "1m", "How long to run the transactions.") 44 alpha = flag.String("alpha", "localhost:9080", "Address of Dgraph alpha.") 45 verbose = flag.Bool("verbose", true, "Output all logs in verbose mode.") 46 ) 47 48 var startBal = 10 49 50 type account struct { 51 Uid string `json:"uid"` 52 Key int `json:"key,omitempty"` 53 Bal int `json:"bal,omitempty"` 54 Typ string `json:"typ"` 55 } 56 57 type state struct { 58 aborts int32 59 runs int32 60 } 61 62 func (s *state) createAccounts(dg *dgo.Dgraph) { 63 op := api.Operation{DropAll: true} 64 x.Check(dg.Alter(context.Background(), &op)) 65 66 op.DropAll = false 67 op.Schema = ` 68 key: int @index(int) @upsert . 69 bal: int . 70 typ: string @index(exact) @upsert . 71 ` 72 x.Check(dg.Alter(context.Background(), &op)) 73 74 var all []account 75 for i := 1; i <= *users; i++ { 76 a := account{ 77 Key: i, 78 Bal: startBal, 79 Typ: "ba", 80 } 81 all = append(all, a) 82 } 83 data, err := json.Marshal(all) 84 x.Check(err) 85 86 txn := dg.NewTxn() 87 defer func() { 88 if err := txn.Discard(context.Background()); err != nil { 89 log.Fatalf("Discarding transaction failed: %+v\n", err) 90 } 91 }() 92 93 var mu api.Mutation 94 mu.SetJson = data 95 if *verbose { 96 log.Printf("mutation: %s\n", mu.SetJson) 97 } 98 _, err = txn.Mutate(context.Background(), &mu) 99 x.Check(err) 100 x.Check(txn.Commit(context.Background())) 101 } 102 103 func (s *state) runTotal(dg *dgo.Dgraph) error { 104 query := ` 105 { 106 q(func: eq(typ, "ba")) { 107 uid 108 key 109 bal 110 } 111 } 112 ` 113 txn := dg.NewReadOnlyTxn() 114 defer func() { 115 if err := txn.Discard(context.Background()); err != nil { 116 log.Fatalf("Discarding transaction failed: %+v\n", err) 117 } 118 }() 119 120 resp, err := txn.Query(context.Background(), query) 121 if err != nil { 122 return err 123 } 124 125 m := make(map[string][]account) 126 if err := json.Unmarshal(resp.Json, &m); err != nil { 127 return err 128 } 129 accounts := m["q"] 130 sort.Slice(accounts, func(i, j int) bool { 131 return accounts[i].Key < accounts[j].Key 132 }) 133 var total int 134 for _, a := range accounts { 135 total += a.Bal 136 } 137 if *verbose { 138 log.Printf("Read: %v. Total: %d\n", accounts, total) 139 } 140 if len(accounts) > *users { 141 log.Fatalf("len(accounts) = %d", len(accounts)) 142 } 143 if total != *users*startBal { 144 log.Fatalf("Total = %d", total) 145 } 146 return nil 147 } 148 149 func (s *state) findAccount(txn *dgo.Txn, key int) (account, error) { 150 query := fmt.Sprintf(`{ q(func: eq(key, %d)) { key, uid, bal, typ }}`, key) 151 resp, err := txn.Query(context.Background(), query) 152 if err != nil { 153 return account{}, err 154 } 155 m := make(map[string][]account) 156 if err := json.Unmarshal(resp.Json, &m); err != nil { 157 log.Fatal(err) 158 } 159 accounts := m["q"] 160 if len(accounts) > 1 { 161 log.Printf("Query: %s. Response: %s\n", query, resp.Json) 162 log.Fatal("Found multiple accounts") 163 } 164 if len(accounts) == 0 { 165 if *verbose { 166 log.Printf("Unable to find account for K_%02d. JSON: %s\n", key, resp.Json) 167 } 168 return account{Key: key, Typ: "ba"}, nil 169 } 170 return accounts[0], nil 171 } 172 173 func (s *state) runTransaction(dg *dgo.Dgraph, buf *bytes.Buffer) error { 174 w := bufio.NewWriter(buf) 175 fmt.Fprintf(w, "==>\n") 176 defer func() { 177 fmt.Fprintf(w, "---\n") 178 w.Flush() 179 }() 180 181 ctx := context.Background() 182 txn := dg.NewTxn() 183 defer func() { 184 if err := txn.Discard(context.Background()); err != nil { 185 log.Fatalf("Discarding transaction failed: %+v\n", err) 186 } 187 }() 188 189 var sk, sd int 190 for { 191 sk = rand.Intn(*users + 1) 192 sd = rand.Intn(*users + 1) 193 if sk == 0 || sd == 0 { // Don't touch zero. 194 continue 195 } 196 if sk != sd { 197 break 198 } 199 } 200 201 src, err := s.findAccount(txn, sk) 202 if err != nil { 203 return err 204 } 205 dst, err := s.findAccount(txn, sd) 206 if err != nil { 207 return err 208 } 209 if src.Key == dst.Key { 210 return nil 211 } 212 213 amount := rand.Intn(10) 214 if src.Bal-amount <= 0 { 215 amount = src.Bal 216 } 217 fmt.Fprintf(w, "Moving [$%d, K_%02d -> K_%02d]. Src:%+v. Dst: %+v\n", 218 amount, src.Key, dst.Key, src, dst) 219 src.Bal -= amount 220 dst.Bal += amount 221 var mu api.Mutation 222 if len(src.Uid) > 0 { 223 // If there was no src.Uid, then don't run any mutation. 224 if src.Bal == 0 { 225 pb, err := json.Marshal(src) 226 x.Check(err) 227 mu.DeleteJson = pb 228 fmt.Fprintf(w, "Deleting K_%02d: %s\n", src.Key, mu.DeleteJson) 229 } else { 230 data, err := json.Marshal(src) 231 x.Check(err) 232 mu.SetJson = data 233 } 234 _, err := txn.Mutate(ctx, &mu) 235 if err != nil { 236 fmt.Fprintf(w, "Error while mutate: %v", err) 237 return err 238 } 239 } 240 241 mu = api.Mutation{} 242 data, err := json.Marshal(dst) 243 x.Check(err) 244 mu.SetJson = data 245 assigned, err := txn.Mutate(ctx, &mu) 246 if err != nil { 247 fmt.Fprintf(w, "Error while mutate: %v", err) 248 return err 249 } 250 251 if err := txn.Commit(ctx); err != nil { 252 return err 253 } 254 if len(assigned.GetUids()) > 0 { 255 fmt.Fprintf(w, "CREATED K_%02d: %+v for %+v\n", dst.Key, assigned.GetUids(), dst) 256 for _, uid := range assigned.GetUids() { 257 dst.Uid = uid 258 } 259 } 260 fmt.Fprintf(w, "MOVED [$%d, K_%02d -> K_%02d]. Src:%+v. Dst: %+v\n", 261 amount, src.Key, dst.Key, src, dst) 262 return nil 263 } 264 265 func (s *state) loop(dg *dgo.Dgraph, wg *sync.WaitGroup) { 266 defer wg.Done() 267 dur, err := time.ParseDuration(*dur) 268 if err != nil { 269 log.Fatal(err) 270 } 271 end := time.Now().Add(dur) 272 273 var buf bytes.Buffer 274 for i := 0; ; i++ { 275 if i%5 == 0 { 276 if err := s.runTotal(dg); err != nil { 277 log.Printf("Error while runTotal: %v", err) 278 } 279 continue 280 } 281 282 buf.Reset() 283 err := s.runTransaction(dg, &buf) 284 if *verbose { 285 log.Printf("Final error: %v. %s", err, buf.String()) 286 } 287 if err != nil { 288 atomic.AddInt32(&s.aborts, 1) 289 } else { 290 r := atomic.AddInt32(&s.runs, 1) 291 if r%100 == 0 { 292 a := atomic.LoadInt32(&s.aborts) 293 fmt.Printf("Runs: %d. Aborts: %d\n", r, a) 294 } 295 if time.Now().After(end) { 296 return 297 } 298 } 299 } 300 } 301 302 func main() { 303 flag.Parse() 304 305 all := strings.Split(*alpha, ",") 306 x.AssertTrue(len(all) > 0) 307 308 var clients []*dgo.Dgraph 309 for _, one := range all { 310 conn, err := grpc.Dial(one, grpc.WithInsecure()) 311 if err != nil { 312 log.Fatal(err) 313 } 314 dc := api.NewDgraphClient(conn) 315 dg := dgo.NewDgraphClient(dc) 316 // login as groot to perform the DropAll operation later 317 x.Check(dg.Login(context.Background(), "groot", "password")) 318 clients = append(clients, dg) 319 } 320 321 s := state{} 322 s.createAccounts(clients[0]) 323 324 var wg sync.WaitGroup 325 for i := 0; i < *conc; i++ { 326 for _, dg := range clients { 327 wg.Add(1) 328 go s.loop(dg, &wg) 329 } 330 } 331 wg.Wait() 332 fmt.Println() 333 fmt.Println("Total aborts", s.aborts) 334 fmt.Println("Total success", s.runs) 335 if err := s.runTotal(clients[0]); err != nil { 336 log.Fatal(err) 337 } 338 }