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  }