status-go/services/wallet/activity/benchmarks_test.go
Stefan 579f7e4a52 chore(wallet) optimize the filer query
Main changes:

- Use tr_type instead of IN clause
- Use binary (X'...' syntax) directly into the query instead of
  converting DB values to HEX
  - Found to be slightly faster than query parameters in the dedicated benchmark
  - Didn't see much improvement in filter benchmarks
- Tried various combinations of optimizations but without impressive performance results

Benchmark results:

| Name                   | Original   | tr_type   | join     | hex      | no-db     | db_only   |     last |    net_j |
|:-----------------------|:-----------|:----------|:---------|:---------|:----------|:----------|---------:|---------:|
| RAM_NoFilter-10        | 49580229   | 51253242  | 51112462 | 50915133 | 121217817 | 141691008 | 50908642 | 50239712 |
| SSD_NoFilter-10        | 49963604   | 51393588  | 51213038 | 50881483 | 120785679 | 141063467 | 50462767 | 49676867 |
| SSD_MovingWindow-10    | 53695712   | 54155292  | 54161733 | 54061325 | 126966633 | 146866017 | 53479929 | 53350475 |
| SSD_AllAddr_AllTos-10  | 41382804   | 41195225  | 51684175 | 52107262 | 64348100  | 97608833  | 50523529 | 49968321 |
| SSD_OneAddress-10      | 34945275   | 35103850  | 31066429 | 31328762 | 50927300  | 54322971  | 30098529 | 30252546 |
| FilterSend_AllAddr-10  | 39546808   | 37566604  | 38389725 | 38260738 | 114820458 | 125588408 | 37127625 | 36864575 |
| FilterSend_6Addr-10    | 41221458   | 41111225  | 40848288 | 40135492 | 118629700 | 128200467 | 38942521 | 39012100 |
| FilterThreeNetworks-10 | -          | -         | -        | -        | -         | -         | 50058929 | 49854450 |

Update status-desktop: #11036
2023-09-21 13:56:44 +02:00

304 lines
8.4 KiB
Go

package activity
import (
"context"
"fmt"
"testing"
eth "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/testutils"
"github.com/status-im/status-go/services/wallet/transfer"
)
func setupBenchmark(b *testing.B, accountsCount int, inMemory bool) (deps FilterDependencies, close func(), accounts []eth.Address) {
deps, close = setupTestActivityDBStorageChoice(b, inMemory)
const transactionCount = 100000
const mtSendRatio = 0.2 // 20%
const mtSwapRatio = 0.1 // 10%
const mtBridgeRatio = 0.1 // 10%
const pendingCount = 10
const mtSendCount = int(float64(transactionCount) * mtSendRatio)
const mtSwapCount = int(float64(transactionCount) * mtSwapRatio)
// Bridge requires two transactions
const mtBridgeCount = int(float64(transactionCount) * (mtBridgeRatio / 2))
trs, _, _ := transfer.GenerateTestTransfers(b, deps.db, 0, transactionCount)
accounts = []eth.Address{}
for i := 0; i < accountsCount; i++ {
if i%2 == 0 {
accounts = append(accounts, trs[i].From)
} else {
accounts = append(accounts, trs[i].To)
}
}
i := 0
multiTxs := make([]transfer.TestMultiTransaction, mtSendCount+mtSwapCount+mtBridgeCount)
for ; i < mtSendCount; i++ {
multiTxs[i] = transfer.GenerateTestSendMultiTransaction(trs[i])
trs[i].From = accounts[i%len(accounts)]
multiTxs[i].FromAddress = trs[i].From
// Currently the network ID is not filled in for send transactions
multiTxs[i].FromNetworkID = nil
multiTxs[i].ToNetworkID = nil
multiTxs[i].MultiTransactionID = transfer.InsertTestMultiTransaction(b, deps.db, &multiTxs[i])
trs[i].MultiTransactionID = multiTxs[i].MultiTransactionID
}
for j := 0; j < mtSwapCount; i, j = i+1, j+1 {
multiTxs[i] = transfer.GenerateTestSwapMultiTransaction(trs[i], testutils.SntSymbol, int64(i))
trs[i].From = accounts[i%len(accounts)]
multiTxs[i].FromAddress = trs[i].From
multiTxs[i].MultiTransactionID = transfer.InsertTestMultiTransaction(b, deps.db, &multiTxs[i])
trs[i].MultiTransactionID = multiTxs[i].MultiTransactionID
}
for mtIdx := 0; mtIdx < mtBridgeCount; i, mtIdx = i+2, mtIdx+1 {
firstTrIdx := i
secondTrIdx := i + 1
multiTxs[mtIdx] = transfer.GenerateTestBridgeMultiTransaction(trs[firstTrIdx], trs[secondTrIdx])
trs[firstTrIdx].From = accounts[i%len(accounts)]
trs[secondTrIdx].To = accounts[(i+3)%len(accounts)]
multiTxs[mtIdx].FromAddress = trs[firstTrIdx].From
multiTxs[mtIdx].ToAddress = trs[secondTrIdx].To
multiTxs[mtIdx].FromAddress = trs[i].From
multiTxs[mtIdx].MultiTransactionID = transfer.InsertTestMultiTransaction(b, deps.db, &multiTxs[mtIdx])
trs[firstTrIdx].MultiTransactionID = multiTxs[mtIdx].MultiTransactionID
trs[secondTrIdx].MultiTransactionID = multiTxs[mtIdx].MultiTransactionID
}
for i = 0; i < transactionCount-pendingCount; i++ {
trs[i].From = accounts[i%len(accounts)]
transfer.InsertTestTransfer(b, deps.db, trs[i].From, &trs[i])
}
for ; i < transactionCount; i++ {
trs[i].From = accounts[i%len(accounts)]
transfer.InsertTestPendingTransaction(b, deps.db, &trs[i])
}
return
}
var allNetEnabled = []common.ChainID(nil)
func BenchmarkGetActivityEntries(bArg *testing.B) {
deps, closeFn, accounts := setupBenchmark(bArg, 6, true)
defer closeFn()
type params struct {
inMemory bool
// resultCount must be nil to expect as many requested
resultCount *int
generateTestParameters func() (addresses []eth.Address, allAddresses bool, networks []common.ChainID, filter *Filter, startIndex int)
}
testCases := []struct {
name string
params params
}{
{
"RAM_NoFilter",
params{
true,
nil,
func() ([]eth.Address, bool, []common.ChainID, *Filter, int) {
return accounts, true, allNetEnabled, &Filter{}, 0
},
},
},
{
"SSD_NoFilter",
params{
false,
nil,
func() ([]eth.Address, bool, []common.ChainID, *Filter, int) {
return accounts, true, allNetEnabled, &Filter{}, 0
},
},
},
{
"SSD_MovingWindow",
params{
false,
nil,
func() ([]eth.Address, bool, []common.ChainID, *Filter, int) {
return accounts, true, allNetEnabled, &Filter{}, 200
},
},
},
{
"SSD_AllAddr_AllTos",
params{
false,
nil,
func() ([]eth.Address, bool, []common.ChainID, *Filter, int) {
return accounts, true, allNetEnabled, &Filter{CounterpartyAddresses: accounts[3:]}, 0
},
},
},
{
"SSD_OneAddress",
params{
false,
nil,
func() ([]eth.Address, bool, []common.ChainID, *Filter, int) {
return accounts[0:1], false, allNetEnabled, &Filter{}, 0
},
},
},
// All memory from here
{
"FilterSend_AllAddr",
params{
true,
nil,
func() ([]eth.Address, bool, []common.ChainID, *Filter, int) {
return accounts, true, allNetEnabled, &Filter{
Types: []Type{SendAT},
}, 0
},
},
},
{
"FilterSend_6Addr",
params{
true,
nil,
func() ([]eth.Address, bool, []common.ChainID, *Filter, int) {
return accounts[len(accounts)-6:], false, allNetEnabled, &Filter{
Types: []Type{SendAT},
}, 0
},
},
},
{
"FilterThreeNetworks",
params{
true,
nil,
func() ([]eth.Address, bool, []common.ChainID, *Filter, int) {
return accounts, true, []common.ChainID{}, &Filter{}, 0
},
},
},
}
const resultCount = 100
for _, tc := range testCases {
addresses, allAddresses, nets, filter, startIndex := tc.params.generateTestParameters()
networks := allNetworksFilter()
if len(nets) > 0 {
networks = nets
}
bArg.Run(tc.name, func(b *testing.B) {
// Reset timer after setup
b.ResetTimer()
// Run benchmark
for i := 0; i < b.N; i++ {
res, err := getActivityEntries(context.Background(), deps, addresses, allAddresses, networks, *filter, startIndex, resultCount)
if err != nil {
b.Error(err)
} else if tc.params.resultCount != nil {
if len(res) != *tc.params.resultCount {
b.Error("Got less then expected")
}
} else if len(res) != resultCount {
b.Error("Got less than requested")
}
}
})
}
}
func BenchmarkSQLQuery(b *testing.B) {
type params struct {
query string
args []interface{}
expectedResCount int
}
deps, closeFn, accounts := setupBenchmark(b, 10000, true)
defer closeFn()
var addrValuesStr, insertAddrValuesStr, addrPlaceholdersStr string
var refAccounts []interface{}
for _, acc := range accounts {
addrValuesStr += fmt.Sprintf("X'%s',", acc.Hex()[2:])
insertAddrValuesStr += fmt.Sprintf("(X'%s'),", acc.Hex()[2:])
addrPlaceholdersStr += "?,"
refAccounts = append(refAccounts, acc)
}
addrValuesStr = addrValuesStr[:len(addrValuesStr)-1]
insertAddrValuesStr = insertAddrValuesStr[:len(insertAddrValuesStr)-1]
addrPlaceholdersStr = addrPlaceholdersStr[:len(addrPlaceholdersStr)-1]
if _, err := deps.db.Exec(fmt.Sprintf("CREATE TEMP TABLE filter_addresses_table (address VARCHAR PRIMARY KEY); INSERT INTO filter_addresses_table (address) VALUES %s;", insertAddrValuesStr)); err != nil {
b.Fatal("failed to create temporary table", err)
}
testCases := []struct {
name string
args params
res testing.BenchmarkResult
}{
{
name: "JoinQuery",
args: params{
query: "SELECT COUNT(*) FROM transfers JOIN filter_addresses_table ON transfers.tx_from_address = filter_addresses_table.address",
expectedResCount: 99990,
},
},
{
name: "LiteralQuery",
args: params{
query: fmt.Sprintf("SELECT COUNT(*) FROM transfers WHERE tx_from_address IN (%s)", addrValuesStr),
expectedResCount: 99990,
},
},
{
name: "ParamQuery",
args: params{
query: fmt.Sprintf("SELECT COUNT(*) FROM transfers WHERE tx_from_address IN (%s)", addrPlaceholdersStr),
args: refAccounts,
expectedResCount: 99990,
},
},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.ResetTimer()
for i := 0; i < b.N; i++ {
res, err := deps.db.Query(tc.args.query, tc.args.args...)
if err != nil {
b.Fatal("failed to query db", err)
}
res.Next()
var count int
if err := res.Scan(&count); err != nil {
b.Fatal("failed to scan db result", err)
}
if count != tc.args.expectedResCount {
b.Fatalf("unexpected result count: %d, expected: %d", count, tc.args.expectedResCount)
}
res.Close()
}
}
})
}
}