[API] Run scorecard queries concurrently (#339)

### Summary

Before pull request, in `GET /api/v1/scorecards`, the queries for each scorecard were being executed sequentially. This PR changes the handler to run all those queries concurrently.

Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/336
This commit is contained in:
agodnic 2023-05-23 14:50:19 -03:00 committed by GitHub
parent fe574754eb
commit 69251f136d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 33 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt"
"strconv"
"strings"
"sync"
"time"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
@ -350,46 +351,89 @@ func (r *Repository) buildFindVolumeQuery(q *ChainActivityQuery) string {
}
func (r *Repository) GetScorecards(ctx context.Context) (*Scorecards, error) {
tvl, err := r.tvl.Get(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get tvl")
}
messages24h, err := r.getMessages24h(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query 24h messages: %w", err)
}
// This function launches one goroutine for each scorecard.
//
// We use a `sync.WaitGroup` to block until all goroutines are done.
var wg sync.WaitGroup
totalTxCount, err := r.getTotalTxCount(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query total tx count by portal bridge")
}
var messages24h, tvl, totalTxCount, totalTxVolume, txCount24h, volume24h string
totalTxVolume, err := r.getTotalTxVolume(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query tx volume by portal bridge")
}
wg.Add(1)
go func() {
defer wg.Done()
var err error
messages24h, err = r.getMessages24h(ctx)
if err != nil {
r.logger.Error("failed to query 24h messages", zap.Error(err))
}
}()
txCount24h, err := r.getTxCount24h(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query 24h transactions: %w", err)
}
wg.Add(1)
go func() {
defer wg.Done()
var err error
tvl, err = r.tvl.Get(ctx)
if err != nil {
r.logger.Error("failed to get tvl", zap.Error(err))
}
}()
volume24h, err := r.getVolume24h(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query 24h volume: %w", err)
}
wg.Add(1)
go func() {
defer wg.Done()
var err error
totalTxCount, err = r.getTotalTxCount(ctx)
if err != nil {
r.logger.Error("failed to tx count", zap.Error(err))
}
}()
// build the result and return
wg.Add(1)
go func() {
defer wg.Done()
var err error
totalTxVolume, err = r.getTotalTxVolume(ctx)
if err != nil {
r.logger.Error("failed to get total tx volume", zap.Error(err))
}
}()
wg.Add(1)
go func() {
defer wg.Done()
var err error
txCount24h, err = r.getTxCount24h(ctx)
if err != nil {
r.logger.Error("failed to get 24h transactions", zap.Error(err))
}
}()
wg.Add(1)
go func() {
defer wg.Done()
var err error
volume24h, err = r.getVolume24h(ctx)
if err != nil {
r.logger.Error("failed to get 24h volume", zap.Error(err))
}
}()
// Each of the queries synchronized by this wait group has a context timeout.
//
// Hence, this call to `wg.Wait()` will not block indefinitely as long as the
// context timeouts are properly handled in each goroutine.
wg.Wait()
// Build the result and return
scorecards := Scorecards{
Messages24h: messages24h,
TotalTxCount: totalTxCount,
TotalTxVolume: totalTxVolume,
Tvl: tvl,
TxCount24h: txCount24h,
TxCount24h: txCount24h,
Volume24h: volume24h,
}
return &scorecards, nil
}

View File

@ -31,7 +31,8 @@ func NewTVL(p2pNetwork string, cache wormscanCache.Cache, tvlKey string, expirat
// Get get tvl value from cache if exists or call wormhole api to get tvl value and set the in cache for t.expiration time.
func (t *Tvl) Get(ctx context.Context) (string, error) {
// get tvl from cache
// Get tvl from cache
tvl, err := t.cache.Get(ctx, t.tvlKey)
if err == nil {
return tvl, nil
@ -42,8 +43,8 @@ func (t *Tvl) Get(ctx context.Context) (string, error) {
zap.String("key", t.tvlKey))
}
// get tvl from wormhole api
tvlUSD, err := t.api.GetNotionalUSD([]string{"all"})
// Get tvl from wormhole api
tvlUSD, err := t.api.GetNotionalUSD(ctx, []string{"all"})
if err != nil {
t.logger.Error("error getting tvl from wormhole api",
zap.Error(err))
@ -52,7 +53,7 @@ func (t *Tvl) Get(ctx context.Context) (string, error) {
return "", errs.ErrNotFound
}
// set tvl in cache with t.expiration time
// Set tvl in cache with t.expiration time
err = t.cache.Set(ctx, t.tvlKey, *tvlUSD, t.expiration)
if err != nil {
t.logger.Error("error setting tvl in cache",

View File

@ -1,6 +1,7 @@
package tvl
import (
"context"
"io/ioutil"
"net/http"
@ -28,23 +29,28 @@ func NewTvlAPI(net string) *TvlAPI {
}
}
func (c *TvlAPI) GetNotionalUSD(ids []string) (*string, error) {
func (c *TvlAPI) GetNotionalUSD(ctx context.Context, ids []string) (*string, error) {
req, err := http.NewRequest(http.MethodGet, c.url, nil)
// Build the request
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url, nil)
if err != nil {
return nil, err
}
// Send it
res, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
// Read response body
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
// Extract TVL from the response
tvl := gjson.Get(string(body), "AllTime.\\*.\\*.Notional")
response := tvl.String()
return &response, nil