Skip to content

Commit

Permalink
feat: various bench improvements (#887)
Browse files Browse the repository at this point in the history
  • Loading branch information
gfyrag authored Nov 23, 2023
1 parent c100ec2 commit bd8497f
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 74 deletions.
14 changes: 12 additions & 2 deletions components/ledger/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pre-commit:

bench:
FROM core+builder-image
DO --pass-args core+GO_INSTALL --package=golang.org/x/perf/cmd/benchstat@latest
COPY (+sources/*) /src
WORKDIR /src/components/ledger/internal/storage/ledgerstore
ARG numberOfTransactions=10000
Expand All @@ -90,19 +91,28 @@ bench:
ARG GOPROXY
ARG testTimeout=10m
ARG bench=.
ARG verbose=0
ARG GOMAXPROCS=2
ARG GOMEMLIMIT=1024MiB
LET additionalArgs=""
IF [ "$verbose" = "1" ]
SET additionalArgs=-v
END
WITH DOCKER --pull postgres:15-alpine
RUN --mount type=cache,id=gopkgcache,target=${GOPATH}/pkg/mod \
--mount type=cache,id=gobuild,target=/root/.cache/go-build \
go test -timeout $testTimeout -bench=$bench -run ^$ \
go test -timeout $testTimeout -bench=$bench -run ^$ $additionalArgs \
-benchtime=$benchTime \
-count=$count \
-transactions=$numberOfTransactions | tee -a /results.txt
END
RUN benchstat /results.txt
SAVE ARTIFACT /results.txt

benchstat:
FROM core+builder-image
DO --pass-args core+GO_INSTALL --package=golang.org/x/perf/cmd/benchstat@latest
COPY --pass-args +bench/results.txt /tmp/branch.txt
COPY --pass-args github.com/formancehq/stack/components/ledger:main+bench/results.txt /tmp/main.txt
ARG compareAgainstRevision=main
COPY --pass-args github.com/formancehq/stack/components/ledger:$compareAgainstRevision+bench/results.txt /tmp/main.txt
RUN benchstat /tmp/main.txt /tmp/branch.txt
171 changes: 103 additions & 68 deletions components/ledger/internal/storage/ledgerstore/store_benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,77 +17,112 @@ import (
)

var nbTransactions = flag.Int("transactions", 10000, "number of transactions to create")
var batch = flag.Int("batch", 1000, "logs batching")

type scenarioInfo struct {
nbAccounts int
}

type scenario struct {
name string
setup func(ctx context.Context, b *testing.B, store *Store) *scenarioInfo
}

var scenarios = []scenario{
{
name: "nominal",
setup: func(ctx context.Context, b *testing.B, store *Store) *scenarioInfo {
var lastLog *ledger.ChainedLog
for i := 0; i < *nbTransactions/(*batch); i++ {
logs := make([]*ledger.ChainedLog, 0)
appendLog := func(log *ledger.Log) {
chainedLog := log.ChainLog(lastLog)
logs = append(logs, chainedLog)
lastLog = chainedLog
}
for j := 0; j < (*batch); j += 2 {
provision := big.NewInt(10000)
itemPrice := provision.Div(provision, big.NewInt(2))
fees := itemPrice.Div(itemPrice, big.NewInt(100)) // 1%

appendLog(ledger.NewTransactionLog(
ledger.NewTransaction().WithPostings(ledger.NewPosting(
"world", fmt.Sprintf("player:%d", j/2), "USD/2", provision,
)).WithID(big.NewInt(int64(i*(*batch)+j))),
map[string]metadata.Metadata{},
))
appendLog(ledger.NewTransactionLog(
ledger.NewTransaction().WithPostings(
ledger.NewPosting(fmt.Sprintf("player:%d", j/2), "seller", "USD/2", itemPrice),
ledger.NewPosting("seller", "fees", "USD/2", fees),
).WithID(big.NewInt(int64(i*(*batch)+j+1))),
map[string]metadata.Metadata{},
))
status := "pending"
if j%8 == 0 {
status = "terminated"
}
appendLog(ledger.NewSetMetadataLog(ledger.Now(), ledger.SetMetadataLogPayload{
TargetType: ledger.MetaTargetTypeTransaction,
TargetID: big.NewInt(int64(i*(*batch) + j + 1)),
Metadata: map[string]string{
"status": status,
},
}))
}
require.NoError(b, store.InsertLogs(ctx, logs...))
}

nbAccounts := *batch / 2

for i := 0; i < nbAccounts; i++ {
lastLog = ledger.NewSetMetadataLog(ledger.Now(), ledger.SetMetadataLogPayload{
TargetType: ledger.MetaTargetTypeAccount,
TargetID: fmt.Sprintf("player:%d", i),
Metadata: map[string]string{
"level": fmt.Sprint(i % 4),
},
}).ChainLog(lastLog)
require.NoError(b, store.InsertLogs(ctx, lastLog))
}

return &scenarioInfo{
nbAccounts: nbAccounts,
}
},
},
}

func BenchmarkList(b *testing.B) {
const batchSize = 1000

ctx := logging.TestingContext()
hooks := make([]bun.QueryHook, 0)
if testing.Verbose() {
hooks = append(hooks, bunexplain.NewExplainHook())
}
store := newLedgerStore(b, hooks...)

var lastLog *ledger.ChainedLog
for i := 0; i < *nbTransactions/batchSize; i++ {
batch := make([]*ledger.ChainedLog, 0)
appendLog := func(log *ledger.Log) {
chainedLog := log.ChainLog(lastLog)
batch = append(batch, chainedLog)
lastLog = chainedLog
}
for j := 0; j < batchSize; j += 2 {
provision := big.NewInt(10000)
itemPrice := provision.Div(provision, big.NewInt(2))
fees := itemPrice.Div(itemPrice, big.NewInt(100)) // 1%

appendLog(ledger.NewTransactionLog(
ledger.NewTransaction().WithPostings(ledger.NewPosting(
"world", fmt.Sprintf("player:%d", j/2), "USD/2", provision,
)).WithID(big.NewInt(int64(i*batchSize+j))),
map[string]metadata.Metadata{},
))
appendLog(ledger.NewTransactionLog(
ledger.NewTransaction().WithPostings(
ledger.NewPosting(fmt.Sprintf("player:%d", j/2), "seller", "USD/2", itemPrice),
ledger.NewPosting("seller", "fees", "USD/2", fees),
).WithID(big.NewInt(int64(i*batchSize+j+1))),
map[string]metadata.Metadata{},
))
status := "pending"
if j%8 == 0 {
status = "terminated"
}
appendLog(ledger.NewSetMetadataLog(ledger.Now(), ledger.SetMetadataLogPayload{
TargetType: ledger.MetaTargetTypeTransaction,
TargetID: big.NewInt(int64(i*batchSize + j + 1)),
Metadata: map[string]string{
"status": status,
},
}))
}
require.NoError(b, store.InsertLogs(ctx, batch...))
}

nbAccounts := batchSize / 2

for i := 0; i < nbAccounts; i++ {
lastLog = ledger.NewSetMetadataLog(ledger.Now(), ledger.SetMetadataLogPayload{
TargetType: ledger.MetaTargetTypeAccount,
TargetID: fmt.Sprintf("player:%d", i),
Metadata: map[string]string{
"level": fmt.Sprint(i % 4),
},
}).ChainLog(lastLog)
require.NoError(b, store.InsertLogs(ctx, lastLog))
}
for _, scenario := range scenarios {
b.Run(scenario.name, func(b *testing.B) {
store := newLedgerStore(b, hooks...)
info := scenario.setup(ctx, b, store)

_, err := store.db.Exec("VACUUM ANALYZE")
require.NoError(b, err)

benchmarksReadTransactions(b, ctx, store, *nbTransactions, nbAccounts)
benchmarksReadAccounts(b, ctx, store, nbAccounts)
benchmarksGetAggregatedBalances(b, ctx, store, nbAccounts)
b.Run("transactions", func(b *testing.B) {
benchmarksReadTransactions(b, ctx, store, info)
})
b.Run("accounts", func(b *testing.B) {
benchmarksReadAccounts(b, ctx, store)
})
b.Run("aggregates", func(b *testing.B) {
benchmarksGetAggregatedBalances(b, ctx, store)
})
})
}
}

func benchmarksReadTransactions(b *testing.B, ctx context.Context, store *Store, nbTransactions, nbAccounts int) {
func benchmarksReadTransactions(b *testing.B, ctx context.Context, store *Store, info *scenarioInfo) {
type testCase struct {
name string
query query.Builder
Expand All @@ -98,23 +133,23 @@ func benchmarksReadTransactions(b *testing.B, ctx context.Context, store *Store,

testCases := []testCase{
{
name: "with no query",
name: "no query",
},
{
name: "using an exact address",
query: query.Match("account", fmt.Sprintf("player:%d", nbAccounts-1)), // Last inserted account
query: query.Match("account", fmt.Sprintf("player:%d", info.nbAccounts-1)), // Last inserted account
},
{
name: "using an address segment",
query: query.Match("account", fmt.Sprintf(":%d", nbAccounts-1)),
query: query.Match("account", fmt.Sprintf(":%d", info.nbAccounts-1)),
},
{
name: "using a metadata metadata",
query: query.Match("metadata[status]", "terminated"),
},
{
name: "using non existent account by exact address",
query: query.Match("account", fmt.Sprintf("player:%d", nbAccounts)),
query: query.Match("account", fmt.Sprintf("player:%d", info)),
allowEmptyResponse: true,
},
{
Expand All @@ -134,7 +169,7 @@ func benchmarksReadTransactions(b *testing.B, ctx context.Context, store *Store,

for _, t := range testCases {
t := t
b.Run("listing transactions "+t.name, func(b *testing.B) {
b.Run(t.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
q := NewGetTransactionsQuery(PaginatedQueryOptions[PITFilterWithVolumes]{
PageSize: 10,
Expand All @@ -156,7 +191,7 @@ func benchmarksReadTransactions(b *testing.B, ctx context.Context, store *Store,
}
}

func benchmarksReadAccounts(b *testing.B, ctx context.Context, store *Store, nbAccounts int) {
func benchmarksReadAccounts(b *testing.B, ctx context.Context, store *Store) {
type testCase struct {
name string
query query.Builder
Expand Down Expand Up @@ -188,7 +223,7 @@ func benchmarksReadAccounts(b *testing.B, ctx context.Context, store *Store, nbA

for _, t := range testCases {
t := t
b.Run("listing accounts "+t.name, func(b *testing.B) {
b.Run(t.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
q := NewGetAccountsQuery(PaginatedQueryOptions[PITFilterWithVolumes]{
PageSize: 10,
Expand All @@ -210,7 +245,7 @@ func benchmarksReadAccounts(b *testing.B, ctx context.Context, store *Store, nbA
}
}

func benchmarksGetAggregatedBalances(b *testing.B, ctx context.Context, store *Store, nbAccounts int) {
func benchmarksGetAggregatedBalances(b *testing.B, ctx context.Context, store *Store) {
type testCase struct {
name string
query query.Builder
Expand All @@ -237,7 +272,7 @@ func benchmarksGetAggregatedBalances(b *testing.B, ctx context.Context, store *S

for _, t := range testCases {
t := t
b.Run("aggregating balance "+t.name, func(b *testing.B) {
b.Run(t.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
ret, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PaginatedQueryOptions[PITFilter]{
PageSize: 10,
Expand Down
4 changes: 2 additions & 2 deletions components/ledger/libs/bun/bunexplain/explain_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ func (h *explainHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) {}
func (h *explainHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context {

lowerQuery := strings.ToLower(event.Query)
if !strings.HasPrefix(lowerQuery, "select") {
if !strings.HasPrefix(lowerQuery, "select") && !strings.HasPrefix(lowerQuery, "with") {
return ctx
}

if err := event.DB.RunInTx(context.Background(), &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
rows, err := tx.Query("explain analyze " + event.Query)
rows, err := tx.Query("explain analyze verbose " + event.Query)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions libs/go-libs/bun/bunexplain/explain_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ func (h *explainHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) {}
func (h *explainHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context {

lowerQuery := strings.ToLower(event.Query)
if !strings.HasPrefix(lowerQuery, "select") {
if !strings.HasPrefix(lowerQuery, "select") && !strings.HasPrefix(lowerQuery, "with") {
return ctx
}

if err := event.DB.RunInTx(context.Background(), &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
rows, err := tx.Query("explain analyze " + event.Query)
rows, err := tx.Query("explain analyze verbose " + event.Query)
if err != nil {
return err
}
Expand Down

1 comment on commit bd8497f

@vercel
Copy link

@vercel vercel bot commented on bd8497f Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.