Comment by matdehaast

3 hours ago

I'm a bit worried you think instantiating a new client for every request is common practice. If you did that to Postgres or MySQL clients, you would also have degradation in performance.

PHP has created mysqli or PDO to deal with this specifically because of the known issues of it being expensive to recreate client connects per request

Ok your comment made me double check our benchmarking script in Go. Can confirm we didn't instantiate a new client with each request.

For transparency here's the full Golang benchmarking code if you want to replicate it yourself.

Batch transactions:

  package main

  import (
    "flag"
    "fmt"
    "log"

    . "github.com/tigerbeetle/tigerbeetle-go"
    . "github.com/tigerbeetle/tigerbeetle-go/pkg/types"
  )

  func main() {
    numTransfers := flag.Int("num_transfers", 1, "Number of transfers to create")
    transferID := flag.Uint64("start_transfer_id", 0, "Transfer ID")
    debitAccountID := flag.Uint64("debit_account_id", 0, "Debit Account ID")
    creditAccountID := flag.Uint64("credit_account_id", 0, "Credit Account ID")
    amount := flag.Uint64("amount", 0, "Transfer Amount")
    batchSize := flag.Int("batch_size", 8190, "Batch size") // 8190 is the maximum batch size
    flag.Parse()

    if *transferID == 0 || *debitAccountID == 0 || *creditAccountID == 0 || *amount == 0 {
      log.Fatalf("Usage: ./batch-transfers -num_transfers=<num> -batch_size=<num> -start_transfer_id=<id> -debit_account_id=<id> -credit_account_id=<id> -amount=<amount>")
    }

    client, err := NewClient(ToUint128(0), []string{"3001"})
    if err != nil {
      log.Fatalf("Failed to connect to TigerBeetle: %v", err)
    }
    defer client.Close()

    transfers := make([]Transfer, *numTransfers)
    for i := 0; i < *numTransfers; i++ {
      transfers[i] = Transfer{
        ID:              ToUint128(*transferID + uint64(i)),
        DebitAccountID:  ToUint128(*debitAccountID),
        CreditAccountID: ToUint128(*creditAccountID),
        Ledger:          700,
        Code:            10,
        Amount:          ToUint128(*amount),
      }
    }

    for i := 0; i < len(transfers); i += *batchSize {
      size := *batchSize
      if i+*batchSize > len(transfers) {
        size = len(transfers) - i
      }
      res, _ := client.CreateTransfers(transfers[i : i+size])

      for _, err := range res {
        if err.Result != 0 {
          log.Printf("Error creating transfer %d: %s", err.Index, err.Result)
        }
      }
    }

    balance_res, balance_err := client.GetAccountBalances(AccountFilter{
      AccountID: ToUint128(*debitAccountID),
      Limit: 1,
      Flags: AccountFilterFlags{
          Debits:   true,
          Credits:  true,
          Reversed: true,
        }.ToUint32(),
    })
    if balance_err != nil {
      log.Fatalf("Error looking up account balance: %s", err)
    }
    debit_balance := balance_res[0].DebitsPosted.BigInt()
    fmt.Printf("%v\n", debit_balance.String())
  }

Individual transactions:

  package main

  import (
    "flag"
    "fmt"
    "log"

    . "github.com/tigerbeetle/tigerbeetle-go"
    . "github.com/tigerbeetle/tigerbeetle-go/pkg/types"
  )

  func main() {
    numTransfers := flag.Int("num_transfers", 1, "Number of transfers to create")
    transferID := flag.Uint64("start_transfer_id", 0, "Transfer ID")
    debitAccountID := flag.Uint64("debit_account_id", 0, "Debit Account ID")
    creditAccountID := flag.Uint64("credit_account_id", 0, "Credit Account ID")
    amount := flag.Uint64("amount", 0, "Transfer Amount")
    flag.Parse()

    if *transferID == 0 || *debitAccountID == 0 || *creditAccountID == 0 || *amount == 0 {
      log.Fatalf("Usage: ./individual-transfers -num_transfers=<num> -start_transfer_id=<id> -debit_account_id=<id> -credit_account_id=<id> -amount=<amount>")
    }

    client, err := NewClient(ToUint128(0), []string{"3001"})
    if err != nil {
      log.Fatalf("Failed to connect to TigerBeetle: %v", err)
    }
    defer client.Close()

    transfers := make([]Transfer, *numTransfers)
    for i := 0; i < *numTransfers; i++ {
      transfers[i] = Transfer{
        ID:              ToUint128(*transferID + uint64(i)),
        DebitAccountID:  ToUint128(*debitAccountID),
        CreditAccountID: ToUint128(*creditAccountID),
        Ledger:          700,
        Code:            10,
        Amount:          ToUint128(*amount),
      }
    }

    for i := 0; i < len(transfers); i++ {
      res, _ := client.CreateTransfers([]Transfer{transfers[i]})
      for _, err := range res {
        if err.Result != 0 {
          log.Printf("Error creating transfer %d: %s", err.Index, err.Result)
        }
      }
      if i%100 == 0 {
          fmt.Println("%d\n", i)
      }
    }

    balance_res, balance_err := client.GetAccountBalances(AccountFilter{
      AccountID: ToUint128(*debitAccountID),
      Limit: 1,
      Flags: AccountFilterFlags{
          Debits:   true,
          Credits:  true,
          Reversed: true,
        }.ToUint32(),
    })
    if balance_err != nil {
      log.Fatalf("Error looking up account balance: %s", err)
    }
    debit_balance := balance_res[0].DebitsPosted.BigInt()
    fmt.Printf("%v\n", debit_balance.String())
  }

We shared the code with the Tigerbeetle team (who were very nice and responsive), and they didn't raise any issues with our benchmarking code of their Tigerbeetle client.